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