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