// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // 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. #include #include #include #include #include #include #include "jxl/decode.h" #include "jxl/decode_cxx.h" #include "jxl/resizable_parallel_runner.h" #include "jxl/resizable_parallel_runner_cxx.h" /** 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* pixels, size_t* xsize, size_t* ysize, std::vector* icc_profile) { // Multi-threaded parallel runner. auto runner = JxlResizableParallelRunnerMake(nullptr); 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(), JxlResizableParallelRunner, 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); JxlDecoderCloseInput(dec.get()); 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; JxlResizableParallelRunnerSetThreads( runner.get(), JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize)); } 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) { fprintf(stderr, "Invalid out buffer size %" PRIu64 " %" PRIu64 "\n", static_cast(buffer_size), static_cast(*xsize * *ysize * 16)); 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* 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); } 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 \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 jxl; if (!LoadFile(jxl_filename, &jxl)) { fprintf(stderr, "couldn't load %s\n", jxl_filename); return 1; } std::vector pixels; std::vector 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; }