2021-05-26 11:46:02 +03:00
|
|
|
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
2021-05-06 05:00:55 +03:00
|
|
|
//
|
2021-05-26 11:46:02 +03:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
2021-05-06 05:00:55 +03:00
|
|
|
|
|
|
|
// This C++ example decodes a JPEG XL image in one shot (all input bytes
|
|
|
|
// available at once). The example outputs the pixels and color information to a
|
|
|
|
// floating point image and an ICC profile on disk.
|
|
|
|
|
2021-10-28 04:06:52 +03:00
|
|
|
#include <inttypes.h>
|
2021-05-06 05:00:55 +03:00
|
|
|
#include <limits.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "jxl/decode.h"
|
|
|
|
#include "jxl/decode_cxx.h"
|
2021-09-28 13:54:24 +03:00
|
|
|
#include "jxl/resizable_parallel_runner.h"
|
|
|
|
#include "jxl/resizable_parallel_runner_cxx.h"
|
2021-05-06 05:00:55 +03:00
|
|
|
|
|
|
|
/** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are
|
|
|
|
* stored as floating point, as interleaved RGBA (4 floating point values per
|
|
|
|
* pixel), line per line from top to bottom. Pixel values have nominal range
|
|
|
|
* 0..1 but may go beyond this range for HDR or wide gamut. The ICC profile
|
|
|
|
* describes the color format of the pixel data.
|
|
|
|
*/
|
|
|
|
bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size,
|
|
|
|
std::vector<float>* pixels, size_t* xsize,
|
|
|
|
size_t* ysize, std::vector<uint8_t>* icc_profile) {
|
|
|
|
// Multi-threaded parallel runner.
|
2021-09-28 13:54:24 +03:00
|
|
|
auto runner = JxlResizableParallelRunnerMake(nullptr);
|
2021-05-06 05:00:55 +03:00
|
|
|
|
|
|
|
auto dec = JxlDecoderMake(nullptr);
|
|
|
|
if (JXL_DEC_SUCCESS !=
|
|
|
|
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
|
|
|
|
JXL_DEC_COLOR_ENCODING |
|
|
|
|
JXL_DEC_FULL_IMAGE)) {
|
|
|
|
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
|
2021-09-28 13:54:24 +03:00
|
|
|
JxlResizableParallelRunner,
|
2021-05-06 05:00:55 +03:00
|
|
|
runner.get())) {
|
|
|
|
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
JxlBasicInfo info;
|
|
|
|
JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
|
|
|
|
|
|
|
|
JxlDecoderSetInput(dec.get(), jxl, size);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
|
|
|
|
|
|
|
|
if (status == JXL_DEC_ERROR) {
|
|
|
|
fprintf(stderr, "Decoder error\n");
|
|
|
|
return false;
|
|
|
|
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
|
|
|
|
fprintf(stderr, "Error, already provided all input\n");
|
|
|
|
return false;
|
|
|
|
} else if (status == JXL_DEC_BASIC_INFO) {
|
|
|
|
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
|
|
|
|
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
*xsize = info.xsize;
|
|
|
|
*ysize = info.ysize;
|
2021-09-28 13:54:24 +03:00
|
|
|
JxlResizableParallelRunnerSetThreads(
|
|
|
|
runner.get(),
|
|
|
|
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
|
2021-05-06 05:00:55 +03:00
|
|
|
} else if (status == JXL_DEC_COLOR_ENCODING) {
|
|
|
|
// Get the ICC color profile of the pixel data
|
|
|
|
size_t icc_size;
|
|
|
|
if (JXL_DEC_SUCCESS !=
|
|
|
|
JxlDecoderGetICCProfileSize(
|
|
|
|
dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) {
|
|
|
|
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
icc_profile->resize(icc_size);
|
|
|
|
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
|
|
|
|
dec.get(), &format,
|
|
|
|
JXL_COLOR_PROFILE_TARGET_DATA,
|
|
|
|
icc_profile->data(), icc_profile->size())) {
|
|
|
|
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
|
|
|
size_t buffer_size;
|
|
|
|
if (JXL_DEC_SUCCESS !=
|
|
|
|
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
|
|
|
|
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (buffer_size != *xsize * *ysize * 16) {
|
2021-10-28 04:06:52 +03:00
|
|
|
fprintf(stderr, "Invalid out buffer size %" PRIu64 " %" PRIu64 "\n",
|
|
|
|
static_cast<uint64_t>(buffer_size),
|
|
|
|
static_cast<uint64_t>(*xsize * *ysize * 16));
|
2021-05-06 05:00:55 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pixels->resize(*xsize * *ysize * 4);
|
|
|
|
void* pixels_buffer = (void*)pixels->data();
|
|
|
|
size_t pixels_buffer_size = pixels->size() * sizeof(float);
|
|
|
|
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
|
|
|
|
pixels_buffer,
|
|
|
|
pixels_buffer_size)) {
|
|
|
|
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (status == JXL_DEC_FULL_IMAGE) {
|
|
|
|
// Nothing to do. Do not yet return. If the image is an animation, more
|
|
|
|
// full frames may be decoded. This example only keeps the last one.
|
|
|
|
} else if (status == JXL_DEC_SUCCESS) {
|
|
|
|
// All decoding successfully finished.
|
|
|
|
// It's not required to call JxlDecoderReleaseInput(dec.get()) here since
|
|
|
|
// the decoder will be destroyed.
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Unknown decoder status\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Writes to .pfm file (Portable FloatMap). Gimp, tev viewer and ImageMagick
|
|
|
|
* support viewing this format.
|
|
|
|
* The input pixels are given as 32-bit floating point with 4-channel RGBA.
|
|
|
|
* The alpha channel will not be written since .pfm does not support it.
|
|
|
|
*/
|
|
|
|
bool WritePFM(const char* filename, const float* pixels, size_t xsize,
|
|
|
|
size_t ysize) {
|
|
|
|
FILE* file = fopen(filename, "wb");
|
|
|
|
if (!file) {
|
|
|
|
fprintf(stderr, "Could not open %s for writing", filename);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
uint32_t endian_test = 1;
|
|
|
|
uint8_t little_endian[4];
|
|
|
|
memcpy(little_endian, &endian_test, 4);
|
|
|
|
|
|
|
|
fprintf(file, "PF\n%d %d\n%s\n", (int)xsize, (int)ysize,
|
|
|
|
little_endian[0] ? "-1.0" : "1.0");
|
|
|
|
for (int y = ysize - 1; y >= 0; y--) {
|
|
|
|
for (size_t x = 0; x < xsize; x++) {
|
|
|
|
for (size_t c = 0; c < 3; c++) {
|
|
|
|
const float* f = &pixels[(y * xsize + x) * 4 + c];
|
|
|
|
fwrite(f, 4, 1, file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fclose(file) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
|
|
|
|
FILE* file = fopen(filename, "rb");
|
|
|
|
if (!file) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fseek(file, 0, SEEK_END) != 0) {
|
|
|
|
fclose(file);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
long size = ftell(file);
|
|
|
|
// Avoid invalid file or directory.
|
|
|
|
if (size >= LONG_MAX || size < 0) {
|
|
|
|
fclose(file);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fseek(file, 0, SEEK_SET) != 0) {
|
|
|
|
fclose(file);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
out->resize(size);
|
|
|
|
size_t readsize = fread(out->data(), 1, size, file);
|
|
|
|
if (fclose(file) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return readsize == static_cast<size_t>(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
|
|
|
|
FILE* file = fopen(filename, "wb");
|
|
|
|
if (!file) {
|
|
|
|
fprintf(stderr, "Could not open %s for writing", filename);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fwrite(data, 1, size, file);
|
|
|
|
if (fclose(file) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
if (argc != 4) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Usage: %s <jxl> <pfm> <icc>\n"
|
|
|
|
"Where:\n"
|
|
|
|
" jxl = input JPEG XL image filename\n"
|
|
|
|
" pfm = output Portable FloatMap image filename\n"
|
|
|
|
" icc = output ICC color profile filename\n"
|
|
|
|
"Output files will be overwritten.\n",
|
|
|
|
argv[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* jxl_filename = argv[1];
|
|
|
|
const char* pfm_filename = argv[2];
|
|
|
|
const char* icc_filename = argv[3];
|
|
|
|
|
|
|
|
std::vector<uint8_t> jxl;
|
|
|
|
if (!LoadFile(jxl_filename, &jxl)) {
|
|
|
|
fprintf(stderr, "couldn't load %s\n", jxl_filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<float> pixels;
|
|
|
|
std::vector<uint8_t> icc_profile;
|
|
|
|
size_t xsize = 0, ysize = 0;
|
|
|
|
if (!DecodeJpegXlOneShot(jxl.data(), jxl.size(), &pixels, &xsize, &ysize,
|
|
|
|
&icc_profile)) {
|
|
|
|
fprintf(stderr, "Error while decoding the jxl file\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!WritePFM(pfm_filename, pixels.data(), xsize, ysize)) {
|
|
|
|
fprintf(stderr, "Error while writing the PFM image file\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!WriteFile(icc_filename, icc_profile.data(), icc_profile.size())) {
|
|
|
|
fprintf(stderr, "Error while writing the ICC profile file\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
printf("Successfully wrote %s and %s\n", pfm_filename, icc_filename);
|
|
|
|
return 0;
|
|
|
|
}
|