// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "tools/jni/org/jpeg/jpegxl/wrapper/decoder_jni.h" #include #include #include "jxl/decode.h" #include "jxl/thread_parallel_runner.h" #include "lib/jxl/base/status.h" namespace { template bool StaticCast(const From& from, To* to) { To tmp = static_cast(from); // Check sign is preserved. if ((from < 0 && tmp > 0) || (from > 0 && tmp < 0)) return false; // Check value is preserved. if (from != static_cast(tmp)) return false; *to = tmp; return true; } bool BufferToSpan(JNIEnv* env, jobject buffer, uint8_t** data, size_t* size) { if (buffer == nullptr) return true; *data = reinterpret_cast(env->GetDirectBufferAddress(buffer)); if (*data == nullptr) return false; return StaticCast(env->GetDirectBufferCapacity(buffer), size); } int ToStatusCode(const jxl::Status& status) { if (status) return 0; if (status.IsFatalError()) return -1; return 1; // Non-fatal -> not enough input. } constexpr const size_t kLastPixelFormat = 3; constexpr const size_t kNoPixelFormat = static_cast(-1); JxlPixelFormat ToPixelFormat(size_t pixel_format) { if (pixel_format == 0) { // RGBA, 4 x byte per pixel, no scanline padding. return {/*num_channels=*/4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, /*align=*/0}; } else if (pixel_format == 1) { // RGBA, 4 x float16 per pixel, no scanline padding. return {/*num_channels=*/4, JXL_TYPE_FLOAT16, JXL_LITTLE_ENDIAN, /*align=*/0}; } else if (pixel_format == 2) { // RGB, 4 x byte per pixel, no scanline padding. return {/*num_channels=*/3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, /*align=*/0}; } else if (pixel_format == 3) { // RGB, 4 x float16 per pixel, no scanline padding. return {/*num_channels=*/3, JXL_TYPE_FLOAT16, JXL_LITTLE_ENDIAN, /*align=*/0}; } else { abort(); return {0, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; } } jxl::Status DoDecode(JNIEnv* env, jobject data_buffer, size_t* info_pixels_size, size_t* info_icc_size, JxlBasicInfo* info, size_t pixel_format, jobject pixels_buffer, jobject icc_buffer) { if (data_buffer == nullptr) return JXL_FAILURE("No data buffer"); uint8_t* data = nullptr; size_t data_size = 0; if (!BufferToSpan(env, data_buffer, &data, &data_size)) { return JXL_FAILURE("Failed to access data buffer"); } uint8_t* pixels = nullptr; size_t pixels_size = 0; if (!BufferToSpan(env, pixels_buffer, &pixels, &pixels_size)) { return JXL_FAILURE("Failed to access pixels buffer"); } uint8_t* icc = nullptr; size_t icc_size = 0; if (!BufferToSpan(env, icc_buffer, &icc, &icc_size)) { return JXL_FAILURE("Failed to access ICC buffer"); } JxlDecoder* dec = JxlDecoderCreate(NULL); constexpr size_t kNumThreads = 0; // Do everything in this thread. void* runner = JxlThreadParallelRunnerCreate(NULL, kNumThreads); struct Defer { JxlDecoder* dec; void* runner; ~Defer() { JxlThreadParallelRunnerDestroy(runner); JxlDecoderDestroy(dec); } } defer{dec, runner}; auto status = JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to set parallel runner"); } status = JxlDecoderSubscribeEvents( dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to subscribe for events"); } status = JxlDecoderSetInput(dec, data, data_size); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to set input"); } status = JxlDecoderProcessInput(dec); if (status == JXL_DEC_NEED_MORE_INPUT) { return JXL_STATUS(jxl::StatusCode::kNotEnoughBytes, "Not enough input"); } if (status != JXL_DEC_BASIC_INFO) { return JXL_FAILURE("Unexpected notification (want: basic info)"); } if (info_pixels_size) { JxlPixelFormat format = ToPixelFormat(pixel_format); status = JxlDecoderImageOutBufferSize(dec, &format, info_pixels_size); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to get pixels size"); } } if (info) { status = JxlDecoderGetBasicInfo(dec, info); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to get basic info"); } } status = JxlDecoderProcessInput(dec); if (status != JXL_DEC_COLOR_ENCODING) { return JXL_FAILURE("Unexpected notification (want: color encoding)"); } if (info_icc_size) { JxlPixelFormat format = ToPixelFormat(pixel_format); status = JxlDecoderGetICCProfileSize( dec, &format, JXL_COLOR_PROFILE_TARGET_DATA, info_icc_size); if (status != JXL_DEC_SUCCESS) *info_icc_size = 0; } if (icc && icc_size > 0) { JxlPixelFormat format = ToPixelFormat(pixel_format); status = JxlDecoderGetColorAsICCProfile( dec, &format, JXL_COLOR_PROFILE_TARGET_DATA, icc, icc_size); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to get ICC"); } } if (pixels) { JxlPixelFormat format = ToPixelFormat(pixel_format); status = JxlDecoderProcessInput(dec); if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) { return JXL_FAILURE("Unexpected notification (want: need out buffer)"); } status = JxlDecoderSetImageOutBuffer(dec, &format, pixels, pixels_size); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Failed to set out buffer"); } status = JxlDecoderProcessInput(dec); if (status != JXL_DEC_FULL_IMAGE) { return JXL_FAILURE("Unexpected notification (want: full image)"); } status = JxlDecoderProcessInput(dec); if (status != JXL_DEC_SUCCESS) { return JXL_FAILURE("Unexpected notification (want: success)"); } } return true; } #undef FAILURE } // namespace #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_org_jpeg_jpegxl_wrapper_DecoderJni_nativeGetBasicInfo( JNIEnv* env, jobject /*jobj*/, jintArray ctx, jobject data_buffer) { jint context[6] = {0}; env->GetIntArrayRegion(ctx, 0, 1, context); JxlBasicInfo info = {}; size_t pixels_size = 0; size_t icc_size = 0; size_t pixel_format = 0; jxl::Status status = true; if (status) { pixel_format = context[0]; if (pixel_format == kNoPixelFormat) { // OK } else if (pixel_format > kLastPixelFormat) { status = JXL_FAILURE("Unrecognized pixel format"); } } if (status) { bool want_output_size = (pixel_format != kNoPixelFormat); if (want_output_size) { status = DoDecode( env, data_buffer, &pixels_size, &icc_size, &info, pixel_format, /* pixels_buffer= */ nullptr, /* icc_buffer= */ nullptr); } else { status = DoDecode(env, data_buffer, /* info_pixels_size= */ nullptr, /* info_icc_size= */ nullptr, &info, pixel_format, /* pixels_buffer= */ nullptr, /* icc_buffer= */ nullptr); } } if (status) { bool ok = true; ok &= StaticCast(info.xsize, context + 1); ok &= StaticCast(info.ysize, context + 2); ok &= StaticCast(pixels_size, context + 3); ok &= StaticCast(icc_size, context + 4); ok &= StaticCast(info.alpha_bits, context + 5); if (!ok) status = JXL_FAILURE("Invalid value"); } context[0] = ToStatusCode(status); env->SetIntArrayRegion(ctx, 0, 6, context); } /** * Get image pixel data. * * @param ctx {out_status} tuple * @param data [in] Buffer with encoded JXL stream * @param pixels [out] Buffer to place pixels to */ JNIEXPORT void JNICALL Java_org_jpeg_jpegxl_wrapper_DecoderJni_nativeGetPixels( JNIEnv* env, jobject /* jobj */, jintArray ctx, jobject data_buffer, jobject pixels_buffer, jobject icc_buffer) { jint context[1] = {0}; env->GetIntArrayRegion(ctx, 0, 1, context); size_t pixel_format = 0; jxl::Status status = true; if (status) { // Unlike getBasicInfo, "no-pixel-format" is not supported. pixel_format = context[0]; if (pixel_format > kLastPixelFormat) { status = JXL_FAILURE("Unrecognized pixel format"); } } if (status) { status = DoDecode(env, data_buffer, /* info_pixels_size= */ nullptr, /* info_icc_size= */ nullptr, /* info= */ nullptr, pixel_format, pixels_buffer, icc_buffer); } context[0] = ToStatusCode(status); env->SetIntArrayRegion(ctx, 0, 1, context); } #ifdef __cplusplus } #endif