jxl_decoder.cc (7091B)
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/wasm_demo/jxl_decoder.h" 7 8 #include <jxl/decode.h> 9 #include <jxl/decode_cxx.h> 10 #include <jxl/thread_parallel_runner_cxx.h> 11 12 #include <cstdio> 13 #include <cstring> 14 #include <memory> 15 #include <vector> 16 17 extern "C" { 18 19 namespace { 20 21 struct DecoderInstancePrivate { 22 // Due to "Standard Layout" rules it is guaranteed that address of the entity 23 // and its first non-static member are the same. 24 DecoderInstance info; 25 26 size_t pixels_size = 0; 27 bool want_sdr; 28 uint32_t display_nits; 29 JxlPixelFormat format; 30 JxlDecoderPtr decoder; 31 JxlThreadParallelRunnerPtr thread_pool; 32 33 std::vector<uint8_t> tail; 34 }; 35 36 } // namespace 37 38 DecoderInstance* jxlCreateInstance(bool want_sdr, uint32_t display_nits) { 39 DecoderInstancePrivate* self = new DecoderInstancePrivate(); 40 41 if (!self) { 42 return nullptr; 43 } 44 45 self->want_sdr = want_sdr; 46 self->display_nits = display_nits; 47 JxlDataType storageFormat = want_sdr ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16; 48 self->format = {4, storageFormat, JXL_NATIVE_ENDIAN, 0}; 49 self->decoder = JxlDecoderMake(nullptr); 50 51 JxlDecoder* dec = self->decoder.get(); 52 53 auto report_error = [&](uint32_t code, const char* text) { 54 fprintf(stderr, "%s\n", text); 55 delete self; 56 return reinterpret_cast<DecoderInstance*>(code); 57 }; 58 59 self->thread_pool = JxlThreadParallelRunnerMake(nullptr, 4); 60 void* runner = self->thread_pool.get(); 61 62 auto status = 63 JxlDecoderSetParallelRunner(dec, JxlThreadParallelRunner, runner); 64 65 if (status != JXL_DEC_SUCCESS) { 66 return report_error(1, "JxlDecoderSetParallelRunner failed"); 67 } 68 69 status = JxlDecoderSubscribeEvents( 70 dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE | 71 JXL_DEC_FRAME_PROGRESSION); 72 if (JXL_DEC_SUCCESS != status) { 73 return report_error(2, "JxlDecoderSubscribeEvents failed"); 74 } 75 76 status = JxlDecoderSetProgressiveDetail(dec, kPasses); 77 if (JXL_DEC_SUCCESS != status) { 78 return report_error(3, "JxlDecoderSetProgressiveDetail failed"); 79 } 80 return &self->info; 81 } 82 83 void jxlDestroyInstance(DecoderInstance* instance) { 84 if (instance == nullptr) return; 85 DecoderInstancePrivate* self = 86 reinterpret_cast<DecoderInstancePrivate*>(instance); 87 if (instance->pixels) { 88 free(instance->pixels); 89 } 90 delete self; 91 } 92 93 uint32_t jxlProcessInput(DecoderInstance* instance, const uint8_t* input, 94 size_t input_size) { 95 if (instance == nullptr) return static_cast<uint32_t>(-1); 96 DecoderInstancePrivate* self = 97 reinterpret_cast<DecoderInstancePrivate*>(instance); 98 JxlDecoder* dec = self->decoder.get(); 99 100 auto report_error = [&](int code, const char* text) { 101 fprintf(stderr, "%s\n", text); 102 return static_cast<uint32_t>(code); 103 }; 104 105 std::vector<uint8_t>& tail = self->tail; 106 if (!tail.empty()) { 107 tail.reserve(tail.size() + input_size); 108 tail.insert(tail.end(), input, input + input_size); 109 input = tail.data(); 110 input_size = tail.size(); 111 } 112 113 auto status = JxlDecoderSetInput(dec, input, input_size); 114 if (JXL_DEC_SUCCESS != status) { 115 return report_error(-2, "JxlDecoderSetInput failed"); 116 } 117 118 auto release_input = [&]() { 119 size_t unused_input = JxlDecoderReleaseInput(dec); 120 if (unused_input == 0) { 121 tail.clear(); 122 return; 123 } 124 if (tail.empty()) { 125 tail.insert(tail.end(), input + input_size - unused_input, 126 input + input_size); 127 } else { 128 memmove(tail.data(), tail.data() + tail.size() - unused_input, 129 unused_input); 130 tail.resize(unused_input); 131 } 132 }; 133 134 while (true) { 135 status = JxlDecoderProcessInput(dec); 136 if (JXL_DEC_SUCCESS == status) { 137 release_input(); 138 return 0; // ¯\_(ツ)_/¯ 139 } else if (JXL_DEC_FRAME_PROGRESSION == status) { 140 release_input(); 141 return 1; // ready to flush; client will decide whether it is necessary 142 } else if (JXL_DEC_NEED_MORE_INPUT == status) { 143 release_input(); 144 return 2; 145 } else if (JXL_DEC_FULL_IMAGE == status) { 146 release_input(); 147 return 0; // final image is ready 148 } else if (JXL_DEC_BASIC_INFO == status) { 149 JxlBasicInfo info; 150 status = JxlDecoderGetBasicInfo(dec, &info); 151 if (status != JXL_DEC_SUCCESS) { 152 release_input(); 153 return report_error(-4, "JxlDecoderGetBasicInfo failed"); 154 } 155 instance->width = info.xsize; 156 instance->height = info.ysize; 157 status = 158 JxlDecoderImageOutBufferSize(dec, &self->format, &self->pixels_size); 159 if (status != JXL_DEC_SUCCESS) { 160 release_input(); 161 return report_error(-6, "JxlDecoderImageOutBufferSize failed"); 162 } 163 if (instance->pixels) { 164 release_input(); 165 return report_error(-7, "Tried to realloc pixels"); 166 } 167 instance->pixels = reinterpret_cast<uint8_t*>(malloc(self->pixels_size)); 168 } else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == status) { 169 if (!self->info.pixels) { 170 release_input(); 171 return report_error(-8, "Out buffer not allocated"); 172 } 173 status = JxlDecoderSetImageOutBuffer(dec, &self->format, instance->pixels, 174 self->pixels_size); 175 if (status != JXL_DEC_SUCCESS) { 176 release_input(); 177 return report_error(-9, "JxlDecoderSetImageOutBuffer failed"); 178 } 179 } else if (JXL_DEC_COLOR_ENCODING == status) { 180 JxlColorEncoding color_encoding; 181 color_encoding.color_space = JXL_COLOR_SPACE_RGB; 182 color_encoding.white_point = JXL_WHITE_POINT_D65; 183 color_encoding.primaries = 184 self->want_sdr ? JXL_PRIMARIES_SRGB : JXL_PRIMARIES_2100; 185 color_encoding.transfer_function = self->want_sdr 186 ? JXL_TRANSFER_FUNCTION_SRGB 187 : JXL_TRANSFER_FUNCTION_PQ; 188 color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; 189 status = JxlDecoderSetPreferredColorProfile(dec, &color_encoding); 190 if (status != JXL_DEC_SUCCESS) { 191 release_input(); 192 return report_error(-5, "JxlDecoderSetPreferredColorProfile failed"); 193 } 194 } else { 195 release_input(); 196 return report_error(-3, "Unexpected decoder status"); 197 } 198 } 199 200 release_input(); 201 return 0; 202 } 203 204 uint32_t jxlFlush(DecoderInstance* instance) { 205 if (instance == nullptr) return static_cast<uint32_t>(-1); 206 DecoderInstancePrivate* self = 207 reinterpret_cast<DecoderInstancePrivate*>(instance); 208 JxlDecoder* dec = self->decoder.get(); 209 210 auto report_error = [&](int code, const char* text) { 211 fprintf(stderr, "%s\n", text); 212 // self->result = code; 213 return static_cast<uint32_t>(code); 214 }; 215 216 if (!instance->pixels) { 217 return report_error(-2, "Not ready to flush"); 218 } 219 220 auto status = JxlDecoderFlushImage(dec); 221 if (status != JXL_DEC_SUCCESS) { 222 return report_error(-3, "Failed to flush"); 223 } 224 225 return 0; 226 } 227 228 } // extern "C"