mirror of
https://github.com/android/ndk-samples
synced 2025-11-05 23:20:53 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2eef15d3 | ||
|
|
44d725a358 | ||
|
|
9d05e29492 |
@@ -1,9 +1,10 @@
|
||||
WEBP Samples
|
||||
=============
|
||||
Webp is an Android sample including a small app to demo usage of webp in [Native Activity](http://developer.android.com/reference/android/app/NativeActivity.html)
|
||||
view:
|
||||
- rotate decoding 3 webp images and load them into on-screen buffer. Decoding is in its own thread
|
||||
Sample in this directory is to demonstrate image decoding
|
||||
- view: demonstrate how to use webp in [Native Activity](http://developer.android.com/reference/android/app/NativeActivity.html)
|
||||
- image-decoder: demonstrate AImageDecoder usage integrated in Android 12
|
||||
|
||||
Note that the directory names will be changed in DP2
|
||||
|
||||
This sample uses the new [Android Studio CMake plugin](https://developer.android.com/ndk/guides/cmake.html).
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#Thu Feb 18 09:46:00 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
|
||||
40
webp/image-decoder/README.md
Normal file
40
webp/image-decoder/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Image-Decoder
|
||||
The sample demonstrates how to use the new AImageDecoder animated decoding API,
|
||||
added in Android 12. The animated images such as webp, avif and other formats, are
|
||||
decoded and copied into the native window to display them on the screen.
|
||||
|
||||
The goal of the sample is to collect feedback for new AImageDecoder API added in Android 12
|
||||
and Android 11, please try it out and let us know your thoughts!
|
||||
|
||||
## Pre-requistites
|
||||
- Android Gradle Plugin 4.1.2
|
||||
- Platform SDK and NDK for Android 12
|
||||
- An Android device running Android 12(API level 31)
|
||||
|
||||
## Getting Start
|
||||
- build the sample and run it on an Android 12 device
|
||||
- swap in new image files: drop them into src/main/assets/images, and add to imageFiles[] in image_viewer.cpp
|
||||
|
||||
## Support
|
||||
Android 12, AImageDecoder API and this sample are at developer preview stage, you
|
||||
may expect changes in the future Android 12 release cycles. If you find any error
|
||||
or have any feedback, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
|
||||
Comments and patches are highly very welcome too!
|
||||
|
||||
## Copyright
|
||||
Copyright 2021 Google, Inc.
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this
|
||||
file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||
use this file except in compliance with the License. You may obtain a copy of
|
||||
the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations under
|
||||
the License.
|
||||
30
webp/image-decoder/build.gradle
Normal file
30
webp/image-decoder/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 'android-S'
|
||||
ndkVersion '23.0.7123448'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.android.example.webp_image_decoder'
|
||||
minSdkVersion 'S'
|
||||
targetSdkVersion 'S'
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version '3.10.2'
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
webp/image-decoder/src/main/AndroidManifest.xml
Normal file
26
webp/image-decoder/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.example.webp_accelerated_view"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-feature android:glEsVersion="0x00030002"></uses-feature>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:hasCode="false">
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="image_decoder" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
webp/image-decoder/src/main/assets/images/android.avif
Normal file
BIN
webp/image-decoder/src/main/assets/images/android.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 244 KiB |
BIN
webp/image-decoder/src/main/assets/images/android.heic
Normal file
BIN
webp/image-decoder/src/main/assets/images/android.heic
Normal file
Binary file not shown.
BIN
webp/image-decoder/src/main/assets/images/android.webp
Normal file
BIN
webp/image-decoder/src/main/assets/images/android.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 811 KiB |
42
webp/image-decoder/src/main/cpp/CMakeLists.txt
Normal file
42
webp/image-decoder/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
#[[
|
||||
# Copyright (C) 2021 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#]]
|
||||
project("image_decoder")
|
||||
cmake_minimum_required(VERSION 3.10.2)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
|
||||
# Build native_app_glue as a static lib
|
||||
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
|
||||
|
||||
add_library(native_app_glue STATIC
|
||||
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
||||
|
||||
# Create app's shared lib
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall")
|
||||
|
||||
# Export ANativeActivity_onCreate(),
|
||||
# Refer to: https://github.com/android-ndk/ndk/issues/381.
|
||||
set(CMAKE_SHARED_LINKER_FLAGS
|
||||
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
|
||||
|
||||
add_library(image_decoder SHARED
|
||||
android_main.cpp
|
||||
image_viewer.cpp
|
||||
image_decoder.cpp)
|
||||
|
||||
target_link_libraries(image_decoder native_app_glue jnigraphics android log m)
|
||||
54
webp/image-decoder/src/main/cpp/android_debug.h
Normal file
54
webp/image-decoder/src/main/cpp/android_debug.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#if 1
|
||||
|
||||
#ifndef MODULE_NAME
|
||||
#define MODULE_NAME "ImageDecoder"
|
||||
#endif
|
||||
|
||||
#define LOGV(...) \
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGD(...) \
|
||||
__android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGI(...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGW(...) \
|
||||
__android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGE(...) \
|
||||
__android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGF(...) \
|
||||
__android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
|
||||
|
||||
#define ASSERT(cond, ...) \
|
||||
if (!(cond)) { \
|
||||
__android_log_assert(#cond, MODULE_NAME, __VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
|
||||
#define LOGV(...)
|
||||
#define LOGD(...)
|
||||
#define LOGI(...)
|
||||
#define LOGW(...)
|
||||
#define LOGE(...)
|
||||
#define LOGF(...)
|
||||
#define ASSERT(cond, ...)
|
||||
|
||||
#endif
|
||||
107
webp/image-decoder/src/main/cpp/android_main.cpp
Normal file
107
webp/image-decoder/src/main/cpp/android_main.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "image_viewer.h"
|
||||
#include "android_debug.h"
|
||||
|
||||
static int32_t ProcessAndroidInput(struct android_app *app, AInputEvent *event) {
|
||||
ImageViewer* imageViewer = reinterpret_cast<ImageViewer*>(app->userData);
|
||||
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
|
||||
imageViewer->StartAnimation(true);
|
||||
return 1;
|
||||
} else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
|
||||
LOGI("Key event: action=%d keyCode=%d metaState=0x%x",
|
||||
AKeyEvent_getAction(event),
|
||||
AKeyEvent_getKeyCode(event),
|
||||
AKeyEvent_getMetaState(event));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ProcessAndroidCmd(struct android_app *app, int32_t cmd) {
|
||||
static int32_t format = WINDOW_FORMAT_RGBA_8888;
|
||||
ImageViewer* imageViewer = reinterpret_cast<ImageViewer*>(app->userData);
|
||||
switch (cmd) {
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
if (imageViewer->GetAndroidApp()->window) {
|
||||
// save current format to format variable, and set
|
||||
// display format to 8888
|
||||
format = ANativeWindow_getFormat(app->window);
|
||||
ANativeWindow_setBuffersGeometry(app->window,
|
||||
ANativeWindow_getWidth(app->window),
|
||||
ANativeWindow_getHeight(app->window),
|
||||
WINDOW_FORMAT_RGBA_8888);
|
||||
imageViewer->PrepareDrawing();
|
||||
imageViewer->UpdateDisplay();
|
||||
imageViewer->StartAnimation(true);
|
||||
}
|
||||
break;
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
imageViewer->StartAnimation(false);
|
||||
imageViewer->TerminateDisplay();
|
||||
ANativeWindow_setBuffersGeometry(app->window,
|
||||
ANativeWindow_getWidth(app->window),
|
||||
ANativeWindow_getHeight(app->window),
|
||||
format);
|
||||
break;
|
||||
case APP_CMD_LOST_FOCUS:
|
||||
imageViewer->StartAnimation(false);
|
||||
imageViewer->UpdateDisplay();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Android application glue entry function for us
|
||||
extern "C" void android_main(struct android_app* state) {
|
||||
|
||||
ImageViewer imageViewer(state);
|
||||
|
||||
state->userData = reinterpret_cast<void*>(&imageViewer);
|
||||
state->onAppCmd = ProcessAndroidCmd;
|
||||
state->onInputEvent = ProcessAndroidInput;
|
||||
|
||||
// loop waiting for stuff to do.
|
||||
while (1) {
|
||||
// Read all pending events.
|
||||
int ident;
|
||||
int events;
|
||||
struct android_poll_source* source;
|
||||
|
||||
// If not animating, we will block forever waiting for events.
|
||||
// If animating, we loop until all events are read, then continue
|
||||
// to draw the next frame of animation.
|
||||
while ((ident = ALooper_pollAll(imageViewer.IsAnimating() ? 0 : -1, NULL, &events,
|
||||
(void**)&source)) >= 0) {
|
||||
|
||||
// Process this event.
|
||||
if (source != NULL) {
|
||||
source->process(state, source);
|
||||
}
|
||||
|
||||
// Check if we are exiting.
|
||||
if (state->destroyRequested != 0) {
|
||||
LOGI("Engine thread destroy requested!");
|
||||
imageViewer.TerminateDisplay();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageViewer.IsAnimating()) {
|
||||
imageViewer.UpdateDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
webp/image-decoder/src/main/cpp/display_timer.h
Normal file
57
webp/image-decoder/src/main/cpp/display_timer.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
|
||||
/**
|
||||
* DisplayTimer: a utility class to track the display time in nano seconds
|
||||
*/
|
||||
class DisplayTimer {
|
||||
public:
|
||||
explicit DisplayTimer(void) {
|
||||
Reset(0);
|
||||
}
|
||||
|
||||
void Reset(uint64_t durationInNanoSecond) {
|
||||
durationInNano = durationInNanoSecond;
|
||||
struct timespec cur;
|
||||
clock_gettime(CLOCKS_MONO, &cur);
|
||||
startTime = cur.tv_sec * 1000000000UL + cur.tv_nsec;
|
||||
}
|
||||
|
||||
bool IsExpired(void) {
|
||||
uint64_t timePassed = GetTimePassed();
|
||||
return timePassed >= durationInNano ? true : false;
|
||||
}
|
||||
|
||||
uint64_t timeLeft(void) {
|
||||
uint64_t timePassed = GetTimePassed();
|
||||
return (timePassed >= durationInNano) ? 0 : (timePassed - durationInNano);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t GetTimePassed(void) {
|
||||
struct timespec cur;
|
||||
clock_gettime(CLOCKS_MONO, &cur);
|
||||
uint64_t curNS = cur.tv_sec * 1000000000UL + cur.tv_nsec;
|
||||
|
||||
return (curNS - startTime);
|
||||
}
|
||||
|
||||
uint64_t durationInNano;
|
||||
uint64_t startTime;
|
||||
};
|
||||
240
webp/image-decoder/src/main/cpp/image_decoder.cpp
Normal file
240
webp/image-decoder/src/main/cpp/image_decoder.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/data_space.h>
|
||||
#include "image_decoder.h"
|
||||
#include "image_scale_descriptor.h"
|
||||
#include "android_debug.h"
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
|
||||
// Default displaying time image for each frame, used when switching from one image file to
|
||||
// the next image file at run time:
|
||||
// - frames inside the animated files would provide the display time, so kDEFAULT_DURATION
|
||||
// is not needed
|
||||
// - non-animated image files will need this.
|
||||
const uint64_t kDEFAULT_DURATION = (2 * 1000000000UL); // 2 second
|
||||
|
||||
/*
|
||||
* ImageDecoder constructor:
|
||||
*/
|
||||
ImageDecoder::ImageDecoder(const char** files, uint32_t count,
|
||||
DecodeSurfaceDescriptor *frameBuf,
|
||||
AAssetManager* mgr)
|
||||
: assetMgr(mgr), curImgFile(nullptr), imgAsset(nullptr),
|
||||
decoder(nullptr),
|
||||
headerInfo(nullptr), frameInfo(nullptr), frameDuration(0UL),
|
||||
decodeState(DecodeState::IDLE),
|
||||
decodedImageStride(0), rotationAngle(0) {
|
||||
|
||||
for (auto i = 0; i < count; i++) {
|
||||
fileNames.push(files[i]);
|
||||
}
|
||||
|
||||
bufInfo = *frameBuf;
|
||||
if (count && frameBuf) {
|
||||
switch (bufInfo.format) {
|
||||
case SurfaceFormat::SURFACE_FORMAT_RGB_565:
|
||||
bytePerPixel = 2;
|
||||
break;
|
||||
case SurfaceFormat::SURFACE_FORMAT_RGBA_8888:
|
||||
case SurfaceFormat::SURFACE_FORMAT_RGBX_8888:
|
||||
bytePerPixel = 4;
|
||||
break;
|
||||
default:
|
||||
ASSERT(false, "Unsupported display buffer format(%d) to %s",
|
||||
bufInfo.format, __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
memset(&targetRect, 0, sizeof(targetRect));
|
||||
}
|
||||
|
||||
/**
|
||||
* Report decoder is currently decoding frames.
|
||||
* User should call this function before sending command to decode the
|
||||
* next frame.
|
||||
*/
|
||||
bool ImageDecoder::IsBusy(void) {
|
||||
return (decodeState == DecodeState::BUSY ? true : false);
|
||||
}
|
||||
/*
|
||||
* DecodeNextImage(): start decode the next image or frame.
|
||||
*/
|
||||
bool ImageDecoder::DecodeNextImage(void) {
|
||||
|
||||
std::unique_lock<std::mutex> decodeLock(decodeMutex);
|
||||
|
||||
if(IsBusy())
|
||||
return false;
|
||||
|
||||
int status;
|
||||
if(!curImgFile) {
|
||||
curImgFile = fileNames.front();
|
||||
fileNames.pop();
|
||||
// Open the asset with the give name from the APK's assets folder.
|
||||
imgAsset =
|
||||
AAssetManager_open(assetMgr, curImgFile, AASSET_MODE_BUFFER);
|
||||
ASSERT(imgAsset, "%s does not exist in %s", curImgFile,
|
||||
__FUNCTION__);
|
||||
|
||||
// Create an AImageDecoder from the given AAsset
|
||||
decoder = nullptr;
|
||||
status = AImageDecoder_createFromAAsset(imgAsset, &decoder);
|
||||
ASSERT(ANDROID_IMAGE_DECODER_SUCCESS == status,
|
||||
"Failed to create ImageDecoder for %s with the error of %s",
|
||||
curImgFile, AImageDecoder_resultToString(status));
|
||||
|
||||
status = AImageDecoder_setAndroidBitmapFormat(
|
||||
decoder, GetDecodingBitmapFormat());
|
||||
ASSERT(ANDROID_IMAGE_DECODER_SUCCESS == status,
|
||||
"Failed to request decoding output format %s(format: %d)",
|
||||
curImgFile, bufInfo.format);
|
||||
|
||||
status = AImageDecoder_setUnpremultipliedRequired(decoder, false);
|
||||
ASSERT(ANDROID_IMAGE_DECODER_SUCCESS == status,
|
||||
"Failed to use pre-multiply alpha for %s", curImgFile);
|
||||
|
||||
// scale the image to the max possible size to fit into display window
|
||||
// but keep the image's aspect ratio.
|
||||
headerInfo =AImageDecoder_getHeaderInfo(decoder);
|
||||
ASSERT(headerInfo != nullptr, "Failed to get ImageHeaderInfo for %s",
|
||||
curImgFile);
|
||||
|
||||
ARect imgRect = {
|
||||
.left = 0, .top = 0,
|
||||
.right = AImageDecoderHeaderInfo_getWidth(headerInfo),
|
||||
.bottom = AImageDecoderHeaderInfo_getHeight(headerInfo)
|
||||
};
|
||||
ARect dstRect = {
|
||||
.left =0, .top = 0,
|
||||
.right = bufInfo.width,
|
||||
.bottom =bufInfo.height
|
||||
};
|
||||
ImageScaleDescriptor scalerInfo(dstRect, imgRect);
|
||||
rotationAngle = scalerInfo.getScaleRectInfo(&targetRect);
|
||||
status = AImageDecoder_setTargetSize(decoder, targetRect.right - targetRect.left,
|
||||
targetRect.bottom - targetRect.top);
|
||||
ASSERT(ANDROID_IMAGE_DECODER_SUCCESS == status,
|
||||
"Failed to set target rect (%d, %d), error:%s", bufInfo.width, bufInfo.height,
|
||||
AImageDecoder_resultToString(status));
|
||||
|
||||
// scRGB/sRGB/SCRGB_LINEAR are okay for this sample
|
||||
ADataSpace dataSpace =
|
||||
static_cast<ADataSpace>(AImageDecoderHeaderInfo_getDataSpace(headerInfo));
|
||||
if (dataSpace != ADATASPACE_SCRGB && dataSpace != ADATASPACE_SRGB &&
|
||||
dataSpace != ADATASPACE_SCRGB_LINEAR) {
|
||||
status = AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
ASSERT(ANDROID_IMAGE_DECODER_SUCCESS == status,
|
||||
"Failed to set SRGB color space for %s", curImgFile);
|
||||
}
|
||||
|
||||
decodedImageStride = AImageDecoder_getMinimumStride(decoder);
|
||||
|
||||
decodedImageBits.resize(bufInfo.height * decodedImageStride);
|
||||
}
|
||||
|
||||
frameDuration = kDEFAULT_DURATION;
|
||||
if (!frameInfo) {
|
||||
// create frameInfo for each image file, delete it when that file decoding is completed.
|
||||
// for animated image files, frameInfo will be created once and reused for the whole file.
|
||||
frameInfo = AImageDecoderFrameInfo_create();
|
||||
ASSERT(frameInfo, "Failed to create FrameInfo object for %s", curImgFile);
|
||||
}
|
||||
if (AImageDecoder_isAnimated(decoder) &&
|
||||
ANDROID_IMAGE_DECODER_SUCCESS == AImageDecoder_getFrameInfo(decoder, frameInfo)) {
|
||||
frameDuration = AImageDecoderFrameInfo_getDuration(frameInfo);
|
||||
}
|
||||
|
||||
// Create and start decoding thread: the thread is detached right after creation,
|
||||
// hence it will run to completion.
|
||||
decodeState = DecodeState::BUSY;
|
||||
std::thread decodingThread([&] () {
|
||||
std::unique_lock<std::mutex> threadLock(decodeMutex);
|
||||
int status = AImageDecoder_decodeImage(decoder,
|
||||
decodedImageBits.data(),
|
||||
decodedImageStride,
|
||||
decodedImageBits.size());
|
||||
ASSERT(status == ANDROID_IMAGE_DECODER_SUCCESS, "Failed to decode image %s",
|
||||
curImgFile);
|
||||
|
||||
if(!AImageDecoder_isAnimated(decoder) ||
|
||||
ANDROID_IMAGE_DECODER_SUCCESS != AImageDecoder_advanceFrame(decoder)) {
|
||||
if (frameInfo) {
|
||||
AImageDecoderFrameInfo_delete(frameInfo);
|
||||
frameInfo = nullptr;
|
||||
}
|
||||
|
||||
AImageDecoder_delete(decoder);
|
||||
decoder = nullptr;
|
||||
AAsset_close(imgAsset);
|
||||
imgAsset = nullptr;
|
||||
fileNames.push(curImgFile);
|
||||
curImgFile = nullptr;
|
||||
}
|
||||
decodeState = DecodeState::COMPLETED;
|
||||
});
|
||||
decodingThread.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the decoded frame info.
|
||||
* param frame - pointer to data structure for decoded frame info
|
||||
* return bool:
|
||||
* true - image successfully copied into frame
|
||||
* false - image is not ready (not decoded yet)
|
||||
*/
|
||||
bool ImageDecoder::GetDecodedFrame(DecodeFrameDescriptor* _Nonnull frame) {
|
||||
if(decodeState == DecodeState::COMPLETED) {
|
||||
frame->bits = decodedImageBits.data();
|
||||
frame->width = targetRect.right - targetRect.left;
|
||||
frame->height = targetRect.bottom - targetRect.top;
|
||||
frame->stride = decodedImageStride;
|
||||
frame->rotation = rotationAngle;
|
||||
frame->displayDuration = frameDuration;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** GetDecodingBitmapFormat():
|
||||
* translate the required image format to image decoder's format
|
||||
*/
|
||||
AndroidBitmapFormat ImageDecoder::GetDecodingBitmapFormat() {
|
||||
switch (bufInfo.format) {
|
||||
case SurfaceFormat::SURFACE_FORMAT_RGBA_8888:
|
||||
return ANDROID_BITMAP_FORMAT_RGBA_8888;
|
||||
case SurfaceFormat::SURFACE_FORMAT_RGB_565:
|
||||
return ANDROID_BITMAP_FORMAT_RGB_565;
|
||||
default:
|
||||
LOGE("Unsupported display buffer format (%d) in %s", bufInfo.format, __FUNCTION__);
|
||||
}
|
||||
return ANDROID_BITMAP_FORMAT_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor to wait for outstanding decoding finish before
|
||||
* tearing down.
|
||||
*/
|
||||
ImageDecoder::~ImageDecoder() {
|
||||
// make sure the decoding thread is finished
|
||||
std::lock_guard<std::mutex> lock(decodeMutex);
|
||||
}
|
||||
|
||||
114
webp/image-decoder/src/main/cpp/image_decoder.h
Normal file
114
webp/image-decoder/src/main/cpp/image_decoder.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/imagedecoder.h>
|
||||
|
||||
enum class DecodeState { IDLE, BUSY, COMPLETED };
|
||||
enum class SurfaceFormat : unsigned int {
|
||||
SURFACE_FORMAT_RGBA_8888,
|
||||
SURFACE_FORMAT_RGBX_8888,
|
||||
SURFACE_FORMAT_RGB_565,
|
||||
SURFACE_FORMAT_YUV_420 // Not implemented yet
|
||||
};
|
||||
struct DecodeSurfaceDescriptor {
|
||||
// surface size in pixels
|
||||
int32_t width, height, stride;
|
||||
SurfaceFormat format;
|
||||
};
|
||||
|
||||
struct DecodeFrameDescriptor {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t stride;
|
||||
uint8_t* _Nullable bits;
|
||||
int32_t rotation;
|
||||
uint64_t displayDuration;
|
||||
};
|
||||
|
||||
/*
|
||||
* ImageDecoder:
|
||||
* Select a picture to decode in its own thread. The thread is deleted
|
||||
* once a picture is decoded into its internal memory. The next picture
|
||||
* will be decoded when:
|
||||
* - the decoded frame is retrieved
|
||||
* - Another DecodeFrame() is called [this is trigger]
|
||||
* when display format changes, call DestroyDecoder() to release this decoder
|
||||
* and allocate a new decoder object.
|
||||
*/
|
||||
class ImageDecoder {
|
||||
public:
|
||||
explicit ImageDecoder(const char* _Nonnull * _Nonnull files, uint32_t count,
|
||||
DecodeSurfaceDescriptor * _Nullable frameBuf,
|
||||
AAssetManager* _Nonnull assetMgr);
|
||||
|
||||
/**
|
||||
* Report whether the decoder is currently decoding frames.
|
||||
* Caller should:
|
||||
* - call isBusy() to make sure decoder is not busy
|
||||
* - call DecoderNextImage() to kick start decoding
|
||||
* - [Optional] call isBusy() to make sure frame is decoded
|
||||
* - call getDecodedFrame() to retrieve the decoded bits
|
||||
* - call DecodeNextImage() again to kick start the next frame decoding
|
||||
* ...
|
||||
*/
|
||||
bool IsBusy(void);
|
||||
|
||||
/**
|
||||
* Kick start to decode the next image(or frame). Refer to isBusy() for usage.
|
||||
* This is non-re-entrant function: do not call if previous frame is not completed
|
||||
* yet.
|
||||
* return bool: true - decoding started,
|
||||
* false - error happened, decoding could not proceed
|
||||
*/
|
||||
bool DecodeNextImage(void);
|
||||
|
||||
/**
|
||||
* Report the decoded frame info.
|
||||
* param frame - pointer to data structure for decoded frame info
|
||||
* return bool:
|
||||
* true - image successfully copied into the given frame.
|
||||
* false - image is not ready (not decoded yet).
|
||||
*/
|
||||
bool GetDecodedFrame(DecodeFrameDescriptor* _Nonnull frame);
|
||||
|
||||
~ImageDecoder();
|
||||
|
||||
private:
|
||||
DecodeSurfaceDescriptor bufInfo;
|
||||
AAssetManager * _Nonnull assetMgr;
|
||||
std::queue<const char* _Nonnull> fileNames;
|
||||
const char* _Nullable curImgFile;
|
||||
AAsset* _Nullable imgAsset;
|
||||
AImageDecoder* _Nullable decoder;
|
||||
const AImageDecoderHeaderInfo* _Nullable headerInfo;
|
||||
AImageDecoderFrameInfo* _Nullable frameInfo;
|
||||
uint64_t frameDuration;
|
||||
|
||||
std::atomic<DecodeState> decodeState;
|
||||
std::vector<uint8_t> decodedImageBits;
|
||||
int32_t decodedImageStride;
|
||||
ARect targetRect;
|
||||
int32_t rotationAngle;
|
||||
|
||||
uint32_t bytePerPixel;
|
||||
|
||||
std::mutex decodeMutex;
|
||||
AndroidBitmapFormat GetDecodingBitmapFormat();
|
||||
};
|
||||
79
webp/image-decoder/src/main/cpp/image_scale_descriptor.h
Normal file
79
webp/image-decoder/src/main/cpp/image_scale_descriptor.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android/rect.h>
|
||||
#include <cstring>
|
||||
|
||||
class ImageScaleDescriptor {
|
||||
public:
|
||||
explicit ImageScaleDescriptor(ARect dRect, ARect sRect) {
|
||||
requestRect = dRect;
|
||||
srcRect = sRect;
|
||||
memset(&resultRect, 0, sizeof(requestRect));
|
||||
rotationAngle = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the min scaled rectangle size.
|
||||
* @param rect: resulting scaled rectangle
|
||||
* @return counter clockwise rotation angle
|
||||
*/
|
||||
int32_t getScaleRectInfo(ARect* _Nonnull rect) {
|
||||
bool srcPortrait = true;
|
||||
int32_t srcH = srcRect.bottom - srcRect.top;
|
||||
int32_t srcW = srcRect.right - srcRect.left;
|
||||
if(srcW > srcH) srcPortrait = false;
|
||||
|
||||
bool dstPortrait = true;
|
||||
int32_t dstH = requestRect.bottom - requestRect.top;
|
||||
int32_t dstW = requestRect.right - requestRect.left;
|
||||
if(dstW > dstH) dstPortrait = false;
|
||||
|
||||
int32_t height =0, width = 0;
|
||||
if(dstPortrait != srcPortrait) {
|
||||
rotationAngle = 270;
|
||||
float hRatio = static_cast<float>(dstH) / srcW;
|
||||
float wRatio = static_cast<float>(dstW) / srcH;
|
||||
|
||||
float ratio = (hRatio < wRatio)? hRatio : wRatio;
|
||||
|
||||
width = static_cast<int32_t>(ratio * srcW);
|
||||
height = static_cast<int32_t>(ratio * srcH);
|
||||
|
||||
} else {
|
||||
rotationAngle = 0;
|
||||
float hRatio = static_cast<float>(dstH) / srcH;
|
||||
float wRatio = static_cast<float>(dstW) / srcW;
|
||||
float ratio = (hRatio < wRatio) ? hRatio : wRatio;
|
||||
height = static_cast<int32_t>(ratio * srcH);
|
||||
width = static_cast<int32_t>(ratio * srcW);
|
||||
}
|
||||
|
||||
rect->left = 0;
|
||||
rect->right = width;
|
||||
rect->top = 0;
|
||||
rect->bottom = height;
|
||||
return rotationAngle;
|
||||
}
|
||||
|
||||
~ImageScaleDescriptor() {};
|
||||
|
||||
private:
|
||||
ARect requestRect, srcRect, resultRect;
|
||||
int32_t rotationAngle; // Counter Clock rotation Angle
|
||||
};
|
||||
|
||||
|
||||
278
webp/image-decoder/src/main/cpp/image_viewer.cpp
Normal file
278
webp/image-decoder/src/main/cpp/image_viewer.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <android/native_window.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include "image_viewer.h"
|
||||
#include "image_decoder.h"
|
||||
#include "display_timer.h"
|
||||
#include "android_debug.h"
|
||||
|
||||
/*
|
||||
* Compressed files that are inside assets folder:
|
||||
* they will be decoded and displayed as slide-show
|
||||
* decoding will happen in its own thread
|
||||
*/
|
||||
const char * imageFiles[] = {
|
||||
"images/android.webp",
|
||||
"images/android.avif",
|
||||
"images/android.heic",
|
||||
};
|
||||
const int kFRAME_COUNT = sizeof(imageFiles) / sizeof(imageFiles[0]);
|
||||
|
||||
ImageViewer::ImageViewer(android_app* _Nullable app) :
|
||||
androidApp(app),
|
||||
imgDecoder(nullptr),
|
||||
animatingInProgress(false) {
|
||||
|
||||
}
|
||||
|
||||
ImageViewer::~ImageViewer() {
|
||||
delete imgDecoder;
|
||||
}
|
||||
|
||||
struct android_app* _Nullable ImageViewer::GetAndroidApp(void) const{
|
||||
return androidApp;
|
||||
}
|
||||
void ImageViewer::StartAnimation(bool start) {
|
||||
animatingInProgress = start;
|
||||
}
|
||||
bool ImageViewer::IsAnimating(void) const {
|
||||
return animatingInProgress;
|
||||
}
|
||||
void ImageViewer::TerminateDisplay(void) {
|
||||
StartAnimation(false);
|
||||
}
|
||||
|
||||
// Engine class implementations
|
||||
bool ImageViewer::PrepareDrawing(void) {
|
||||
// create decoder
|
||||
if (imgDecoder) {
|
||||
delete imgDecoder;
|
||||
imgDecoder = nullptr;
|
||||
}
|
||||
ANativeWindow_Buffer buf;
|
||||
if (ANativeWindow_lock(androidApp->window, &buf, NULL) < 0) {
|
||||
LOGW("Unable to lock window buffer to create decoder");
|
||||
return false;
|
||||
}
|
||||
ClearFrameBuffer(&buf);
|
||||
ANativeWindow_unlockAndPost(androidApp->window);
|
||||
|
||||
DecodeSurfaceDescriptor descriptor;
|
||||
switch (buf.format) {
|
||||
case WINDOW_FORMAT_RGB_565:
|
||||
descriptor.format = SurfaceFormat::SURFACE_FORMAT_RGB_565;
|
||||
break;
|
||||
case WINDOW_FORMAT_RGBX_8888:
|
||||
descriptor.format = SurfaceFormat::SURFACE_FORMAT_RGBX_8888;
|
||||
break;
|
||||
case WINDOW_FORMAT_RGBA_8888:
|
||||
descriptor.format = SurfaceFormat::SURFACE_FORMAT_RGBA_8888;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
descriptor.width = buf.width;
|
||||
descriptor.height = buf.height;
|
||||
descriptor.stride = buf.stride;
|
||||
|
||||
imgDecoder = new ImageDecoder(imageFiles, kFRAME_COUNT, &descriptor, androidApp->activity->assetManager);
|
||||
|
||||
if (!imgDecoder) {
|
||||
LOGE("Unable to create ImageDecoder instance in %s", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kick start the 1st image decoding
|
||||
imgDecoder->DecodeNextImage();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only copy decoded webp picture when:
|
||||
* - current frame has been on for kFrame_DISPLAY_TIME seconds
|
||||
* - a new picture is decoded
|
||||
* After copying, start decoding the next frame
|
||||
*/
|
||||
bool ImageViewer::UpdateDisplay(void) {
|
||||
if (!androidApp->window || !imgDecoder) {
|
||||
LOGE("Decoder(%p) or NativeWindow(%p) is not ready in %s",
|
||||
androidApp->window, imgDecoder, __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!displayTimer.IsExpired() || imgDecoder->IsBusy()) {
|
||||
// current frame is displayed less than required duration or image is not ready
|
||||
return false;
|
||||
}
|
||||
|
||||
DecodeFrameDescriptor rawImg;
|
||||
if(!imgDecoder->GetDecodedFrame(&rawImg))
|
||||
return false;
|
||||
|
||||
ANativeWindow_Buffer buffer;
|
||||
if (ANativeWindow_lock(androidApp->window, &buffer, nullptr) < 0) {
|
||||
LOGW("Unable to lock window buffer");
|
||||
return false;
|
||||
}
|
||||
ClearFrameBuffer(&buffer); // clear the screen
|
||||
UpdateFrameBuffer(&buffer, &rawImg);
|
||||
ANativeWindow_unlockAndPost(androidApp->window);
|
||||
|
||||
displayTimer.Reset(rawImg.displayDuration);
|
||||
|
||||
imgDecoder->DecodeNextImage();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to translate ANativeWindow format to byte per pixel.
|
||||
* @param buf: points to the ANativeWindow format.
|
||||
* @return int32_t: byte per pixel for the give ANativeWindow
|
||||
*/
|
||||
int32_t ImageViewer::getNativeWindowBpp(ANativeWindow_Buffer* _Nonnull buf) {
|
||||
uint32_t bpp = 0;
|
||||
switch (buf->format) {
|
||||
case WINDOW_FORMAT_RGB_565:
|
||||
bpp = 2;
|
||||
break;
|
||||
case WINDOW_FORMAT_RGBA_8888:
|
||||
case WINDOW_FORMAT_RGBX_8888:
|
||||
bpp = 4;
|
||||
break;
|
||||
default:
|
||||
LOGE("Could not recognize the NativeWindow format: %d in %s",
|
||||
buf->format, __FUNCTION__);
|
||||
assert(0);;
|
||||
}
|
||||
|
||||
return bpp;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClearFrameBuffer: zero out the framebuffer
|
||||
* @param buf: the display buffer to clear
|
||||
*/
|
||||
void ImageViewer::ClearFrameBuffer(ANativeWindow_Buffer * _Nonnull buf) {
|
||||
uint8_t *dst = reinterpret_cast<uint8_t *> (buf->bits);
|
||||
|
||||
int32_t bpp = getNativeWindowBpp(buf);
|
||||
uint32_t dstStride, width;
|
||||
dstStride = buf->stride * bpp;
|
||||
width = buf->width * bpp;
|
||||
|
||||
// blank the screen
|
||||
for (auto height = 0; height < buf->height; ++height) {
|
||||
memset(dst, 0, width);
|
||||
dst += dstStride;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* UpdateFrameBuffer():
|
||||
* Internal function to perform bits copying onto current frame buffer
|
||||
* @param buf: already locked-down target NativeWindow to update,
|
||||
* @param frame: already decoded raw image to display.
|
||||
* - if nullptr, clear screen
|
||||
* - otherwise, pixel by pixel copy with letterbox enabled
|
||||
*/
|
||||
void ImageViewer::UpdateFrameBuffer(ANativeWindow_Buffer * _Nonnull buf,
|
||||
DecodeFrameDescriptor* _Nonnull frame) {
|
||||
|
||||
uint8_t *dst = reinterpret_cast<uint8_t *> (buf->bits);
|
||||
|
||||
int32_t bpp = getNativeWindowBpp(buf);
|
||||
uint32_t dstStride, width;
|
||||
dstStride = buf->stride * bpp;
|
||||
width = buf->width * bpp;
|
||||
|
||||
// perform pixel by pixel copying.
|
||||
uint8_t *src = frame->bits;
|
||||
int32_t start_x;
|
||||
int32_t start_y;
|
||||
if(frame->rotation == 0 || frame->rotation == 180) {
|
||||
start_x = (buf->width - frame->width) / 2;
|
||||
start_y = (buf->height - frame->height) / 2;
|
||||
} else if (frame->rotation == 90 || frame->rotation == 270){
|
||||
// need to rotate 90 or 270 degree
|
||||
start_x = (buf->width - frame->height) / 2;
|
||||
start_y = (buf->height - frame->width) / 2;
|
||||
} else {
|
||||
LOGE("Wrong frame->rotation(%d) to %s", frame->rotation, __FUNCTION__ );
|
||||
return;
|
||||
}
|
||||
assert(start_x >= 0 && start_y >= 0);
|
||||
dst += (start_y * dstStride) + (start_x * bpp);
|
||||
|
||||
// line by line copying for 0 degree rotation
|
||||
if(frame->rotation == 0) {
|
||||
for (auto height = 0; height < frame->height; ++height) {
|
||||
memcpy(dst, src, width);
|
||||
dst += dstStride, src += frame->stride;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(frame->rotation == 180) {
|
||||
src += frame->stride * (frame->height - 1);
|
||||
for (auto height = 0; height < frame->height; ++height) {
|
||||
memcpy(dst, src, width);
|
||||
dst += dstStride, src -= frame->stride;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// one pixel at a time, rotate on the fly
|
||||
if (frame->rotation == 90) {
|
||||
// one pixel at a time, rotate on the fly
|
||||
src += (frame->width - 1) * bpp;
|
||||
|
||||
for (auto height = 0; height < frame->width; height++) {
|
||||
uint8_t *srcLine = src;
|
||||
uint8_t *dstLine = dst;
|
||||
for (auto w = 0; w < frame->height; w++) {
|
||||
memcpy(dstLine, srcLine, bpp);
|
||||
|
||||
dstLine += bpp; // advance one pixel
|
||||
srcLine += frame->stride; // move down a line
|
||||
}
|
||||
dst += dstStride; // advance 1 line
|
||||
src -= bpp; // move 1 pixel backward for the next source column
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(frame->rotation == 270) {
|
||||
src += (frame->height - 1) * frame->stride;
|
||||
for (auto height = 0; height < frame->width; height++) {
|
||||
uint8_t *srcLine = src;
|
||||
uint8_t *dstLine = dst;
|
||||
for (auto w = 0; w < frame->height; w++) {
|
||||
memcpy(dstLine, srcLine, bpp);
|
||||
dstLine += bpp; // advance one pixel
|
||||
srcLine -= frame->stride; // move up a line
|
||||
}
|
||||
|
||||
dst += dstStride; // advance 1 line
|
||||
src += bpp; // move 1 pixel forward for the next source column
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
59
webp/image-decoder/src/main/cpp/image_viewer.h
Normal file
59
webp/image-decoder/src/main/cpp/image_viewer.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include "image_decoder.h"
|
||||
#include "display_timer.h"
|
||||
|
||||
/*
|
||||
* Main object handles Android window frame update, and use webp to decode
|
||||
* pictures
|
||||
*/
|
||||
class ImageViewer {
|
||||
public:
|
||||
explicit ImageViewer(android_app* _Nullable app);
|
||||
~ImageViewer();
|
||||
|
||||
// Functions to connect to native_activity
|
||||
struct android_app* _Nullable GetAndroidApp(void) const;
|
||||
void StartAnimation(bool start);
|
||||
bool IsAnimating(void) const;
|
||||
void TerminateDisplay(void);
|
||||
|
||||
// PrepareDrawing(): Initialize the Engine with current native window geometry
|
||||
// and blank current screen to avoid garbage displaying on device
|
||||
bool PrepareDrawing(void);
|
||||
|
||||
// Update the next image into display window when
|
||||
// - decoding is completed, and
|
||||
// - the current image has been displayed longer than the requested time
|
||||
bool UpdateDisplay(void);
|
||||
|
||||
private:
|
||||
void ClearFrameBuffer(ANativeWindow_Buffer* _Nonnull buf);
|
||||
void UpdateFrameBuffer(ANativeWindow_Buffer * _Nonnull buf,
|
||||
DecodeFrameDescriptor* _Nonnull frame);
|
||||
int32_t getNativeWindowBpp(ANativeWindow_Buffer* _Nonnull buf);
|
||||
|
||||
struct android_app* _Nullable androidApp;
|
||||
ImageDecoder* _Nullable imgDecoder;
|
||||
bool animatingInProgress;
|
||||
DisplayTimer displayTimer;
|
||||
};
|
||||
|
||||
BIN
webp/image-decoder/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
webp/image-decoder/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
webp/image-decoder/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
webp/image-decoder/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
webp/image-decoder/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
webp/image-decoder/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
webp/image-decoder/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
webp/image-decoder/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
4
webp/image-decoder/src/main/res/values/strings.xml
Normal file
4
webp/image-decoder/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NdkImageDecoder</string>
|
||||
</resources>
|
||||
@@ -1 +1 @@
|
||||
include ':view'
|
||||
include ':image-decoder'
|
||||
|
||||
Reference in New Issue
Block a user