mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
Enable WebP decoding in Signal using libwebp v1.3.2
Co-authored-by: Greyson Parrelli <greyson@signal.org> Co-authored-by: Greyson Parrelli <greyson@pop-os.localdomain>
This commit is contained in:
committed by
Alex Hart
parent
091f7c49ab
commit
a7d9fd19d9
24
glide-webp/lib/src/main/cpp/CMakeLists.txt
Executable file
24
glide-webp/lib/src/main/cpp/CMakeLists.txt
Executable file
@@ -0,0 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
project(signal-webp)
|
||||
|
||||
add_compile_options(-Oz)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
set(WEBP_BUILD_ANIM_UTILS OFF)
|
||||
set(WEBP_BUILD_CWEBP OFF)
|
||||
set(WEBP_BUILD_DWEBP OFF)
|
||||
set(WEBP_BUILD_VWEBP OFF)
|
||||
set(WEBP_BUILD_WEBPINFO OFF)
|
||||
set(WEBP_BUILD_LIBWEBPMUX OFF)
|
||||
set(WEBP_BUILD_EXTRAS OFF)
|
||||
set(WEBP_NEAR_LOSSLESS, OFF)
|
||||
|
||||
file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/*.cpp)
|
||||
|
||||
add_subdirectory(${LIBWEBP_PATH} ${CMAKE_CURRENT_BINARY_DIR}/libwebp)
|
||||
|
||||
add_library(signalwebp SHARED ${SOURCES})
|
||||
target_include_directories(signalwebp PRIVATE ${LIBWEBP_PATH}/src)
|
||||
target_link_libraries(signalwebp webpdemux)
|
||||
62
glide-webp/lib/src/main/cpp/signalwebp.cpp
Normal file
62
glide-webp/lib/src/main/cpp/signalwebp.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <jni.h>
|
||||
#include <webp/demux.h>
|
||||
|
||||
jobject createBitmap(JNIEnv *env, int width, int height, const uint8_t *pixels) {
|
||||
static auto jbitmapConfigClass = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap$Config")));
|
||||
static jfieldID jbitmapConfigARGB8888Field = env->GetStaticFieldID(jbitmapConfigClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
static auto jbitmapClass = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("android/graphics/Bitmap")));
|
||||
static jmethodID jbitmapCreateBitmapMethod = env->GetStaticMethodID(jbitmapClass, "createBitmap", "([IIIIILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
|
||||
jintArray intArray = env->NewIntArray(width * height);
|
||||
env->SetIntArrayRegion(intArray, 0, width * height, reinterpret_cast<const jint *>(pixels));
|
||||
|
||||
jobject argb8888Config = env->GetStaticObjectField(jbitmapConfigClass, jbitmapConfigARGB8888Field);
|
||||
jobject jbitmap = env->CallStaticObjectMethod(jbitmapClass, jbitmapCreateBitmapMethod, intArray, 0, width, width, height, argb8888Config);
|
||||
env->DeleteLocalRef(argb8888Config);
|
||||
|
||||
return jbitmap;
|
||||
}
|
||||
|
||||
jobject nativeDecodeBitmap(JNIEnv *env, jobject, jbyteArray data) {
|
||||
jbyte *javaBytes = env->GetByteArrayElements(data, nullptr);
|
||||
auto *buffer = reinterpret_cast<uint8_t *>(javaBytes);
|
||||
jsize bufferLength = env->GetArrayLength(data);
|
||||
|
||||
WebPBitstreamFeatures features;
|
||||
WebPGetFeatures(buffer, bufferLength, &features);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
uint8_t *pixels = WebPDecodeBGRA(buffer, bufferLength, &width, &height);
|
||||
jobject jbitmap = createBitmap(env, width, height, pixels);
|
||||
|
||||
WebPFree(pixels);
|
||||
env->ReleaseByteArrayElements(data, javaBytes, 0);
|
||||
|
||||
return jbitmap;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
jclass c = env->FindClass("org/signal/glide/webp/WebpDecoder");
|
||||
if (c == nullptr) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
{"nativeDecodeBitmap", "([B)Landroid/graphics/Bitmap;", reinterpret_cast<void *>(nativeDecodeBitmap)}
|
||||
};
|
||||
|
||||
int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
|
||||
|
||||
if (rc != JNI_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
class WebpDecoder {
|
||||
|
||||
init {
|
||||
System.loadLibrary("signalwebp")
|
||||
}
|
||||
|
||||
external fun nativeDecodeBitmap(data: ByteArray): Bitmap?
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapResource
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class WebpInputStreamResourceDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder<InputStream, Bitmap> {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WebpResourceDecoder" // Name > 23 characters
|
||||
|
||||
private val MAGIC_NUMBER_P1 = byteArrayOf(0x52, 0x49, 0x46, 0x46) // "RIFF"
|
||||
private val MAGIC_NUMBER_P2 = byteArrayOf(0x57, 0x45, 0x42, 0x50) // "WEBP"
|
||||
|
||||
private const val MAX_WEBP_COMPRESSED_SIZE = 10 * 1024 * 1024; // 10mb
|
||||
}
|
||||
|
||||
/**
|
||||
* The "magic number" for a WEBP file is in the first 12 bytes. The layout is:
|
||||
*
|
||||
* [0-3]: "RIFF"
|
||||
* [4-7]: File length
|
||||
* [8-11]: "WEBP"
|
||||
*
|
||||
* We're not verifying the file length here, so we just need to check the first and last
|
||||
*/
|
||||
override fun handles(source: InputStream, options: Options): Boolean {
|
||||
return try {
|
||||
val magicNumberP1 = ByteArray(4)
|
||||
StreamUtil.readFully(source, magicNumberP1)
|
||||
|
||||
val fileLength = ByteArray(4)
|
||||
StreamUtil.readFully(source, fileLength)
|
||||
|
||||
val magicNumberP2 = ByteArray(4)
|
||||
StreamUtil.readFully(source, magicNumberP2)
|
||||
|
||||
magicNumberP1.contentEquals(MAGIC_NUMBER_P1) && magicNumberP2.contentEquals(MAGIC_NUMBER_P2)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to read magic number from stream!", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<Bitmap>? {
|
||||
Log.d(TAG, "decode()")
|
||||
|
||||
val webp: ByteArray = try {
|
||||
StreamUtil.readFully(source, MAX_WEBP_COMPRESSED_SIZE)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Unexpected IOException hit while reading image data", e)
|
||||
throw e
|
||||
}
|
||||
|
||||
|
||||
val bitmap: Bitmap? = WebpDecoder().nativeDecodeBitmap(webp)
|
||||
return BitmapResource.obtain(bitmap, bitmapPool)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.glide.webp
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.module.LibraryGlideModule
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Registers a custom handler for webp images.
|
||||
*/
|
||||
@GlideModule
|
||||
class WebpLibraryGlideModule : LibraryGlideModule() {
|
||||
companion object {
|
||||
private val TAG = Log.tag(WebpLibraryGlideModule::class.java)
|
||||
}
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
Log.d(TAG, "registerComponents()")
|
||||
registry.prepend(InputStream::class.java, Bitmap::class.java, WebpInputStreamResourceDecoder(glide.bitmapPool))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user