summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/experimental/fast_lossless
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/jpeg-xl/experimental/fast_lossless')
-rw-r--r--third_party/jpeg-xl/experimental/fast_lossless/.gitignore1
-rw-r--r--third_party/jpeg-xl/experimental/fast_lossless/README.md10
-rwxr-xr-xthird_party/jpeg-xl/experimental/fast_lossless/build-android.sh27
-rwxr-xr-xthird_party/jpeg-xl/experimental/fast_lossless/build.sh27
-rwxr-xr-xthird_party/jpeg-xl/experimental/fast_lossless/cross_compile_aarch64.sh26
-rw-r--r--third_party/jpeg-xl/experimental/fast_lossless/fast_lossless_main.cc113
-rw-r--r--third_party/jpeg-xl/experimental/fast_lossless/pam-input.h289
7 files changed, 493 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/.gitignore b/third_party/jpeg-xl/experimental/fast_lossless/.gitignore
new file mode 100644
index 0000000000..567609b123
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/README.md b/third_party/jpeg-xl/experimental/fast_lossless/README.md
new file mode 100644
index 0000000000..5f99c133d8
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/README.md
@@ -0,0 +1,10 @@
+# Fast-lossless
+This is a script to compile a standalone version of a JXL encoder that supports
+lossless compression, up to 16 bits, of 1- to 4-channel images and animations; it is
+very fast and compression is slightly worse than PNG for 8-bit nonphoto content
+and better or much better than PNG for all other situations.
+
+The main encoder is made out of two files, `lib/jxl/enc_fast_lossless.{cc,h}`;
+it automatically selects and runs a SIMD implementation supported by your CPU.
+
+This folder contains an example build script and `main` file.
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/build-android.sh b/third_party/jpeg-xl/experimental/fast_lossless/build-android.sh
new file mode 100755
index 0000000000..c155b2169a
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/build-android.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# 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.
+set -e
+
+DIR=$(realpath "$(dirname "$0")")
+
+mkdir -p /tmp/build-android
+cd /tmp/build-android
+
+CXX="$ANDROID_NDK"/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++
+if ! command -v "$CXX" >/dev/null ; then
+ printf >&2 '%s: Android C++ compiler not found, is ANDROID_NDK set properly?\n' "${0##*/}"
+ exit 1
+fi
+
+[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
+[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
+[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -o lodepng.o -c
+
+"$CXX" -O3 \
+ -I. lodepng.o \
+ -I"${DIR}"/../../ \
+ "${DIR}"/../../lib/jxl/enc_fast_lossless.cc "${DIR}"/fast_lossless_main.cc \
+ -o fast_lossless
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/build.sh b/third_party/jpeg-xl/experimental/fast_lossless/build.sh
new file mode 100755
index 0000000000..e2c0aa3fd0
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/build.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# 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.
+set -e
+
+DIR=$(realpath "$(dirname "$0")")
+mkdir -p "$DIR"/build
+cd "$DIR"/build
+
+# set CXX to clang++ if not set in the environment
+CXX="${CXX-clang++}"
+if ! command -v "$CXX" >/dev/null ; then
+ printf >&2 '%s: C++ compiler not found\n' "${0##*/}"
+ exit 1
+fi
+
+[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
+[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
+[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -o lodepng.o -c
+
+"$CXX" -O3 \
+ -I. -g lodepng.o \
+ -I"$DIR"/../../ \
+ "$DIR"/../../lib/jxl/enc_fast_lossless.cc "$DIR"/fast_lossless_main.cc \
+ -o fast_lossless
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/cross_compile_aarch64.sh b/third_party/jpeg-xl/experimental/fast_lossless/cross_compile_aarch64.sh
new file mode 100755
index 0000000000..a5e6aa2a52
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/cross_compile_aarch64.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# 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.
+set -e
+
+DIR=$(realpath "$(dirname "$0")")
+mkdir -p "$DIR"/build-aarch64
+cd "$DIR"/build-aarch64
+
+CXX="${CXX-aarch64-linux-gnu-c++}"
+if ! command -v "$CXX" >/dev/null ; then
+ printf >&2 '%s: C++ compiler not found\n' "${0##*/}"
+ exit 1
+fi
+
+[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
+[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
+[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -o lodepng.o -c
+
+"$CXX" -O3 -static \
+ -I. lodepng.o \
+ -I"$DIR"/../../ \
+ "$DIR"/../../lib/jxl/enc_fast_lossless.cc "$DIR"/fast_lossless_main.cc \
+ -o fast_lossless
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless_main.cc b/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless_main.cc
new file mode 100644
index 0000000000..b59051d4e2
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/fast_lossless_main.cc
@@ -0,0 +1,113 @@
+// 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atomic>
+#include <chrono>
+#include <thread>
+#include <vector>
+
+#include "lib/jxl/enc_fast_lossless.h"
+#include "lodepng.h"
+#include "pam-input.h"
+
+int main(int argc, char** argv) {
+ if (argc < 3) {
+ fprintf(stderr,
+ "Usage: %s in.png out.jxl [effort] [num_reps] [num_threads]\n",
+ argv[0]);
+ return 1;
+ }
+
+ const char* in = argv[1];
+ const char* out = argv[2];
+ int effort = argc >= 4 ? atoi(argv[3]) : 2;
+ size_t num_reps = argc >= 5 ? atoi(argv[4]) : 1;
+ size_t num_threads = argc >= 6 ? atoi(argv[5]) : 0;
+
+ if (effort < 0 || effort > 127) {
+ fprintf(
+ stderr,
+ "Effort should be between 0 and 127 (default is 2, more is slower)\n");
+ return 1;
+ }
+
+ unsigned char* png;
+ unsigned w, h;
+ size_t nb_chans = 4, bitdepth = 8;
+
+ unsigned error = lodepng_decode32_file(&png, &w, &h, in);
+
+ size_t width = w, height = h;
+ if (error && !DecodePAM(in, &png, &width, &height, &nb_chans, &bitdepth)) {
+ fprintf(stderr, "lodepng error %u: %s\n", error, lodepng_error_text(error));
+ return 1;
+ }
+
+ auto parallel_runner = [](void* num_threads_ptr, void* opaque,
+ void fun(void*, size_t), size_t count) {
+ size_t num_threads = *(size_t*)num_threads_ptr;
+ if (num_threads == 0) {
+ num_threads = std::thread::hardware_concurrency();
+ }
+ if (num_threads > count) {
+ num_threads = count;
+ }
+ if (num_threads == 1) {
+ for (size_t i = 0; i < count; i++) {
+ fun(opaque, i);
+ }
+ } else {
+ std::atomic<int> task{0};
+ std::vector<std::thread> threads;
+ for (size_t i = 0; i < num_threads; i++) {
+ threads.push_back(std::thread([count, opaque, fun, &task]() {
+ while (true) {
+ int t = task++;
+ if (t >= count) break;
+ fun(opaque, t);
+ }
+ }));
+ }
+ for (auto& t : threads) t.join();
+ }
+ };
+
+ size_t encoded_size = 0;
+ unsigned char* encoded = nullptr;
+ size_t stride = width * nb_chans * (bitdepth > 8 ? 2 : 1);
+
+ auto start = std::chrono::high_resolution_clock::now();
+ for (size_t _ = 0; _ < num_reps; _++) {
+ free(encoded);
+ encoded_size = JxlFastLosslessEncode(
+ png, width, stride, height, nb_chans, bitdepth,
+ /*big_endian=*/true, effort, &encoded, &num_threads, +parallel_runner);
+ }
+ auto stop = std::chrono::high_resolution_clock::now();
+ if (num_reps > 1) {
+ float us =
+ std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
+ .count();
+ size_t pixels = size_t{width} * size_t{height} * num_reps;
+ float mps = pixels / us;
+ fprintf(stderr, "%10.3f MP/s\n", mps);
+ fprintf(stderr, "%10.3f bits/pixel\n",
+ encoded_size * 8.0 / float(width) / float(height));
+ }
+
+ FILE* o = fopen(out, "wb");
+ if (!o) {
+ fprintf(stderr, "error opening %s: %s\n", out, strerror(errno));
+ return 1;
+ }
+ if (fwrite(encoded, 1, encoded_size, o) != encoded_size) {
+ fprintf(stderr, "error writing to %s: %s\n", out, strerror(errno));
+ }
+ fclose(o);
+}
diff --git a/third_party/jpeg-xl/experimental/fast_lossless/pam-input.h b/third_party/jpeg-xl/experimental/fast_lossless/pam-input.h
new file mode 100644
index 0000000000..4ecbe6b72d
--- /dev/null
+++ b/third_party/jpeg-xl/experimental/fast_lossless/pam-input.h
@@ -0,0 +1,289 @@
+// 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.
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+bool error_msg(const char* message) {
+ fprintf(stderr, "%s\n", message);
+ return false;
+}
+#define return_on_error(X) \
+ if (!X) return false;
+
+size_t Log2(uint32_t value) { return 31 - __builtin_clz(value); }
+
+struct HeaderPNM {
+ size_t xsize;
+ size_t ysize;
+ bool is_gray; // PGM
+ bool has_alpha; // PAM
+ size_t bits_per_sample;
+};
+
+class Parser {
+ public:
+ explicit Parser(uint8_t* data, size_t length)
+ : pos_(data), end_(data + length) {}
+
+ // Sets "pos" to the first non-header byte/pixel on success.
+ bool ParseHeader(HeaderPNM* header, const uint8_t** pos) {
+ // codec.cc ensures we have at least two bytes => no range check here.
+ if (pos_[0] != 'P') return false;
+ const uint8_t type = pos_[1];
+ pos_ += 2;
+
+ switch (type) {
+ case '5':
+ header->is_gray = true;
+ return ParseHeaderPNM(header, pos);
+
+ case '6':
+ header->is_gray = false;
+ return ParseHeaderPNM(header, pos);
+
+ case '7':
+ return ParseHeaderPAM(header, pos);
+ }
+ return false;
+ }
+
+ // Exposed for testing
+ bool ParseUnsigned(size_t* number) {
+ if (pos_ == end_) return error_msg("PNM: reached end before number");
+ if (!IsDigit(*pos_)) return error_msg("PNM: expected unsigned number");
+
+ *number = 0;
+ while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
+ *number *= 10;
+ *number += *pos_ - '0';
+ ++pos_;
+ }
+
+ return true;
+ }
+
+ bool ParseSigned(double* number) {
+ if (pos_ == end_) return error_msg("PNM: reached end before signed");
+
+ if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) {
+ return error_msg("PNM: expected signed number");
+ }
+
+ // Skip sign
+ const bool is_neg = *pos_ == '-';
+ if (is_neg || *pos_ == '+') {
+ ++pos_;
+ if (pos_ == end_) return error_msg("PNM: reached end before digits");
+ }
+
+ // Leading digits
+ *number = 0.0;
+ while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
+ *number *= 10;
+ *number += *pos_ - '0';
+ ++pos_;
+ }
+
+ // Decimal places?
+ if (pos_ < end_ && *pos_ == '.') {
+ ++pos_;
+ double place = 0.1;
+ while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
+ *number += (*pos_ - '0') * place;
+ place *= 0.1;
+ ++pos_;
+ }
+ }
+
+ if (is_neg) *number = -*number;
+ return true;
+ }
+
+ private:
+ static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
+ static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
+ static bool IsWhitespace(const uint8_t c) {
+ return IsLineBreak(c) || c == '\t' || c == ' ';
+ }
+
+ bool SkipBlank() {
+ if (pos_ == end_) return error_msg("PNM: reached end before blank");
+ const uint8_t c = *pos_;
+ if (c != ' ' && c != '\n') return error_msg("PNM: expected blank");
+ ++pos_;
+ return true;
+ }
+
+ bool SkipSingleWhitespace() {
+ if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
+ if (!IsWhitespace(*pos_)) return error_msg("PNM: expected whitespace");
+ ++pos_;
+ return true;
+ }
+
+ bool SkipWhitespace() {
+ if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
+ if (!IsWhitespace(*pos_) && *pos_ != '#') {
+ return error_msg("PNM: expected whitespace/comment");
+ }
+
+ while (pos_ < end_ && IsWhitespace(*pos_)) {
+ ++pos_;
+ }
+
+ // Comment(s)
+ while (pos_ != end_ && *pos_ == '#') {
+ while (pos_ != end_ && !IsLineBreak(*pos_)) {
+ ++pos_;
+ }
+ // Newline(s)
+ while (pos_ != end_ && IsLineBreak(*pos_)) pos_++;
+ }
+
+ while (pos_ < end_ && IsWhitespace(*pos_)) {
+ ++pos_;
+ }
+ return true;
+ }
+
+ bool MatchString(const char* keyword) {
+ const uint8_t* ppos = pos_;
+ while (*keyword) {
+ if (ppos >= end_) return error_msg("PAM: unexpected end of input");
+ if (*keyword != *ppos) return false;
+ ppos++;
+ keyword++;
+ }
+ pos_ = ppos;
+ return_on_error(SkipWhitespace());
+ return true;
+ }
+
+ bool ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
+ size_t num_channels = 3;
+ size_t max_val = 255;
+ while (!MatchString("ENDHDR")) {
+ return_on_error(SkipWhitespace());
+ if (MatchString("WIDTH")) {
+ return_on_error(ParseUnsigned(&header->xsize));
+ } else if (MatchString("HEIGHT")) {
+ return_on_error(ParseUnsigned(&header->ysize));
+ } else if (MatchString("DEPTH")) {
+ return_on_error(ParseUnsigned(&num_channels));
+ } else if (MatchString("MAXVAL")) {
+ return_on_error(ParseUnsigned(&max_val));
+ } else if (MatchString("TUPLTYPE")) {
+ if (MatchString("RGB_ALPHA")) {
+ header->has_alpha = true;
+ } else if (MatchString("RGB")) {
+ } else if (MatchString("GRAYSCALE_ALPHA")) {
+ header->has_alpha = true;
+ header->is_gray = true;
+ } else if (MatchString("GRAYSCALE")) {
+ header->is_gray = true;
+ } else if (MatchString("BLACKANDWHITE_ALPHA")) {
+ header->has_alpha = true;
+ header->is_gray = true;
+ max_val = 1;
+ } else if (MatchString("BLACKANDWHITE")) {
+ header->is_gray = true;
+ max_val = 1;
+ } else {
+ return error_msg("PAM: unknown TUPLTYPE");
+ }
+ } else {
+ return error_msg("PAM: unknown header keyword");
+ }
+ }
+ if (num_channels !=
+ (header->has_alpha ? 1 : 0) + (header->is_gray ? 1 : 3)) {
+ return error_msg("PAM: bad DEPTH");
+ }
+ if (max_val == 0 || max_val >= 65536) {
+ return error_msg("PAM: bad MAXVAL");
+ }
+ header->bits_per_sample = Log2(max_val + 1);
+
+ *pos = pos_;
+ return true;
+ }
+
+ bool ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) {
+ return_on_error(SkipWhitespace());
+ return_on_error(ParseUnsigned(&header->xsize));
+
+ return_on_error(SkipWhitespace());
+ return_on_error(ParseUnsigned(&header->ysize));
+
+ return_on_error(SkipWhitespace());
+ size_t max_val;
+ return_on_error(ParseUnsigned(&max_val));
+ if (max_val == 0 || max_val >= 65536) {
+ return error_msg("PNM: bad MaxVal");
+ }
+ header->bits_per_sample = Log2(max_val + 1);
+
+ return_on_error(SkipSingleWhitespace());
+
+ *pos = pos_;
+ return true;
+ }
+
+ const uint8_t* pos_;
+ const uint8_t* const end_;
+};
+
+bool load_file(unsigned char** out, size_t* outsize, const char* filename) {
+ FILE* file;
+ file = fopen(filename, "rb");
+ if (!file) return false;
+ if (fseek(file, 0, SEEK_END) != 0) {
+ fclose(file);
+ return false;
+ }
+ *outsize = ftell(file);
+ if (*outsize == LONG_MAX || *outsize < 9 || fseek(file, 0, SEEK_SET)) {
+ fclose(file);
+ return false;
+ }
+ *out = (unsigned char*)malloc(*outsize);
+ if (!(*out)) return false;
+ size_t readsize;
+ readsize = fread(*out, 1, *outsize, file);
+ fclose(file);
+ if (readsize != *outsize) return false;
+ return true;
+}
+
+bool DecodePAM(const char* filename, uint8_t** buffer, size_t* w, size_t* h,
+ size_t* nb_chans, size_t* bitdepth) {
+ unsigned char* in_file;
+ size_t in_size;
+ if (!load_file(&in_file, &in_size, filename))
+ return error_msg("Could not read input file");
+ Parser parser(in_file, in_size);
+ HeaderPNM header = {};
+ const uint8_t* pos = nullptr;
+ if (!parser.ParseHeader(&header, &pos)) return false;
+
+ if (header.bits_per_sample == 0 || header.bits_per_sample > 16) {
+ return error_msg("PNM: bits_per_sample invalid (can do at most 16-bit)");
+ }
+ *w = header.xsize;
+ *h = header.ysize;
+ *bitdepth = header.bits_per_sample;
+ *nb_chans = (header.is_gray ? 1 : 3) + (header.has_alpha ? 1 : 0);
+
+ size_t pnm_remaining_size = in_file + in_size - pos;
+ size_t buffer_size = *w * *h * *nb_chans * (*bitdepth > 8 ? 2 : 1);
+ if (pnm_remaining_size < buffer_size) {
+ return error_msg("PNM file too small");
+ }
+ *buffer = (uint8_t*)malloc(buffer_size);
+ memcpy(*buffer, pos, buffer_size);
+ return true;
+}