RotationType getRotationTypeFromRawExifOrientation(JNIEnv* env, uint16_t exif_orientation) { switch (exif_orientation) { case 1: return RotationType::ROTATE_0; case 6: return RotationType::ROTATE_90; case 3: return RotationType::ROTATE_180; case 8: return RotationType::ROTATE_270; case 2: return RotationType::FLIP_HORIZONTAL; case 4: return RotationType::FLIP_VERTICAL; case 5: return RotationType::TRANSPOSE; case 7: return RotationType::TRANSVERSE; default: THROW_AND_RETURNVAL_IF( true, "wrong exif orientation", RotationType::ROTATE_0); } }
std::unique_ptr<DecodedImage> decodeWebpFromInputStream( JNIEnv* env, jobject is, PixelFormat pixel_format) { // get image into decoded heap auto encoded_image = readStreamFully(env, is); RETURNVAL_IF_EXCEPTION_PENDING({}); // extract metadata auto metadata = extractMetadata(env, encoded_image); RETURNVAL_IF_EXCEPTION_PENDING({}); // get pixels int image_width = 0; int image_height = 0; uint8_t* raw_pixels = nullptr; switch (pixel_format) { case PixelFormat::RGB: raw_pixels = WebPDecodeRGB( encoded_image.data(), encoded_image.size(), &image_width, &image_height); break; case PixelFormat::RGBA: raw_pixels = WebPDecodeRGBA( encoded_image.data(), encoded_image.size(), &image_width, &image_height); break; default: THROW_AND_RETURNVAL_IF(true, "unrecognized pixel format", {}); } auto pixels = pixels_t{raw_pixels, (void(*)(uint8_t*)) &free}; return std::unique_ptr<DecodedImage>{ new DecodedImage{ std::move(pixels), pixel_format, (unsigned int) image_width, (unsigned int) image_height, std::move(metadata)}}; }
RotationType getRotationTypeFromDegrees(JNIEnv* env, uint16_t degrees) { switch (degrees) { case 0: return RotationType::ROTATE_0; case 90: return RotationType::ROTATE_90; case 180: return RotationType::ROTATE_180; case 270: return RotationType::ROTATE_270; default: THROW_AND_RETURNVAL_IF( true, "wrong rotation angle", RotationType::ROTATE_0); } }
/** * Uses libwebp to extract xmp metadata. */ const std::vector<uint8_t> extractMetadata( JNIEnv* env, std::vector<uint8_t>& image_data) { // Create WebPDemux from provided data. // It is "index" of all chunks. It stores // list of pointers to particular chunks, but does // not copy memory from provided WebPData. WebPData webpdata = {image_data.data(), image_data.size()}; // Thsnks to using RAII we do not need to worry about // releasing WebPDemuxer structure auto demux = std::unique_ptr<WebPDemuxer, decltype(&WebPDemuxDelete)>{ WebPDemux(&webpdata), WebPDemuxDelete}; THROW_AND_RETURNVAL_IF( demux == nullptr, "Could not create WebPDemux from image. This webp might be malformed.", {}); // find xmp chunk WebPChunkIterator chunk_iterator; if (!WebPDemuxGetChunk(demux.get(), "XMP ", 1, &chunk_iterator)) { // we failed to find "XMP " chunk - don't worry, maybe it was not // there. Let the transcode proceed WebPDemuxReleaseChunkIterator(&chunk_iterator); return {}; } // we managed to find "XMP " chunk, let's return its size and pointer to it const unsigned int metadata_length = chunk_iterator.chunk.size; const uint8_t* metadata_ptr = chunk_iterator.chunk.bytes; WebPDemuxReleaseChunkIterator(&chunk_iterator); // If XMP chunk contains no data then return nullptr. if (metadata_length == 0) { return {}; } return {metadata_ptr, metadata_ptr + metadata_length}; }
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } // find java classes jclass runtimeException = env->FindClass("java/lang/RuntimeException"); if (runtimeException == nullptr) { LOGE("could not find RuntimeException class"); return -1; } jRuntimeException_class = reinterpret_cast<jclass>(env->NewGlobalRef(runtimeException)); jclass isClass = env->FindClass("java/io/InputStream"); THROW_AND_RETURNVAL_IF(isClass == nullptr, "could not find InputStream", -1); jclass osClass = env->FindClass("java/io/OutputStream"); THROW_AND_RETURNVAL_IF(osClass == nullptr, "could not find OutputStream", -1); // find java methods midInputStreamRead = env->GetMethodID(isClass, "read", "([B)I"); THROW_AND_RETURNVAL_IF( midInputStreamRead == nullptr, "failed to register InputStream.read", -1); midInputStreamSkip = env->GetMethodID(isClass, "skip", "(J)J"); THROW_AND_RETURNVAL_IF( midInputStreamSkip == nullptr, "failed to register InputStream.skip", -1); midOutputStreamWrite = env->GetMethodID(osClass, "write", "([B)V"); THROW_AND_RETURNVAL_IF( midOutputStreamWrite == nullptr, "failed to register OutputStream.write", -1); midOutputStreamWriteWithBounds = env->GetMethodID(osClass, "write", "([BII)V"); THROW_AND_RETURNVAL_IF( midOutputStreamWriteWithBounds == nullptr, "failed to register OutputStream.write", -1); // register native methods THROW_AND_RETURNVAL_IF( !registerJpegTranscoderMethods(env), "Could not register JpegTranscoder methods", -1); THROW_AND_RETURNVAL_IF( registerBitmapsMethods(env) == JNI_ERR, "Could not register Bitmaps methods", -1); THROW_AND_RETURNVAL_IF( registerNativeMemoryChunkMethods(env) == JNI_ERR, "Could not register NativeMemoryChunk methods", -1); THROW_AND_RETURNVAL_IF( registerBlurFilterMethods(env) == JNI_ERR, "Could not register NativeBlurFilter methods", -1); return JNI_VERSION_1_6; }
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } // find java classes jclass runtimeException = env->FindClass("java/lang/RuntimeException"); if (runtimeException == nullptr) { LOGE("could not find RuntimeException class"); return -1; } jRuntimeException_class = reinterpret_cast<jclass>(env->NewGlobalRef(runtimeException)); CREATE_AS_GLOBAL(runtimeExceptionClass, "java/lang/RuntimeException"); jclass isClass = env->FindClass("java/io/InputStream"); THROW_AND_RETURNVAL_IF(isClass == nullptr, "could not find InputStream", -1); jclass osClass = env->FindClass("java/io/OutputStream"); THROW_AND_RETURNVAL_IF(osClass == nullptr, "could not find OutputStream", -1); // find java methods midInputStreamRead = env->GetMethodID(isClass, "read", "([B)I"); THROW_AND_RETURNVAL_IF( midInputStreamRead == nullptr, "failed to register InputStream.read", -1); midInputStreamSkip = env->GetMethodID(isClass, "skip", "(J)J"); THROW_AND_RETURNVAL_IF( midInputStreamSkip == nullptr, "failed to register InputStream.skip", -1); midOutputStreamWrite = env->GetMethodID(osClass, "write", "([B)V"); THROW_AND_RETURNVAL_IF( midOutputStreamWrite == nullptr, "failed to register OutputStream.write", -1); midOutputStreamWriteWithBounds = env->GetMethodID(osClass, "write", "([BII)V"); THROW_AND_RETURNVAL_IF( midOutputStreamWriteWithBounds == nullptr, "failed to register OutputStream.write", -1); bitmapOptionsClass = (jclass)env->NewGlobalRef(env->FindClass("android/graphics/BitmapFactory$Options")); RETURN_IF_ERROR webpBitmapFactoryClass = (jclass)env->NewGlobalRef(env->FindClass("com/facebook/webpsupport/WebpBitmapFactoryImpl")); RETURN_IF_ERROR CREATE_AS_GLOBAL(bitmapClass, "android/graphics/Bitmap"); CREATE_AS_GLOBAL(fileDescriptorClass, "java/io/FileDescriptor"); createBitmapFunction = env->GetStaticMethodID(webpBitmapFactoryClass, "createBitmap", "(IILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;"); RETURN_IF_ERROR configName = env->NewStringUTF("ARGB_8888"); RETURN_IF_ERROR configName = (jstring)env->NewGlobalRef(configName); RETURN_IF_ERROR CREATE_AS_GLOBAL(bitmapConfigClass, "android/graphics/Bitmap$Config"); valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); RETURN_IF_ERROR UnionJNIEnvToVoid uenv; uenv.venv = NULL; if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) { return -1; } env = uenv.env; if (registerNatives(env) != JNI_TRUE) { return -1; } if (registerWebpTranscoderMethods(env) != JNI_TRUE) { return -1; } // We do this only if a class in animated-webp is present in the classpath jclass animatedWebpClass = env->FindClass("com/facebook/animated/webp/WebPImage"); if (animatedWebpClass) { if (initWebPImage(env) != JNI_OK) { return -1; } } return JNI_VERSION_1_6; }