decode_progressive.cc (7833B)
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 // This C++ example decodes a JPEG XL image progressively (input bytes are 7 // passed in chunks). The example outputs the intermediate steps to PAM files. 8 9 #ifndef __STDC_FORMAT_MACROS 10 #define __STDC_FORMAT_MACROS 11 #endif 12 13 #include <jxl/decode.h> 14 #include <jxl/decode_cxx.h> 15 #include <jxl/resizable_parallel_runner.h> 16 #include <jxl/resizable_parallel_runner_cxx.h> 17 #include <limits.h> 18 19 #include <cinttypes> // PRIu64 20 #include <cstdint> 21 #include <cstdio> 22 #include <cstring> 23 #include <vector> 24 25 bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) { 26 FILE* fp = fopen(filename, "wb"); 27 if (!fp) { 28 fprintf(stderr, "Could not open %s for writing", filename); 29 return false; 30 } 31 fprintf(fp, 32 "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE " 33 "RGB_ALPHA\nENDHDR\n", 34 static_cast<int>(w), static_cast<int>(h)); 35 size_t num_bytes = w * h * 4; 36 if (fwrite(buffer, 1, num_bytes, fp) != num_bytes) { 37 fclose(fp); 38 return false; 39 }; 40 if (fclose(fp) != 0) { 41 return false; 42 } 43 return true; 44 } 45 46 /** Decodes JPEG XL image to 8-bit integer RGBA pixels and an ICC Profile, in a 47 * progressive way, saving the intermediate steps. 48 */ 49 bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size, 50 const char* filename, size_t chunksize) { 51 std::vector<uint8_t> pixels; 52 std::vector<uint8_t> icc_profile; 53 size_t xsize = 0; 54 size_t ysize = 0; 55 56 // Multi-threaded parallel runner. 57 auto runner = JxlResizableParallelRunnerMake(nullptr); 58 59 auto dec = JxlDecoderMake(nullptr); 60 if (JXL_DEC_SUCCESS != 61 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | 62 JXL_DEC_COLOR_ENCODING | 63 JXL_DEC_FULL_IMAGE)) { 64 fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); 65 return false; 66 } 67 68 if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), 69 JxlResizableParallelRunner, 70 runner.get())) { 71 fprintf(stderr, "JxlDecoderSetParallelRunner failed\n"); 72 return false; 73 } 74 75 JxlBasicInfo info; 76 JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 77 78 size_t seen = 0; 79 JxlDecoderSetInput(dec.get(), jxl, chunksize); 80 size_t remaining = chunksize; 81 82 for (;;) { 83 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 84 85 if (status == JXL_DEC_ERROR) { 86 fprintf(stderr, "Decoder error\n"); 87 return false; 88 } else if (status == JXL_DEC_NEED_MORE_INPUT || status == JXL_DEC_SUCCESS || 89 status == JXL_DEC_FULL_IMAGE) { 90 seen += remaining - JxlDecoderReleaseInput(dec.get()); 91 printf("Flushing after %" PRIu64 " bytes\n", static_cast<uint64_t>(seen)); 92 if (status == JXL_DEC_NEED_MORE_INPUT && 93 JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec.get())) { 94 printf("flush error (no preview yet)\n"); 95 } else { 96 char fname[1024]; 97 if (snprintf(fname, 1024, "%s-%" PRIu64 ".pam", filename, 98 static_cast<uint64_t>(seen)) >= 1024) { 99 fprintf(stderr, "Filename too long\n"); 100 return false; 101 }; 102 if (!WritePAM(fname, pixels.data(), xsize, ysize)) { 103 fprintf(stderr, "Error writing progressive output\n"); 104 } 105 } 106 remaining = size - seen; 107 if (remaining > chunksize) remaining = chunksize; 108 if (remaining == 0) { 109 if (status == JXL_DEC_NEED_MORE_INPUT) { 110 fprintf(stderr, "Error, already provided all input\n"); 111 return false; 112 } else { 113 return true; 114 } 115 } 116 JxlDecoderSetInput(dec.get(), jxl + seen, remaining); 117 } else if (status == JXL_DEC_BASIC_INFO) { 118 if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { 119 fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); 120 return false; 121 } 122 xsize = info.xsize; 123 ysize = info.ysize; 124 JxlResizableParallelRunnerSetThreads( 125 runner.get(), 126 JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize)); 127 } else if (status == JXL_DEC_COLOR_ENCODING) { 128 // Get the ICC color profile of the pixel data 129 size_t icc_size; 130 if (JXL_DEC_SUCCESS != 131 JxlDecoderGetICCProfileSize( 132 dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, &icc_size)) { 133 fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); 134 return false; 135 } 136 icc_profile.resize(icc_size); 137 if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( 138 dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, 139 icc_profile.data(), icc_profile.size())) { 140 fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); 141 return false; 142 } 143 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 144 size_t buffer_size; 145 if (JXL_DEC_SUCCESS != 146 JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) { 147 fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n"); 148 return false; 149 } 150 if (buffer_size != xsize * ysize * 4) { 151 fprintf(stderr, "Invalid out buffer size %" PRIu64 " != %" PRIu64 "\n", 152 static_cast<uint64_t>(buffer_size), 153 static_cast<uint64_t>(xsize * ysize * 4)); 154 return false; 155 } 156 pixels.resize(xsize * ysize * 4); 157 if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, 158 pixels.data(), 159 pixels.size())) { 160 fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); 161 return false; 162 } 163 } else { 164 fprintf(stderr, "Unknown decoder status\n"); 165 return false; 166 } 167 } 168 } 169 170 bool LoadFile(const char* filename, std::vector<uint8_t>* out) { 171 FILE* file = fopen(filename, "rb"); 172 if (!file) { 173 return false; 174 } 175 176 if (fseek(file, 0, SEEK_END) != 0) { 177 fclose(file); 178 return false; 179 } 180 181 long size = ftell(file); // NOLINT 182 // Avoid invalid file or directory. 183 if (size >= LONG_MAX || size < 0) { 184 fclose(file); 185 return false; 186 } 187 188 if (fseek(file, 0, SEEK_SET) != 0) { 189 fclose(file); 190 return false; 191 } 192 193 out->resize(size); 194 size_t readsize = fread(out->data(), 1, size, file); 195 if (fclose(file) != 0) { 196 return false; 197 } 198 199 return readsize == static_cast<size_t>(size); 200 } 201 202 int main(int argc, char* argv[]) { 203 if (argc < 3) { 204 fprintf( 205 stderr, 206 "Usage: %s <jxl> <basename> [chunksize]\n" 207 "Where:\n" 208 " jxl = input JPEG XL image filename\n" 209 " basename = prefix of output filenames\n" 210 " chunksize = loads chunksize bytes at a time and writes\n" 211 " intermediate results to basename-[bytes loaded].pam\n" 212 "Output files will be overwritten.\n", 213 argv[0]); 214 return 1; 215 } 216 217 const char* jxl_filename = argv[1]; 218 const char* png_filename = argv[2]; 219 220 std::vector<uint8_t> jxl; 221 if (!LoadFile(jxl_filename, &jxl)) { 222 fprintf(stderr, "couldn't load %s\n", jxl_filename); 223 return 1; 224 } 225 size_t chunksize = jxl.size(); 226 if (argc > 3) { 227 long cs = atol(argv[3]); // NOLINT 228 if (cs < 100) { 229 fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n"); 230 return 1; 231 } 232 chunksize = cs; 233 } 234 235 if (!DecodeJpegXlProgressive(jxl.data(), jxl.size(), png_filename, 236 chunksize)) { 237 fprintf(stderr, "Error while decoding the jxl file\n"); 238 return 1; 239 } 240 return 0; 241 }