mirror of
https://github.com/android/ndk-samples
synced 2025-11-06 23:55:35 +08:00
Compare commits
3 Commits
b21d220f67
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2eef15d3 | ||
|
|
44d725a358 | ||
|
|
9d05e29492 |
@@ -1,9 +1,10 @@
|
|||||||
WEBP Samples
|
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)
|
Sample in this directory is to demonstrate image decoding
|
||||||
view:
|
- view: demonstrate how to use webp in [Native Activity](http://developer.android.com/reference/android/app/NativeActivity.html)
|
||||||
- rotate decoding 3 webp images and load them into on-screen buffer. Decoding is in its own thread
|
- 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).
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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