Skip to content

Instantly share code, notes, and snippets.

@harrisonturton
Last active January 29, 2024 11:30
Show Gist options
  • Save harrisonturton/cc943e032b47e477f523c1c776c8b20f to your computer and use it in GitHub Desktop.
Save harrisonturton/cc943e032b47e477f523c1c776c8b20f to your computer and use it in GitHub Desktop.
Using libjxl to transcode a JPEG file into a JPEG-XL file
/**
* 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;
}
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