decoder_jni.cc (8582B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "tools/jni/org/jpeg/jpegxl/wrapper/decoder_jni.h" 7 8 #include <jni.h> 9 #include <jxl/codestream_header.h> 10 #include <jxl/decode.h> 11 #include <jxl/thread_parallel_runner.h> 12 #include <jxl/types.h> 13 14 #include <cstdint> 15 #include <cstdlib> 16 17 namespace { 18 19 template <typename From, typename To> 20 bool StaticCast(const From& from, To* to) { 21 To tmp = static_cast<To>(from); 22 // Check sign is preserved. 23 if ((from < 0 && tmp > 0) || (from > 0 && tmp < 0)) return false; 24 // Check value is preserved. 25 if (from != static_cast<From>(tmp)) return false; 26 *to = tmp; 27 return true; 28 } 29 30 bool BufferToSpan(JNIEnv* env, jobject buffer, uint8_t** data, size_t* size) { 31 if (buffer == nullptr) return true; 32 33 *data = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer)); 34 if (*data == nullptr) return false; 35 return StaticCast(env->GetDirectBufferCapacity(buffer), size); 36 } 37 38 enum class Status { OK = 0, FATAL_ERROR = -1, NOT_ENOUGH_INPUT = 1 }; 39 40 bool IsOk(Status status) { return status == Status::OK; } 41 42 #define FAILURE(M) Status::FATAL_ERROR 43 44 constexpr const size_t kLastPixelFormat = 3; 45 constexpr const size_t kNoPixelFormat = static_cast<size_t>(-1); 46 47 JxlPixelFormat ToPixelFormat(size_t pixel_format) { 48 if (pixel_format == 0) { 49 // RGBA, 4 x byte per pixel, no scanline padding. 50 return {/*num_channels=*/4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, /*align=*/0}; 51 } else if (pixel_format == 1) { 52 // RGBA, 4 x float16 per pixel, no scanline padding. 53 return {/*num_channels=*/4, JXL_TYPE_FLOAT16, JXL_LITTLE_ENDIAN, 54 /*align=*/0}; 55 } else if (pixel_format == 2) { 56 // RGB, 4 x byte per pixel, no scanline padding. 57 return {/*num_channels=*/3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, /*align=*/0}; 58 } else if (pixel_format == 3) { 59 // RGB, 4 x float16 per pixel, no scanline padding. 60 return {/*num_channels=*/3, JXL_TYPE_FLOAT16, JXL_LITTLE_ENDIAN, 61 /*align=*/0}; 62 } else { 63 abort(); 64 return {0, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; 65 } 66 } 67 68 Status DoDecode(JNIEnv* env, jobject data_buffer, size_t* info_pixels_size, 69 size_t* info_icc_size, JxlBasicInfo* info, size_t pixel_format, 70 jobject pixels_buffer, jobject icc_buffer) { 71 if (data_buffer == nullptr) return FAILURE("No data buffer"); 72 73 uint8_t* data = nullptr; 74 size_t data_size = 0; 75 if (!BufferToSpan(env, data_buffer, &data, &data_size)) { 76 return FAILURE("Failed to access data buffer"); 77 } 78 79 uint8_t* pixels = nullptr; 80 size_t pixels_size = 0; 81 if (!BufferToSpan(env, pixels_buffer, &pixels, &pixels_size)) { 82 return FAILURE("Failed to access pixels buffer"); 83 } 84 85 uint8_t* icc = nullptr; 86 size_t icc_size = 0; 87 if (!BufferToSpan(env, icc_buffer, &icc, &icc_size)) { 88 return FAILURE("Failed to access ICC buffer"); 89 } 90 91 JxlDecoder* dec = JxlDecoderCreate(nullptr); 92 93 constexpr size_t kNumThreads = 0; // Do everything in this thread. 94 void* runner = JxlThreadParallelRunnerCreate(nullptr, kNumThreads); 95 96 struct Defer { 97 JxlDecoder* dec; 98 void* runner; 99 ~Defer() { 100 JxlThreadParallelRunnerDestroy(runner); 101 JxlDecoderDestroy(dec); 102 } 103 } defer{dec, runner}; 104 105 auto status = 106 JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner); 107 if (status != JXL_DEC_SUCCESS) { 108 return FAILURE("Failed to set parallel runner"); 109 } 110 status = JxlDecoderSubscribeEvents( 111 dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_COLOR_ENCODING); 112 if (status != JXL_DEC_SUCCESS) { 113 return FAILURE("Failed to subscribe for events"); 114 } 115 status = JxlDecoderSetInput(dec, data, data_size); 116 if (status != JXL_DEC_SUCCESS) { 117 return FAILURE("Failed to set input"); 118 } 119 status = JxlDecoderProcessInput(dec); 120 if (status == JXL_DEC_NEED_MORE_INPUT) { 121 return Status::NOT_ENOUGH_INPUT; 122 } 123 if (status != JXL_DEC_BASIC_INFO) { 124 return FAILURE("Unexpected notification (want: basic info)"); 125 } 126 if (info_pixels_size) { 127 JxlPixelFormat format = ToPixelFormat(pixel_format); 128 status = JxlDecoderImageOutBufferSize(dec, &format, info_pixels_size); 129 if (status != JXL_DEC_SUCCESS) { 130 return FAILURE("Failed to get pixels size"); 131 } 132 } 133 if (info) { 134 status = JxlDecoderGetBasicInfo(dec, info); 135 if (status != JXL_DEC_SUCCESS) { 136 return FAILURE("Failed to get basic info"); 137 } 138 } 139 status = JxlDecoderProcessInput(dec); 140 if (status != JXL_DEC_COLOR_ENCODING) { 141 return FAILURE("Unexpected notification (want: color encoding)"); 142 } 143 if (info_icc_size) { 144 status = JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, 145 info_icc_size); 146 if (status != JXL_DEC_SUCCESS) *info_icc_size = 0; 147 } 148 if (icc && icc_size > 0) { 149 status = JxlDecoderGetColorAsICCProfile(dec, JXL_COLOR_PROFILE_TARGET_DATA, 150 icc, icc_size); 151 if (status != JXL_DEC_SUCCESS) { 152 return FAILURE("Failed to get ICC"); 153 } 154 } 155 if (pixels) { 156 JxlPixelFormat format = ToPixelFormat(pixel_format); 157 status = JxlDecoderProcessInput(dec); 158 if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 159 return FAILURE("Unexpected notification (want: need out buffer)"); 160 } 161 status = JxlDecoderSetImageOutBuffer(dec, &format, pixels, pixels_size); 162 if (status != JXL_DEC_SUCCESS) { 163 return FAILURE("Failed to set out buffer"); 164 } 165 status = JxlDecoderProcessInput(dec); 166 if (status != JXL_DEC_FULL_IMAGE) { 167 return FAILURE("Unexpected notification (want: full image)"); 168 } 169 status = JxlDecoderProcessInput(dec); 170 if (status != JXL_DEC_SUCCESS) { 171 return FAILURE("Unexpected notification (want: success)"); 172 } 173 } 174 175 return Status::OK; 176 } 177 178 } // namespace 179 180 #ifdef __cplusplus 181 extern "C" { 182 #endif 183 184 JNIEXPORT void JNICALL 185 Java_org_jpeg_jpegxl_wrapper_DecoderJni_nativeGetBasicInfo( 186 JNIEnv* env, jobject /*jobj*/, jintArray ctx, jobject data_buffer) { 187 jint context[6] = {0}; 188 env->GetIntArrayRegion(ctx, 0, 1, context); 189 190 JxlBasicInfo info = {}; 191 size_t pixels_size = 0; 192 size_t icc_size = 0; 193 size_t pixel_format = 0; 194 195 Status status = Status::OK; 196 197 if (IsOk(status)) { 198 pixel_format = context[0]; 199 if (pixel_format == kNoPixelFormat) { 200 // OK 201 } else if (pixel_format > kLastPixelFormat) { 202 status = FAILURE("Unrecognized pixel format"); 203 } 204 } 205 206 if (IsOk(status)) { 207 bool want_output_size = (pixel_format != kNoPixelFormat); 208 if (want_output_size) { 209 status = DoDecode( 210 env, data_buffer, &pixels_size, &icc_size, &info, pixel_format, 211 /* pixels_buffer= */ nullptr, /* icc_buffer= */ nullptr); 212 } else { 213 status = 214 DoDecode(env, data_buffer, /* info_pixels_size= */ nullptr, 215 /* info_icc_size= */ nullptr, &info, pixel_format, 216 /* pixels_buffer= */ nullptr, /* icc_buffer= */ nullptr); 217 } 218 } 219 220 if (IsOk(status)) { 221 bool ok = true; 222 ok &= StaticCast(info.xsize, context + 1); 223 ok &= StaticCast(info.ysize, context + 2); 224 ok &= StaticCast(pixels_size, context + 3); 225 ok &= StaticCast(icc_size, context + 4); 226 ok &= StaticCast(info.alpha_bits, context + 5); 227 if (!ok) status = FAILURE("Invalid value"); 228 } 229 230 context[0] = static_cast<int>(status); 231 232 env->SetIntArrayRegion(ctx, 0, 6, context); 233 } 234 235 /** 236 * Get image pixel data. 237 * 238 * @param ctx {out_status} tuple 239 * @param data [in] Buffer with encoded JXL stream 240 * @param pixels [out] Buffer to place pixels to 241 */ 242 JNIEXPORT void JNICALL Java_org_jpeg_jpegxl_wrapper_DecoderJni_nativeGetPixels( 243 JNIEnv* env, jobject /* jobj */, jintArray ctx, jobject data_buffer, 244 jobject pixels_buffer, jobject icc_buffer) { 245 jint context[1] = {0}; 246 env->GetIntArrayRegion(ctx, 0, 1, context); 247 248 size_t pixel_format = 0; 249 250 Status status = Status::OK; 251 252 if (IsOk(status)) { 253 // Unlike getBasicInfo, "no-pixel-format" is not supported. 254 pixel_format = context[0]; 255 if (pixel_format > kLastPixelFormat) { 256 status = FAILURE("Unrecognized pixel format"); 257 } 258 } 259 260 if (IsOk(status)) { 261 status = DoDecode(env, data_buffer, /* info_pixels_size= */ nullptr, 262 /* info_icc_size= */ nullptr, /* info= */ nullptr, 263 pixel_format, pixels_buffer, icc_buffer); 264 } 265 266 context[0] = static_cast<int>(status); 267 env->SetIntArrayRegion(ctx, 0, 1, context); 268 } 269 270 #undef FAILURE 271 272 #ifdef __cplusplus 273 } 274 #endif