Last active
January 29, 2024 11:30
-
-
Save harrisonturton/cc943e032b47e477f523c1c776c8b20f to your computer and use it in GitHub Desktop.
Using libjxl to transcode a JPEG file into a JPEG-XL file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Run with ./main <input jpeg path> <output jxl path>. | |
* | |
* A minimal example of taking a JPEG and re-encoding it as a JPEG-XL image with | |
* a variety of encoder settings. | |
*/ | |
#include <jxl/encode.h> | |
#include <jxl/encode_cxx.h> | |
#include <jxl/jxl_export.h> | |
#include <jxl/thread_parallel_runner.h> | |
#include <jxl/thread_parallel_runner_cxx.h> | |
#include <climits> | |
#include <iostream> | |
#include <sstream> | |
#include <vector> | |
#define DISTANCE 2.5 | |
#define ENCODER_EFFORT 9 | |
#define RESAMPLING 1 | |
#define BROTLI_EFFORT 8 | |
#define DECODE_SPEED 0 | |
#define PROGRESSIVE_AC true | |
#define QPROGRESSIVE_AC true | |
#define EPF 1 | |
#define GABORISH true | |
int transcode(const std::vector<uint8_t>& inbuf, std::vector<uint8_t>& outbuf) { | |
auto enc = JxlEncoderMake(nullptr); | |
if (JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(enc.get(), false)) { | |
fprintf(stderr, "Failed to set not keep JPEG metadata\n"); | |
return EXIT_FAILURE; | |
} | |
// Since this is called without previously calling `JxlEncoderSetBasicInfo` or | |
// `JxlEncoderSetColorEncoding`, it will infer the parameters from the JPEG. | |
auto frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr); | |
if (JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(frame_settings, false)) { | |
fprintf(stderr, "Failed to set not lossless\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Distance: %f\n", DISTANCE); | |
if (JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, DISTANCE)) { | |
fprintf(stderr, "Failed to set frame distance\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Encoder effort: %d\n", ENCODER_EFFORT); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, ENCODER_EFFORT)) { | |
fprintf(stderr, "Failed to set effort\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Resampling: %d\n", RESAMPLING); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_RESAMPLING, RESAMPLING)) { | |
fprintf(stderr, "Failed to set effort\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Brotli effort: %d\n", BROTLI_EFFORT); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_BROTLI_EFFORT, BROTLI_EFFORT)) { | |
fprintf(stderr, "Failed to set effort\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Decoding speed: %d\n", DECODE_SPEED); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, DECODE_SPEED)) { | |
fprintf(stderr, "Failed to set decode speed\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Keep EXIF: false\n"); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, false)) { | |
fprintf(stderr, "Failed to set keep EXIF\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Keep XMP: false\n"); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, false)) { | |
fprintf(stderr, "Failed to set keep XMP\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Keep JUMBF: false\n"); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF, false)) { | |
fprintf(stderr, "Failed to set keep JUMBF\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Progressive AC: %d\n", PROGRESSIVE_AC); | |
if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption( | |
frame_settings, | |
JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, | |
PROGRESSIVE_AC)) { | |
fprintf(stderr, "Failed to set progressive AC\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Q Progressive AC: %d\n", QPROGRESSIVE_AC); | |
if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption( | |
frame_settings, | |
JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, | |
QPROGRESSIVE_AC)) { | |
fprintf(stderr, "Failed to set Q progressive AC\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Compress JPEG metadata boxes: true\n"); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES, 1)) { | |
fprintf(stderr, "Failed to set compress jpeg metadata boxes\n"); | |
return EXIT_FAILURE; | |
} | |
printf("Gaborish: %d\n", GABORISH); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_GABORISH, GABORISH)) { | |
fprintf(stderr, "Failed to set set gaborish\n"); | |
return EXIT_FAILURE; | |
} | |
printf("EPF strength: %d\n", EPF); | |
if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption( | |
frame_settings, JXL_ENC_FRAME_SETTING_EPF, EPF)) { | |
fprintf(stderr, "Failed to set set epf\n"); | |
return EXIT_FAILURE; | |
} | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderAddJPEGFrame(frame_settings, inbuf.data(), inbuf.size())) { | |
fprintf(stderr, "Failed to add JPEG frame\n"); | |
return EXIT_FAILURE; | |
} | |
// Still images only have one frame. | |
JxlEncoderCloseInput(enc.get()); | |
// Defer threadcount to std::thread::hardware_concurrency | |
auto runner = JxlThreadParallelRunnerMake( | |
nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads()); | |
if (JXL_ENC_SUCCESS != | |
JxlEncoderSetParallelRunner( | |
enc.get(), JxlThreadParallelRunner, runner.get())) { | |
fprintf(stderr, "Failed to create parallel runner\n"); | |
return EXIT_FAILURE; | |
} | |
outbuf.resize(64); | |
auto next_out = outbuf.data(); | |
auto avail_out = outbuf.size() - (next_out - outbuf.data()); | |
// JxlEncoderProcessOutput must be called repeatedly. It's return code | |
// indicates whether or not more bytes are needed in the output buffer, so we | |
// need to handle this case and resize it where necessary. | |
auto res = JXL_ENC_NEED_MORE_OUTPUT; | |
while (res == JXL_ENC_NEED_MORE_OUTPUT) { | |
if (JXL_ENC_SUCCESS != | |
(res = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out))) { | |
auto offset = next_out - outbuf.data(); | |
outbuf.resize(outbuf.size() * 2); | |
next_out = outbuf.data() + offset; | |
avail_out = outbuf.size() - offset; | |
} | |
} | |
outbuf.resize(next_out - outbuf.data()); | |
if (JXL_ENC_SUCCESS != res) { | |
fprintf(stderr, "Failed to process JXL output\n"); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} | |
int read_file(const char* path, std::vector<uint8_t>& buf) { | |
FILE* file = fopen(path, "r"); | |
if (!file) { | |
fprintf(stderr, "Could not open the file\n"); | |
return EXIT_FAILURE; | |
} | |
if (fseek(file, 0, SEEK_END) != 0) { | |
fprintf(stderr, "Could not find the end of the file\n"); | |
fclose(file); | |
return EXIT_FAILURE; | |
} | |
long size = ftell(file); | |
if (size >= LONG_MAX || size < 0) { | |
fprintf(stderr, "File is either too big or it's a directory\n"); | |
fclose(file); | |
return EXIT_FAILURE; | |
} | |
if (fseek(file, 0, SEEK_SET) != 0) { | |
fprintf(stderr, "Failed to reset file cursor\n"); | |
fclose(file); | |
return EXIT_FAILURE; | |
} | |
buf.resize(size); | |
auto read_count = fread(buf.data(), 1, size, file); | |
if ((long)read_count != size) { | |
fprintf(stderr, "Failed to read from file\n"); | |
fclose(file); | |
return EXIT_FAILURE; | |
} | |
if (fclose(file) != 0) { | |
fprintf(stderr, "Failed to close the file\n"); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} | |
int write_file(const std::vector<uint8_t>& buf, const char* filename) { | |
auto file = fopen(filename, "wb"); | |
if (!file) { | |
fprintf(stderr, "Could not open the file\n"); | |
return EXIT_FAILURE; | |
} | |
if (fwrite(buf.data(), sizeof(uint8_t), buf.size(), file) != buf.size()) { | |
fprintf(stderr, "Could not write bytes to %s\n", filename); | |
fclose(file); | |
return EXIT_FAILURE; | |
} | |
if (fclose(file) != 0) { | |
fprintf(stderr, "Could not close %s\n", filename); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} | |
int main(int argc, const char** argv) { | |
if (argc < 3) { | |
fprintf(stderr, "Usage: %s <filepath> <outpath>\n", argv[0]); | |
return 1; | |
} | |
std::vector<uint8_t> jpeg; | |
if (read_file(argv[1], jpeg) != EXIT_SUCCESS) { | |
fprintf(stderr, "Failed to read file data\n"); | |
return EXIT_FAILURE; | |
} | |
std::vector<uint8_t> jxl; | |
if (transcode(jpeg, jxl) != EXIT_SUCCESS) { | |
fprintf(stderr, "Failed to recompress JPEG into JXL\n"); | |
return EXIT_FAILURE; | |
} | |
if (write_file(jxl, argv[2]) != EXIT_SUCCESS) { | |
fprintf(stderr, "Failed to write JXL file\n"); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
COPTS=-std=c++17 -Wall | |
INCLUDE=-I/usr/local/include/jxl/ -L/usr/local/lib/ | |
LIBS=-ljxl -ljxl_threads | |
BIN=main | |
$(BIN): main.cc | |
clang++ $(COPTS) $(INCLUDE) $(LIBS) -o $@ $< | |
.PHONY: clean | |
clean: | |
rm $(BIN) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment