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 in one shot (all input bytes
7 // available at once). The example outputs the pixels and color information to a
8 // floating point image and an ICC profile on disk.
9 
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <string.h>
14 
15 #include <vector>
16 
17 #include "jxl/decode.h"
18 #include "jxl/decode_cxx.h"
19 #include "jxl/resizable_parallel_runner.h"
20 #include "jxl/resizable_parallel_runner_cxx.h"
21 
22 /** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are
23  * stored as floating point, as interleaved RGBA (4 floating point values per
24  * pixel), line per line from top to bottom.  Pixel values have nominal range
25  * 0..1 but may go beyond this range for HDR or wide gamut. The ICC profile
26  * describes the color format of the pixel data.
27  */
DecodeJpegXlOneShot(const uint8_t * jxl,size_t size,std::vector<float> * pixels,size_t * xsize,size_t * ysize,std::vector<uint8_t> * icc_profile)28 bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size,
29                          std::vector<float>* pixels, size_t* xsize,
30                          size_t* ysize, std::vector<uint8_t>* icc_profile) {
31   // Multi-threaded parallel runner.
32   auto runner = JxlResizableParallelRunnerMake(nullptr);
33 
34   auto dec = JxlDecoderMake(nullptr);
35   if (JXL_DEC_SUCCESS !=
36       JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
37                                                JXL_DEC_COLOR_ENCODING |
38                                                JXL_DEC_FULL_IMAGE)) {
39     fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
40     return false;
41   }
42 
43   if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
44                                                      JxlResizableParallelRunner,
45                                                      runner.get())) {
46     fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
47     return false;
48   }
49 
50   JxlBasicInfo info;
51   JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
52 
53   JxlDecoderSetInput(dec.get(), jxl, size);
54 
55   for (;;) {
56     JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
57 
58     if (status == JXL_DEC_ERROR) {
59       fprintf(stderr, "Decoder error\n");
60       return false;
61     } else if (status == JXL_DEC_NEED_MORE_INPUT) {
62       fprintf(stderr, "Error, already provided all input\n");
63       return false;
64     } else if (status == JXL_DEC_BASIC_INFO) {
65       if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
66         fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
67         return false;
68       }
69       *xsize = info.xsize;
70       *ysize = info.ysize;
71       JxlResizableParallelRunnerSetThreads(
72           runner.get(),
73           JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
74     } else if (status == JXL_DEC_COLOR_ENCODING) {
75       // Get the ICC color profile of the pixel data
76       size_t icc_size;
77       if (JXL_DEC_SUCCESS !=
78           JxlDecoderGetICCProfileSize(
79               dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) {
80         fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
81         return false;
82       }
83       icc_profile->resize(icc_size);
84       if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
85                                  dec.get(), &format,
86                                  JXL_COLOR_PROFILE_TARGET_DATA,
87                                  icc_profile->data(), icc_profile->size())) {
88         fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
89         return false;
90       }
91     } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
92       size_t buffer_size;
93       if (JXL_DEC_SUCCESS !=
94           JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
95         fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
96         return false;
97       }
98       if (buffer_size != *xsize * *ysize * 16) {
99         fprintf(stderr, "Invalid out buffer size %zu %zu\n", buffer_size,
100                 *xsize * *ysize * 16);
101         return false;
102       }
103       pixels->resize(*xsize * *ysize * 4);
104       void* pixels_buffer = (void*)pixels->data();
105       size_t pixels_buffer_size = pixels->size() * sizeof(float);
106       if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
107                                                          pixels_buffer,
108                                                          pixels_buffer_size)) {
109         fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
110         return false;
111       }
112     } else if (status == JXL_DEC_FULL_IMAGE) {
113       // Nothing to do. Do not yet return. If the image is an animation, more
114       // full frames may be decoded. This example only keeps the last one.
115     } else if (status == JXL_DEC_SUCCESS) {
116       // All decoding successfully finished.
117       // It's not required to call JxlDecoderReleaseInput(dec.get()) here since
118       // the decoder will be destroyed.
119       return true;
120     } else {
121       fprintf(stderr, "Unknown decoder status\n");
122       return false;
123     }
124   }
125 }
126 
127 /** Writes to .pfm file (Portable FloatMap). Gimp, tev viewer and ImageMagick
128  * support viewing this format.
129  * The input pixels are given as 32-bit floating point with 4-channel RGBA.
130  * The alpha channel will not be written since .pfm does not support it.
131  */
WritePFM(const char * filename,const float * pixels,size_t xsize,size_t ysize)132 bool WritePFM(const char* filename, const float* pixels, size_t xsize,
133               size_t ysize) {
134   FILE* file = fopen(filename, "wb");
135   if (!file) {
136     fprintf(stderr, "Could not open %s for writing", filename);
137     return false;
138   }
139   uint32_t endian_test = 1;
140   uint8_t little_endian[4];
141   memcpy(little_endian, &endian_test, 4);
142 
143   fprintf(file, "PF\n%d %d\n%s\n", (int)xsize, (int)ysize,
144           little_endian[0] ? "-1.0" : "1.0");
145   for (int y = ysize - 1; y >= 0; y--) {
146     for (size_t x = 0; x < xsize; x++) {
147       for (size_t c = 0; c < 3; c++) {
148         const float* f = &pixels[(y * xsize + x) * 4 + c];
149         fwrite(f, 4, 1, file);
150       }
151     }
152   }
153   if (fclose(file) != 0) {
154     return false;
155   }
156   return true;
157 }
158 
LoadFile(const char * filename,std::vector<uint8_t> * out)159 bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
160   FILE* file = fopen(filename, "rb");
161   if (!file) {
162     return false;
163   }
164 
165   if (fseek(file, 0, SEEK_END) != 0) {
166     fclose(file);
167     return false;
168   }
169 
170   long size = ftell(file);
171   // Avoid invalid file or directory.
172   if (size >= LONG_MAX || size < 0) {
173     fclose(file);
174     return false;
175   }
176 
177   if (fseek(file, 0, SEEK_SET) != 0) {
178     fclose(file);
179     return false;
180   }
181 
182   out->resize(size);
183   size_t readsize = fread(out->data(), 1, size, file);
184   if (fclose(file) != 0) {
185     return false;
186   }
187 
188   return readsize == static_cast<size_t>(size);
189 }
190 
WriteFile(const char * filename,const uint8_t * data,size_t size)191 bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
192   FILE* file = fopen(filename, "wb");
193   if (!file) {
194     fprintf(stderr, "Could not open %s for writing", filename);
195     return false;
196   }
197   fwrite(data, 1, size, file);
198   if (fclose(file) != 0) {
199     return false;
200   }
201   return true;
202 }
203 
main(int argc,char * argv[])204 int main(int argc, char* argv[]) {
205   if (argc != 4) {
206     fprintf(stderr,
207             "Usage: %s <jxl> <pfm> <icc>\n"
208             "Where:\n"
209             "  jxl = input JPEG XL image filename\n"
210             "  pfm = output Portable FloatMap image filename\n"
211             "  icc = output ICC color profile filename\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* pfm_filename = argv[2];
219   const char* icc_filename = argv[3];
220 
221   std::vector<uint8_t> jxl;
222   if (!LoadFile(jxl_filename, &jxl)) {
223     fprintf(stderr, "couldn't load %s\n", jxl_filename);
224     return 1;
225   }
226 
227   std::vector<float> pixels;
228   std::vector<uint8_t> icc_profile;
229   size_t xsize = 0, ysize = 0;
230   if (!DecodeJpegXlOneShot(jxl.data(), jxl.size(), &pixels, &xsize, &ysize,
231                            &icc_profile)) {
232     fprintf(stderr, "Error while decoding the jxl file\n");
233     return 1;
234   }
235   if (!WritePFM(pfm_filename, pixels.data(), xsize, ysize)) {
236     fprintf(stderr, "Error while writing the PFM image file\n");
237     return 1;
238   }
239   if (!WriteFile(icc_filename, icc_profile.data(), icc_profile.size())) {
240     fprintf(stderr, "Error while writing the ICC profile file\n");
241     return 1;
242   }
243   printf("Successfully wrote %s and %s\n", pfm_filename, icc_filename);
244   return 0;
245 }
246