зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1848578 - Update libjxl to e6202f7181eff36c78bfdb79aa9bd45c3d1d614b r=saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D186097
This commit is contained in:
Родитель
3f5eefadfb
Коммит
78c3beaf49
|
@ -10,9 +10,9 @@ origin:
|
||||||
|
|
||||||
url: https://github.com/libjxl/libjxl
|
url: https://github.com/libjxl/libjxl
|
||||||
|
|
||||||
release: 69d06e17771830beae7fb62b76de5978b52546fc (2023-07-21T14:55:24Z).
|
release: e6202f7181eff36c78bfdb79aa9bd45c3d1d614b (2023-08-11T12:36:59Z).
|
||||||
|
|
||||||
revision: 69d06e17771830beae7fb62b76de5978b52546fc
|
revision: e6202f7181eff36c78bfdb79aa9bd45c3d1d614b
|
||||||
|
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `JxlEncoderChunkedImageFrameStart`,
|
- `JxlEncoderChunkedImageFrameStart`,
|
||||||
- `JxlEncoderChunkedImageFrameAddPart` and new
|
- `JxlEncoderChunkedImageFrameAddPart` and new
|
||||||
- `JXL_ENC_FRAME_SETTING_BUFFERING` enum value.
|
- `JXL_ENC_FRAME_SETTING_BUFFERING` enum value.
|
||||||
|
- encoder API: new options for more fine-grained control over metadata
|
||||||
|
preservation when using `JxlEncoderAddJPEGFrame`:
|
||||||
|
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF`
|
||||||
|
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP`
|
||||||
|
- `JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF`
|
||||||
|
- encoder API: new function `JxlEncoderSetUpsamplingMode` to change the upsampling
|
||||||
|
method, e.g. to use nearest-neighbor upsampling for pixel art
|
||||||
|
- cjxl can now be used to explicitly add/update/strip Exif/XMP/JUMBF metadata using
|
||||||
|
the decoder-hints syntax, e.g. `cjxl input.ppm -x exif=input.exif output.jxl`
|
||||||
|
- djxl can now be used to extract Exif/XMP/JUMBF metadata
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- API: the Butteraugli API (`jxl/butteraugli.h`) was removed.
|
- API: the Butteraugli API (`jxl/butteraugli.h`) was removed.
|
||||||
- encoder and decoder API: all deprecated functions were removed:
|
- encoder and decoder API: all deprecated functions were removed:
|
||||||
|
@ -54,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed / clarified
|
### Changed / clarified
|
||||||
- encoder API: `JxlEncoderProcessOutput` requires at least 32 bytes of output
|
- encoder API: `JxlEncoderProcessOutput` requires at least 32 bytes of output
|
||||||
space to proceed and guarantees that at least one byte will be written
|
space to proceed and guarantees that at least one byte will be written
|
||||||
|
|
||||||
## [0.7] - 2022-07-21
|
## [0.7] - 2022-07-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# 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.
|
|
|
@ -1,27 +0,0 @@
|
||||||
#!/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
|
|
|
@ -1,27 +0,0 @@
|
||||||
#!/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
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/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
|
|
|
@ -1,113 +0,0 @@
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -1,289 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
|
@ -586,6 +586,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||||
std::vector<std::vector<uint8_t>> chunksInfo;
|
std::vector<std::vector<uint8_t>> chunksInfo;
|
||||||
bool isAnimated = false;
|
bool isAnimated = false;
|
||||||
bool hasInfo = false;
|
bool hasInfo = false;
|
||||||
|
bool seenFctl = false;
|
||||||
APNGFrame frameRaw = {};
|
APNGFrame frameRaw = {};
|
||||||
uint32_t num_channels;
|
uint32_t num_channels;
|
||||||
JxlPixelFormat format;
|
JxlPixelFormat format;
|
||||||
|
@ -653,6 +654,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||||
while (!r.Eof()) {
|
while (!r.Eof()) {
|
||||||
id = read_chunk(&r, &chunk);
|
id = read_chunk(&r, &chunk);
|
||||||
if (!id) break;
|
if (!id) break;
|
||||||
|
seenFctl |= (id == kId_fcTL);
|
||||||
|
|
||||||
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
||||||
isAnimated = true;
|
isAnimated = true;
|
||||||
|
@ -713,6 +715,10 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||||
}
|
}
|
||||||
} else if (id == kId_IDAT) {
|
} else if (id == kId_IDAT) {
|
||||||
// First IDAT chunk means we now have all header info
|
// First IDAT chunk means we now have all header info
|
||||||
|
if (seenFctl) {
|
||||||
|
// `fcTL` chunk must appear after all `IDAT` chunks
|
||||||
|
return JXL_FAILURE("IDAT chunk after fcTL chunk");
|
||||||
|
}
|
||||||
hasInfo = true;
|
hasInfo = true;
|
||||||
JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
|
JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
|
||||||
JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
|
JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
|
||||||
|
@ -780,6 +786,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (id == kId_fdAT && isAnimated) {
|
} else if (id == kId_fdAT && isAnimated) {
|
||||||
|
if (!hasInfo) {
|
||||||
|
return JXL_FAILURE("fDAT chunk before iDAT");
|
||||||
|
}
|
||||||
png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
|
png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
|
||||||
memcpy(chunk.data() + 8, "IDAT", 4);
|
memcpy(chunk.data() + 8, "IDAT", 4);
|
||||||
if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
|
if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
|
|
||||||
#include <jxl/encode.h>
|
#include <jxl/encode.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "lib/extras/dec/color_description.h"
|
#include "lib/extras/dec/color_description.h"
|
||||||
|
#include "lib/jxl/base/status.h"
|
||||||
|
|
||||||
namespace jxl {
|
namespace jxl {
|
||||||
namespace extras {
|
namespace extras {
|
||||||
|
@ -15,19 +18,15 @@ namespace extras {
|
||||||
Status ApplyColorHints(const ColorHints& color_hints,
|
Status ApplyColorHints(const ColorHints& color_hints,
|
||||||
const bool color_already_set, const bool is_gray,
|
const bool color_already_set, const bool is_gray,
|
||||||
PackedPixelFile* ppf) {
|
PackedPixelFile* ppf) {
|
||||||
if (color_already_set) {
|
bool got_color_space = color_already_set;
|
||||||
return color_hints.Foreach(
|
|
||||||
[](const std::string& key, const std::string& /*value*/) {
|
|
||||||
JXL_WARNING("Decoder ignoring %s hint", key.c_str());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool got_color_space = false;
|
|
||||||
|
|
||||||
JXL_RETURN_IF_ERROR(color_hints.Foreach(
|
JXL_RETURN_IF_ERROR(color_hints.Foreach(
|
||||||
[is_gray, ppf, &got_color_space](const std::string& key,
|
[color_already_set, is_gray, ppf, &got_color_space](
|
||||||
const std::string& value) -> Status {
|
const std::string& key, const std::string& value) -> Status {
|
||||||
|
if (color_already_set && (key == "color_space" || key == "icc")) {
|
||||||
|
JXL_WARNING("Decoder ignoring %s hint", key.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (key == "color_space") {
|
if (key == "color_space") {
|
||||||
JxlColorEncoding c_original_external;
|
JxlColorEncoding c_original_external;
|
||||||
if (!ParseDescription(value, &c_original_external)) {
|
if (!ParseDescription(value, &c_original_external)) {
|
||||||
|
@ -46,6 +45,18 @@ Status ApplyColorHints(const ColorHints& color_hints,
|
||||||
std::vector<uint8_t> icc(data, data + value.size());
|
std::vector<uint8_t> icc(data, data + value.size());
|
||||||
ppf->icc.swap(icc);
|
ppf->icc.swap(icc);
|
||||||
got_color_space = true;
|
got_color_space = true;
|
||||||
|
} else if (key == "exif") {
|
||||||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
|
||||||
|
std::vector<uint8_t> blob(data, data + value.size());
|
||||||
|
ppf->metadata.exif.swap(blob);
|
||||||
|
} else if (key == "xmp") {
|
||||||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
|
||||||
|
std::vector<uint8_t> blob(data, data + value.size());
|
||||||
|
ppf->metadata.xmp.swap(blob);
|
||||||
|
} else if (key == "jumbf") {
|
||||||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
|
||||||
|
std::vector<uint8_t> blob(data, data + value.size());
|
||||||
|
ppf->metadata.jumbf.swap(blob);
|
||||||
} else {
|
} else {
|
||||||
JXL_WARNING("Ignoring %s hint", key.c_str());
|
JXL_WARNING("Ignoring %s hint", key.c_str());
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
// information into the file, and those that support it may not have it.
|
// information into the file, and those that support it may not have it.
|
||||||
// To allow attaching color information to those file formats the caller can
|
// To allow attaching color information to those file formats the caller can
|
||||||
// define these color hints.
|
// define these color hints.
|
||||||
|
// Besides color space information, 'ColorHints' may also include other
|
||||||
|
// additional information such as Exif, XMP and JUMBF metadata.
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
|
@ -120,9 +120,15 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
|
||||||
return Codec::kPNM;
|
return Codec::kPNM;
|
||||||
}
|
}
|
||||||
JXLDecompressParams dparams = {};
|
JXLDecompressParams dparams = {};
|
||||||
|
for (const uint32_t num_channels : {1, 2, 3, 4}) {
|
||||||
|
dparams.accepted_formats.push_back(
|
||||||
|
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
|
||||||
|
}
|
||||||
size_t decoded_bytes;
|
size_t decoded_bytes;
|
||||||
if (DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes,
|
if (DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes,
|
||||||
ppf)) {
|
ppf) &&
|
||||||
|
ApplyColorHints(color_hints, true, ppf->info.num_color_channels == 1,
|
||||||
|
ppf)) {
|
||||||
return Codec::kJXL;
|
return Codec::kJXL;
|
||||||
}
|
}
|
||||||
if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
|
if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
|
||||||
|
|
|
@ -125,12 +125,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||||
|
|
||||||
JxlPixelFormat format;
|
JxlPixelFormat format;
|
||||||
std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
|
std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
|
||||||
if (accepted_formats.empty()) {
|
|
||||||
for (const uint32_t num_channels : {1, 2, 3, 4}) {
|
|
||||||
accepted_formats.push_back(
|
|
||||||
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JxlColorEncoding color_encoding;
|
JxlColorEncoding color_encoding;
|
||||||
size_t num_color_channels = 0;
|
size_t num_color_channels = 0;
|
||||||
if (!dparams.color_space.empty()) {
|
if (!dparams.color_space.empty()) {
|
||||||
|
@ -169,6 +164,10 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||||
} else {
|
} else {
|
||||||
events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
|
events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
|
||||||
JXL_DEC_BOX);
|
JXL_DEC_BOX);
|
||||||
|
if (accepted_formats.empty()) {
|
||||||
|
// decoding just the metadata, not the pixel data
|
||||||
|
events ^= (JXL_DEC_FULL_IMAGE | JXL_DEC_PREVIEW_IMAGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
|
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
|
||||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||||
|
@ -206,7 +205,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t progression_index = 0;
|
uint32_t progression_index = 0;
|
||||||
bool codestream_done = false;
|
bool codestream_done = accepted_formats.empty();
|
||||||
BoxProcessor boxes(dec);
|
BoxProcessor boxes(dec);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
|
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
|
||||||
|
@ -285,6 +284,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||||
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (accepted_formats.empty()) continue;
|
||||||
if (num_color_channels != 0) {
|
if (num_color_channels != 0) {
|
||||||
// Mark the change in number of color channels due to the requested
|
// Mark the change in number of color channels due to the requested
|
||||||
// color space.
|
// color space.
|
||||||
|
|
|
@ -191,9 +191,9 @@ void MaybeAddCICP(const JxlColorEncoding& c_enc, png_structp png_ptr,
|
||||||
cicp_data[3] = 1;
|
cicp_data[3] = 1;
|
||||||
cicp_chunk.data = cicp_data;
|
cicp_chunk.data = cicp_data;
|
||||||
cicp_chunk.size = sizeof(cicp_data);
|
cicp_chunk.size = sizeof(cicp_data);
|
||||||
cicp_chunk.location = PNG_HAVE_PLTE;
|
cicp_chunk.location = PNG_HAVE_IHDR;
|
||||||
memcpy(cicp_chunk.name, "cICP", 5);
|
memcpy(cicp_chunk.name, "cICP", 5);
|
||||||
png_set_keep_unknown_chunks(png_ptr, 3,
|
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS,
|
||||||
reinterpret_cast<const png_byte*>("cICP"), 1);
|
reinterpret_cast<const png_byte*>("cICP"), 1);
|
||||||
png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
|
png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
|
||||||
}
|
}
|
||||||
|
@ -243,6 +243,28 @@ void MaybeAddGAMA(const JxlColorEncoding& c_enc, png_structp png_ptr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MaybeAddCLLi(const JxlColorEncoding& c_enc, const float intensity_target,
|
||||||
|
png_structp png_ptr, png_infop info_ptr) {
|
||||||
|
if (c_enc.transfer_function != JXL_TRANSFER_FUNCTION_PQ) return;
|
||||||
|
|
||||||
|
const uint32_t max_cll =
|
||||||
|
static_cast<uint32_t>(10000.f * Clamp1(intensity_target, 0.f, 10000.f));
|
||||||
|
png_byte chunk_data[8] = {};
|
||||||
|
chunk_data[0] = (max_cll >> 24) & 0xFF;
|
||||||
|
chunk_data[1] = (max_cll >> 16) & 0xFF;
|
||||||
|
chunk_data[2] = (max_cll >> 8) & 0xFF;
|
||||||
|
chunk_data[3] = max_cll & 0xFF;
|
||||||
|
// Leave MaxFALL set to 0.
|
||||||
|
png_unknown_chunk chunk;
|
||||||
|
memcpy(chunk.name, "cLLi", 5);
|
||||||
|
chunk.data = chunk_data;
|
||||||
|
chunk.size = sizeof chunk_data;
|
||||||
|
chunk.location = PNG_HAVE_IHDR;
|
||||||
|
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS,
|
||||||
|
reinterpret_cast<const png_byte*>("cLLi"), 1);
|
||||||
|
png_set_unknown_chunks(png_ptr, info_ptr, &chunk, 1);
|
||||||
|
}
|
||||||
|
|
||||||
Status APNGEncoder::EncodePackedPixelFileToAPNG(
|
Status APNGEncoder::EncodePackedPixelFileToAPNG(
|
||||||
const PackedPixelFile& ppf, ThreadPool* pool,
|
const PackedPixelFile& ppf, ThreadPool* pool,
|
||||||
std::vector<uint8_t>* bytes) const {
|
std::vector<uint8_t>* bytes) const {
|
||||||
|
@ -332,6 +354,8 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
|
||||||
MaybeAddCHRM(ppf.color_encoding, png_ptr, info_ptr);
|
MaybeAddCHRM(ppf.color_encoding, png_ptr, info_ptr);
|
||||||
MaybeAddGAMA(ppf.color_encoding, png_ptr, info_ptr);
|
MaybeAddGAMA(ppf.color_encoding, png_ptr, info_ptr);
|
||||||
}
|
}
|
||||||
|
MaybeAddCLLi(ppf.color_encoding, ppf.info.intensity_target, png_ptr,
|
||||||
|
info_ptr);
|
||||||
|
|
||||||
std::vector<std::string> textstrings;
|
std::vector<std::string> textstrings;
|
||||||
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings));
|
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings));
|
||||||
|
|
|
@ -129,6 +129,27 @@ Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <int metadata>
|
||||||
|
class MetadataEncoder : public Encoder {
|
||||||
|
public:
|
||||||
|
std::vector<JxlPixelFormat> AcceptedFormats() const override {
|
||||||
|
std::vector<JxlPixelFormat> formats;
|
||||||
|
// empty, i.e. no need for actual pixel data
|
||||||
|
return formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded,
|
||||||
|
ThreadPool* pool) const override {
|
||||||
|
JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
|
||||||
|
encoded->icc.clear();
|
||||||
|
encoded->bitstreams.resize(1);
|
||||||
|
if (metadata == 0) encoded->bitstreams.front() = ppf.metadata.exif;
|
||||||
|
if (metadata == 1) encoded->bitstreams.front() = ppf.metadata.xmp;
|
||||||
|
if (metadata == 2) encoded->bitstreams.front() = ppf.metadata.jumbf;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
|
std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
|
||||||
std::transform(
|
std::transform(
|
||||||
extension.begin(), extension.end(), extension.begin(),
|
extension.begin(), extension.end(), extension.begin(),
|
||||||
|
@ -143,6 +164,12 @@ std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
|
||||||
if (extension == ".ppm") return GetPPMEncoder();
|
if (extension == ".ppm") return GetPPMEncoder();
|
||||||
if (extension == ".pfm") return GetPFMEncoder();
|
if (extension == ".pfm") return GetPFMEncoder();
|
||||||
if (extension == ".exr") return GetEXREncoder();
|
if (extension == ".exr") return GetEXREncoder();
|
||||||
|
if (extension == ".exif") return jxl::make_unique<MetadataEncoder<0>>();
|
||||||
|
if (extension == ".xmp") return jxl::make_unique<MetadataEncoder<1>>();
|
||||||
|
if (extension == ".xml") return jxl::make_unique<MetadataEncoder<1>>();
|
||||||
|
if (extension == ".jumbf") return jxl::make_unique<MetadataEncoder<2>>();
|
||||||
|
if (extension == ".jumb") return jxl::make_unique<MetadataEncoder<2>>();
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,8 @@ class Encoder {
|
||||||
|
|
||||||
virtual ~Encoder() = default;
|
virtual ~Encoder() = default;
|
||||||
|
|
||||||
|
// Set of pixel formats that this encoder takes as input.
|
||||||
|
// If empty, the 'encoder' does not need any pixels (it's metadata-only).
|
||||||
virtual std::vector<JxlPixelFormat> AcceptedFormats() const = 0;
|
virtual std::vector<JxlPixelFormat> AcceptedFormats() const = 0;
|
||||||
|
|
||||||
// Any existing data in encoded_image is discarded.
|
// Any existing data in encoded_image is discarded.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "lib/extras/enc/jxl.h"
|
#include "lib/extras/enc/jxl.h"
|
||||||
|
|
||||||
|
#include <jxl/encode.h>
|
||||||
#include <jxl/encode_cxx.h>
|
#include <jxl/encode_cxx.h>
|
||||||
|
|
||||||
#include "lib/jxl/exif.h"
|
#include "lib/jxl/exif.h"
|
||||||
|
@ -87,9 +88,35 @@ bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
|
||||||
fprintf(stderr, "Storing JPEG metadata failed.\n");
|
fprintf(stderr, "Storing JPEG metadata failed.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!params.jpeg_store_metadata && params.jpeg_strip_exif) {
|
||||||
|
JxlEncoderFrameSettingsSetOption(settings,
|
||||||
|
JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, 0);
|
||||||
|
}
|
||||||
|
if (!params.jpeg_store_metadata && params.jpeg_strip_xmp) {
|
||||||
|
JxlEncoderFrameSettingsSetOption(settings,
|
||||||
|
JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, 0);
|
||||||
|
}
|
||||||
|
if (params.jpeg_strip_jumbf) {
|
||||||
|
JxlEncoderFrameSettingsSetOption(
|
||||||
|
settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF, 0);
|
||||||
|
}
|
||||||
if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(),
|
if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(),
|
||||||
jpeg_bytes->size())) {
|
jpeg_bytes->size())) {
|
||||||
fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n");
|
JxlEncoderError error = JxlEncoderGetError(enc);
|
||||||
|
if (error == JXL_ENC_ERR_BAD_INPUT) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error while decoding the JPEG image. It may be corrupt (e.g. "
|
||||||
|
"truncated) or of an unsupported type (e.g. CMYK).\n");
|
||||||
|
} else if (error == JXL_ENC_ERR_JBRD) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"JPEG bitstream reconstruction data could not be created. "
|
||||||
|
"Possibly there is too much tail data.\n"
|
||||||
|
"Try using --jpeg_store_metadata 0, to losslessly "
|
||||||
|
"recompress the JPEG image data without bitstream "
|
||||||
|
"reconstruction data.\n");
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,6 +147,12 @@ bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
|
||||||
fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n");
|
fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (JXL_ENC_SUCCESS !=
|
||||||
|
JxlEncoderSetUpsamplingMode(enc, params.already_downsampled,
|
||||||
|
params.upsampling_mode)) {
|
||||||
|
fprintf(stderr, "JxlEncoderSetUpsamplingMode() failed.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (JXL_ENC_SUCCESS !=
|
if (JXL_ENC_SUCCESS !=
|
||||||
JxlEncoderSetFrameBitDepth(settings, ¶ms.input_bitdepth)) {
|
JxlEncoderSetFrameBitDepth(settings, ¶ms.input_bitdepth)) {
|
||||||
fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n");
|
fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n");
|
||||||
|
|
|
@ -43,12 +43,16 @@ struct JXLCompressParams {
|
||||||
bool use_container = false;
|
bool use_container = false;
|
||||||
// Whether to enable/disable byte-exact jpeg reconstruction for jpeg inputs.
|
// Whether to enable/disable byte-exact jpeg reconstruction for jpeg inputs.
|
||||||
bool jpeg_store_metadata = true;
|
bool jpeg_store_metadata = true;
|
||||||
|
bool jpeg_strip_exif = false;
|
||||||
|
bool jpeg_strip_xmp = false;
|
||||||
|
bool jpeg_strip_jumbf = false;
|
||||||
// Whether to create brob boxes.
|
// Whether to create brob boxes.
|
||||||
bool compress_boxes = true;
|
bool compress_boxes = true;
|
||||||
// Upper bound on the intensity level present in the image in nits (zero means
|
// Upper bound on the intensity level present in the image in nits (zero means
|
||||||
// that the library chooses a default).
|
// that the library chooses a default).
|
||||||
float intensity_target = 0;
|
float intensity_target = 0;
|
||||||
int already_downsampled = 1;
|
int already_downsampled = 1;
|
||||||
|
int upsampling_mode = -1;
|
||||||
// Overrides for bitdepth, codestream level and alpha premultiply.
|
// Overrides for bitdepth, codestream level and alpha premultiply.
|
||||||
size_t override_bitdepth = 0;
|
size_t override_bitdepth = 0;
|
||||||
int32_t codestream_level = -1;
|
int32_t codestream_level = -1;
|
||||||
|
|
|
@ -23,7 +23,7 @@ void ResetExifOrientation(std::vector<uint8_t>& exif) {
|
||||||
return; // not a valid tiff header
|
return; // not a valid tiff header
|
||||||
}
|
}
|
||||||
t += 4;
|
t += 4;
|
||||||
uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
|
uint64_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
|
||||||
if (exif.size() < 12 + offset + 2 || offset < 8) return;
|
if (exif.size() < 12 + offset + 2 || offset < 8) return;
|
||||||
t += offset - 4;
|
t += offset - 4;
|
||||||
uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
|
uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
|
||||||
|
|
|
@ -349,6 +349,30 @@ typedef enum {
|
||||||
*/
|
*/
|
||||||
JXL_ENC_FRAME_SETTING_BUFFERING = 34,
|
JXL_ENC_FRAME_SETTING_BUFFERING = 34,
|
||||||
|
|
||||||
|
/** Keep or discard Exif metadata boxes derived from a JPEG frame when using
|
||||||
|
* JxlEncoderAddJPEGFrame. This has no effect on boxes added using
|
||||||
|
* JxlEncoderAddBox. When JxlEncoderStoreJPEGMetadata is set to 1, this option
|
||||||
|
* cannot be set to 0. Even when Exif metadata is discarded, the orientation
|
||||||
|
* will still be applied. 0 = discard Exif metadata, 1 = keep Exif metadata
|
||||||
|
* (default).
|
||||||
|
*/
|
||||||
|
JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF = 35,
|
||||||
|
|
||||||
|
/** Keep or discard XMP metadata boxes derived from a JPEG frame when using
|
||||||
|
* JxlEncoderAddJPEGFrame. This has no effect on boxes added using
|
||||||
|
* JxlEncoderAddBox. When JxlEncoderStoreJPEGMetadata is set to 1, this option
|
||||||
|
* cannot be set to 0. 0 = discard XMP metadata, 1 = keep XMP metadata
|
||||||
|
* (default).
|
||||||
|
*/
|
||||||
|
JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP = 36,
|
||||||
|
|
||||||
|
/** Keep or discard JUMBF metadata boxes derived from a JPEG frame when using
|
||||||
|
* JxlEncoderAddJPEGFrame. This has no effect on boxes added using
|
||||||
|
* JxlEncoderAddBox. 0 = discard JUMBF metadata, 1 = keep JUMBF metadata
|
||||||
|
* (default).
|
||||||
|
*/
|
||||||
|
JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF = 37,
|
||||||
|
|
||||||
/** Enum value not to be used as an option. This value is added to force the
|
/** Enum value not to be used as an option. This value is added to force the
|
||||||
* C compiler to have the enum to take a known size.
|
* C compiler to have the enum to take a known size.
|
||||||
*/
|
*/
|
||||||
|
@ -932,6 +956,26 @@ JXL_EXPORT void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info);
|
||||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
|
JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
|
||||||
const JxlBasicInfo* info);
|
const JxlBasicInfo* info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the upsampling method the decoder will use in case there are frames
|
||||||
|
* with JXL_ENC_FRAME_SETTING_RESAMPLING set. This is useful in combination
|
||||||
|
* with the JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED option, to control the
|
||||||
|
* type of upsampling that will be used.
|
||||||
|
*
|
||||||
|
* @param enc encoder object.
|
||||||
|
* @param factor upsampling factor to configure (1, 2, 4 or 8; for 1 this
|
||||||
|
* function has no effect at all)
|
||||||
|
* @param mode upsampling mode to use for this upsampling:
|
||||||
|
* -1: default (good for photographic images, no signaling overhead)
|
||||||
|
* 0: nearest neighbor (good for pixel art)
|
||||||
|
* 1: 'pixel dots' (same as NN for 2x, diamond-shaped 'pixel dots' for 4x/8x)
|
||||||
|
* @return JXL_ENC_SUCCESS if the operation was successful,
|
||||||
|
* JXL_ENC_ERROR or JXL_ENC_NOT_SUPPORTED otherwise
|
||||||
|
*/
|
||||||
|
JXL_EXPORT JxlEncoderStatus JxlEncoderSetUpsamplingMode(JxlEncoder* enc,
|
||||||
|
const int64_t factor,
|
||||||
|
const int64_t mode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a JxlExtraChannelInfo struct to default values.
|
* Initializes a JxlExtraChannelInfo struct to default values.
|
||||||
* For forwards-compatibility, this function has to be called before values
|
* For forwards-compatibility, this function has to be called before values
|
||||||
|
|
|
@ -305,8 +305,8 @@ class ANSSymbolReader {
|
||||||
|
|
||||||
// Takes a *clustered* idx. Inlined, for use in hot paths.
|
// Takes a *clustered* idx. Inlined, for use in hot paths.
|
||||||
template <bool uses_lz77>
|
template <bool uses_lz77>
|
||||||
JXL_INLINE size_t ReadHybridUintClustered(size_t ctx,
|
JXL_INLINE size_t ReadHybridUintClusteredInlined(size_t ctx,
|
||||||
BitReader* JXL_RESTRICT br) {
|
BitReader* JXL_RESTRICT br) {
|
||||||
if (uses_lz77) {
|
if (uses_lz77) {
|
||||||
if (JXL_UNLIKELY(num_to_copy_ > 0)) {
|
if (JXL_UNLIKELY(num_to_copy_ > 0)) {
|
||||||
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
|
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
|
||||||
|
@ -363,6 +363,23 @@ class ANSSymbolReader {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same but not inlined
|
||||||
|
template <bool uses_lz77>
|
||||||
|
size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) {
|
||||||
|
return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inlined only in the no-lz77 case
|
||||||
|
template <bool uses_lz77>
|
||||||
|
JXL_INLINE size_t
|
||||||
|
ReadHybridUintClusteredMaybeInlined(size_t ctx, BitReader* JXL_RESTRICT br) {
|
||||||
|
if (uses_lz77) {
|
||||||
|
return ReadHybridUintClustered<uses_lz77>(ctx, br);
|
||||||
|
} else {
|
||||||
|
return ReadHybridUintClusteredInlined<uses_lz77>(ctx, br);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// inlined, for use in hot paths
|
// inlined, for use in hot paths
|
||||||
template <bool uses_lz77>
|
template <bool uses_lz77>
|
||||||
JXL_INLINE size_t
|
JXL_INLINE size_t
|
||||||
|
|
|
@ -63,15 +63,14 @@ class Rec2408ToneMapper {
|
||||||
Min(Set(df_, target_range_.second),
|
Min(Set(df_, target_range_.second),
|
||||||
ZeroIfNegative(
|
ZeroIfNegative(
|
||||||
Mul(Set(df_, 10000), TF_PQ().DisplayFromEncoded(df_, e4))));
|
Mul(Set(df_, 10000), TF_PQ().DisplayFromEncoded(df_, e4))));
|
||||||
|
const V min_luminance = Set(df_, 1e-6f);
|
||||||
const V ratio = Div(new_luminance, luminance);
|
const auto use_cap = Le(luminance, min_luminance);
|
||||||
const V inv_target_peak = Set(df_, inv_target_peak_);
|
const V ratio = Div(new_luminance, Max(luminance, min_luminance));
|
||||||
|
const V cap = Mul(new_luminance, Set(df_, inv_target_peak_));
|
||||||
const V normalizer = Set(df_, normalizer_);
|
const V normalizer = Set(df_, normalizer_);
|
||||||
const V multiplier = Mul(ratio, normalizer);
|
const V multiplier = Mul(ratio, normalizer);
|
||||||
for (V* const val : {red, green, blue}) {
|
for (V* const val : {red, green, blue}) {
|
||||||
*val = IfThenElse(Le(luminance, Set(df_, 1e-6f)),
|
*val = IfThenElse(use_cap, cap, Mul(*val, multiplier));
|
||||||
Mul(new_luminance, inv_target_peak),
|
|
||||||
Mul(*val, multiplier));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,30 +196,37 @@ void GamutMap(V* red, V* green, V* blue, const float primaries_luminances[3],
|
||||||
// That will reduce its luminance.
|
// That will reduce its luminance.
|
||||||
// - For luminance preservation, getting all components below 1 is
|
// - For luminance preservation, getting all components below 1 is
|
||||||
// done by mixing in yet more gray. That will desaturate it further.
|
// done by mixing in yet more gray. That will desaturate it further.
|
||||||
V gray_mix_saturation = Zero(df);
|
const V zero = Zero(df);
|
||||||
V gray_mix_luminance = Zero(df);
|
const V one = Set(df, 1);
|
||||||
|
V gray_mix_saturation = zero;
|
||||||
|
V gray_mix_luminance = zero;
|
||||||
for (const V* ch : {red, green, blue}) {
|
for (const V* ch : {red, green, blue}) {
|
||||||
const V& val = *ch;
|
const V& val = *ch;
|
||||||
const V inv_val_minus_gray = Div(Set(df, 1), (Sub(val, luminance)));
|
const V val_minus_gray = Sub(val, luminance);
|
||||||
|
const V inv_val_minus_gray =
|
||||||
|
Div(one, IfThenElse(Eq(val_minus_gray, zero), one, val_minus_gray));
|
||||||
|
const V val_over_val_minus_gray = Mul(val, inv_val_minus_gray);
|
||||||
gray_mix_saturation =
|
gray_mix_saturation =
|
||||||
IfThenElse(Ge(val, luminance), gray_mix_saturation,
|
IfThenElse(Ge(val_minus_gray, zero), gray_mix_saturation,
|
||||||
Max(gray_mix_saturation, Mul(val, inv_val_minus_gray)));
|
Max(gray_mix_saturation, val_over_val_minus_gray));
|
||||||
gray_mix_luminance =
|
gray_mix_luminance =
|
||||||
Max(gray_mix_luminance,
|
Max(gray_mix_luminance,
|
||||||
IfThenElse(Le(val, luminance), gray_mix_saturation,
|
IfThenElse(Le(val_minus_gray, zero), gray_mix_saturation,
|
||||||
Mul(Sub(val, Set(df, 1)), inv_val_minus_gray)));
|
Sub(val_over_val_minus_gray, inv_val_minus_gray)));
|
||||||
}
|
}
|
||||||
const V gray_mix = Clamp(
|
const V gray_mix = Clamp(
|
||||||
MulAdd(Set(df, preserve_saturation),
|
MulAdd(Set(df, preserve_saturation),
|
||||||
Sub(gray_mix_saturation, gray_mix_luminance), gray_mix_luminance),
|
Sub(gray_mix_saturation, gray_mix_luminance), gray_mix_luminance),
|
||||||
Zero(df), Set(df, 1));
|
zero, one);
|
||||||
for (V* const val : {red, green, blue}) {
|
for (V* const ch : {red, green, blue}) {
|
||||||
*val = MulAdd(gray_mix, Sub(luminance, *val), *val);
|
V& val = *ch;
|
||||||
|
val = MulAdd(gray_mix, Sub(luminance, val), val);
|
||||||
}
|
}
|
||||||
const V normalizer =
|
const V max_clr = Max(Max(one, *red), Max(*green, *blue));
|
||||||
Div(Set(df, 1), Max(Set(df, 1), Max(*red, Max(*green, *blue))));
|
const V normalizer = Div(one, max_clr);
|
||||||
for (V* const val : {red, green, blue}) {
|
for (V* const ch : {red, green, blue}) {
|
||||||
*val = Mul(*val, normalizer);
|
V& val = *ch;
|
||||||
|
val = Mul(val, normalizer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1666,9 +1666,9 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
|
||||||
EXPECT_THAT(ButteraugliDistance(io0.frames, io1.frames, ba, jxl::GetJxlCms(),
|
EXPECT_THAT(ButteraugliDistance(io0.frames, io1.frames, ba, jxl::GetJxlCms(),
|
||||||
/*distmap=*/nullptr, nullptr),
|
/*distmap=*/nullptr, nullptr),
|
||||||
#if JXL_HIGH_PRECISION
|
#if JXL_HIGH_PRECISION
|
||||||
IsSlightlyBelow(0.70f));
|
IsSlightlyBelow(0.9f));
|
||||||
#else
|
#else
|
||||||
IsSlightlyBelow(0.78f));
|
IsSlightlyBelow(0.98f));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JxlDecoderDestroy(dec);
|
JxlDecoderDestroy(dec);
|
||||||
|
@ -1928,9 +1928,9 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
|
||||||
ButteraugliDistance(io0.frames, io1.frames, ba, jxl::GetJxlCms(),
|
ButteraugliDistance(io0.frames, io1.frames, ba, jxl::GetJxlCms(),
|
||||||
/*distmap=*/nullptr, nullptr),
|
/*distmap=*/nullptr, nullptr),
|
||||||
#if JXL_HIGH_PRECISION
|
#if JXL_HIGH_PRECISION
|
||||||
IsSlightlyBelow(0.74f));
|
IsSlightlyBelow(0.93f));
|
||||||
#else
|
#else
|
||||||
IsSlightlyBelow(0.75f));
|
IsSlightlyBelow(0.94f));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JxlDecoderDestroy(dec);
|
JxlDecoderDestroy(dec);
|
||||||
|
|
|
@ -378,6 +378,41 @@ static const float kChromaErrorWeight[AcStrategy::kNumValidStrategies] = {
|
||||||
2.0f, // DCT128X256 = 26,
|
2.0f, // DCT128X256 = 26,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For DCT the maximum error is roughly a sum of the values.
|
||||||
|
// For some transforms, especially IDENTITY and DCT2X2, not all
|
||||||
|
// the coefficients affect the maximum error. Probably would
|
||||||
|
// be better to do transforms back and forth and look at the pixels
|
||||||
|
// but that would significantly slow down the computation.
|
||||||
|
static const float kMixLossTable[AcStrategy::kNumValidStrategies] = {
|
||||||
|
1.0f, // DCT = 0,
|
||||||
|
0.45f, // IDENTITY = 1,
|
||||||
|
0.45f, // DCT2X2 = 2,
|
||||||
|
0.7f, // DCT4X4 = 3,
|
||||||
|
1.0f, // DCT16X16 = 4,
|
||||||
|
1.0f, // DCT32X32 = 5,
|
||||||
|
1.0f, // DCT16X8 = 6,
|
||||||
|
1.0f, // DCT8X16 = 7,
|
||||||
|
1.0f, // DCT32X8 = 8,
|
||||||
|
1.0f, // DCT8X32 = 9,
|
||||||
|
1.0f, // DCT32X16 = 10,
|
||||||
|
1.0f, // DCT16X32 = 11,
|
||||||
|
0.96f, // DCT4X8 = 12,
|
||||||
|
0.96f, // DCT8X4 = 13,
|
||||||
|
0.94f, // AFV0 = 14,
|
||||||
|
0.94f, // AFV1 = 15,
|
||||||
|
0.94f, // AFV2 = 16,
|
||||||
|
0.94f, // AFV3 = 17,
|
||||||
|
1.0f, // DCT64X64 = 18,
|
||||||
|
1.0f, // DCT64X32 = 19,
|
||||||
|
1.0f, // DCT32X64 = 20,
|
||||||
|
1.0f, // DCT128X128 = 21,
|
||||||
|
1.0f, // DCT128X64 = 22,
|
||||||
|
1.0f, // DCT64X128 = 23,
|
||||||
|
1.0f, // DCT256X256 = 24,
|
||||||
|
1.0f, // DCT256X128 = 25,
|
||||||
|
1.0f, // DCT128X256 = 26,
|
||||||
|
};
|
||||||
|
|
||||||
float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
const ACSConfig& config,
|
const ACSConfig& config,
|
||||||
const float* JXL_RESTRICT cmap_factors, float* block,
|
const float* JXL_RESTRICT cmap_factors, float* block,
|
||||||
|
@ -390,7 +425,6 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
TransformFromPixels(acs.Strategy(), &config.Pixel(c, x, y),
|
TransformFromPixels(acs.Strategy(), &config.Pixel(c, x, y),
|
||||||
config.src_stride, block_c, scratch_space);
|
config.src_stride, block_c, scratch_space);
|
||||||
}
|
}
|
||||||
|
|
||||||
HWY_FULL(float) df;
|
HWY_FULL(float) df;
|
||||||
|
|
||||||
const size_t num_blocks = acs.covered_blocks_x() * acs.covered_blocks_y();
|
const size_t num_blocks = acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||||
|
@ -401,7 +435,12 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
if (num_blocks == 1) {
|
if (num_blocks == 1) {
|
||||||
// When it is only one 8x8, we don't need aggregation of values.
|
// When it is only one 8x8, we don't need aggregation of values.
|
||||||
quant_norm8 = config.Quant(x / 8, y / 8);
|
quant_norm8 = config.Quant(x / 8, y / 8);
|
||||||
masking = 2.0f * config.Masking(x / 8, y / 8);
|
masking = config.Masking(x / 8, y / 8);
|
||||||
|
// Make DCT2X2 more favored when area is exposed.
|
||||||
|
float kExposedMasking = 0.118f;
|
||||||
|
if (acs.RawStrategy() == 2 && masking >= kExposedMasking) {
|
||||||
|
masking = kExposedMasking + 0.56 * (masking - kExposedMasking);
|
||||||
|
}
|
||||||
} else if (num_blocks == 2) {
|
} else if (num_blocks == 2) {
|
||||||
// Taking max instead of 8th norm seems to work
|
// Taking max instead of 8th norm seems to work
|
||||||
// better for smallest blocks up to 16x8. Jyrki couldn't get
|
// better for smallest blocks up to 16x8. Jyrki couldn't get
|
||||||
|
@ -409,13 +448,13 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
if (acs.covered_blocks_y() == 2) {
|
if (acs.covered_blocks_y() == 2) {
|
||||||
quant_norm8 =
|
quant_norm8 =
|
||||||
std::max(config.Quant(x / 8, y / 8), config.Quant(x / 8, y / 8 + 1));
|
std::max(config.Quant(x / 8, y / 8), config.Quant(x / 8, y / 8 + 1));
|
||||||
masking = 2.0f * std::max(config.Masking(x / 8, y / 8),
|
masking = std::max(config.Masking(x / 8, y / 8),
|
||||||
config.Masking(x / 8, y / 8 + 1));
|
config.Masking(x / 8, y / 8 + 1));
|
||||||
} else {
|
} else {
|
||||||
quant_norm8 =
|
quant_norm8 =
|
||||||
std::max(config.Quant(x / 8, y / 8), config.Quant(x / 8 + 1, y / 8));
|
std::max(config.Quant(x / 8, y / 8), config.Quant(x / 8 + 1, y / 8));
|
||||||
masking = 2.0f * std::max(config.Masking(x / 8, y / 8),
|
masking = std::max(config.Masking(x / 8, y / 8),
|
||||||
config.Masking(x / 8 + 1, y / 8));
|
config.Masking(x / 8 + 1, y / 8));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
float masking_norm2 = 0;
|
float masking_norm2 = 0;
|
||||||
|
@ -438,12 +477,12 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
quant_norm8 = FastPowf(quant_norm8, 1.0f / 8.0f);
|
quant_norm8 = FastPowf(quant_norm8, 1.0f / 8.0f);
|
||||||
masking_norm2 = sqrt(masking_norm2 / num_blocks);
|
masking_norm2 = sqrt(masking_norm2 / num_blocks);
|
||||||
// This is a highly empirical formula.
|
// This is a highly empirical formula.
|
||||||
masking = (masking_norm2 + masking_max);
|
masking = 0.5 * (masking_norm2 + masking_max);
|
||||||
}
|
}
|
||||||
const auto q = Set(df, quant_norm8);
|
const auto q = Set(df, quant_norm8);
|
||||||
|
|
||||||
// Compute entropy.
|
// Compute entropy.
|
||||||
float entropy = config.base_entropy;
|
float entropy = 0.0f;
|
||||||
auto info_loss = Zero(df);
|
auto info_loss = Zero(df);
|
||||||
auto info_loss2 = Zero(df);
|
auto info_loss2 = Zero(df);
|
||||||
|
|
||||||
|
@ -453,9 +492,6 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
|
|
||||||
auto entropy_v = Zero(df);
|
auto entropy_v = Zero(df);
|
||||||
auto nzeros_v = Zero(df);
|
auto nzeros_v = Zero(df);
|
||||||
auto cost1 = Set(df, config.cost1);
|
|
||||||
auto cost2 = Set(df, config.cost2);
|
|
||||||
auto cost_delta = Set(df, config.cost_delta);
|
|
||||||
for (size_t i = 0; i < num_blocks * kDCTBlockSize; i += Lanes(df)) {
|
for (size_t i = 0; i < num_blocks * kDCTBlockSize; i += Lanes(df)) {
|
||||||
const auto in = Load(df, block + c * size + i);
|
const auto in = Load(df, block + c * size + i);
|
||||||
const auto in_y = Mul(Load(df, block + size + i), cmap_factor);
|
const auto in_y = Mul(Load(df, block + size + i), cmap_factor);
|
||||||
|
@ -467,18 +503,13 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
info_loss2 = MulAdd(diff, diff, info_loss2);
|
info_loss2 = MulAdd(diff, diff, info_loss2);
|
||||||
const auto q = Abs(rval);
|
const auto q = Abs(rval);
|
||||||
const auto q_is_zero = Eq(q, Zero(df));
|
const auto q_is_zero = Eq(q, Zero(df));
|
||||||
entropy_v = Add(entropy_v, IfThenElseZero(Ge(q, Set(df, 1.5f)), cost2));
|
|
||||||
// We used to have q * C here, but that cost model seems to
|
// We used to have q * C here, but that cost model seems to
|
||||||
// be punishing large values more than necessary. Sqrt tries
|
// be punishing large values more than necessary. Sqrt tries
|
||||||
// to avoid large values less aggressively. Having high accuracy
|
// to avoid large values less aggressively.
|
||||||
// around zero is most important at low qualities, and there
|
entropy_v = Add(Sqrt(q), entropy_v);
|
||||||
// we have directly specified costs for 0, 1, and 2.
|
|
||||||
entropy_v = MulAdd(Sqrt(q), cost_delta, entropy_v);
|
|
||||||
nzeros_v = Add(nzeros_v, IfThenZeroElse(q_is_zero, Set(df, 1.0f)));
|
nzeros_v = Add(nzeros_v, IfThenZeroElse(q_is_zero, Set(df, 1.0f)));
|
||||||
}
|
}
|
||||||
entropy_v = MulAdd(nzeros_v, cost1, entropy_v);
|
entropy += config.cost_delta * cmul[c] * GetLane(SumOfLanes(df, entropy_v));
|
||||||
|
|
||||||
entropy += cmul[c] * GetLane(SumOfLanes(df, entropy_v));
|
|
||||||
size_t num_nzeros = GetLane(SumOfLanes(df, nzeros_v));
|
size_t num_nzeros = GetLane(SumOfLanes(df, nzeros_v));
|
||||||
// Add #bit of num_nonzeros, as an estimate of the cost for encoding the
|
// Add #bit of num_nonzeros, as an estimate of the cost for encoding the
|
||||||
// number of non-zeros of the block.
|
// number of non-zeros of the block.
|
||||||
|
@ -487,13 +518,16 @@ float EstimateEntropy(const AcStrategy& acs, size_t x, size_t y,
|
||||||
// bias.
|
// bias.
|
||||||
entropy += config.zeros_mul * (CeilLog2Nonzero(nbits + 17) + nbits);
|
entropy += config.zeros_mul * (CeilLog2Nonzero(nbits + 17) + nbits);
|
||||||
}
|
}
|
||||||
float ret =
|
const float kMixLoss = kMixLossTable[acs.RawStrategy()];
|
||||||
entropy +
|
const float loss1 = GetLane(SumOfLanes(df, info_loss));
|
||||||
masking *
|
const float loss2 =
|
||||||
((config.info_loss_multiplier * GetLane(SumOfLanes(df, info_loss))) +
|
sqrt(GetLane(SumOfLanes(df, info_loss2)) * (num_blocks * 64));
|
||||||
(config.info_loss_multiplier2 *
|
const float loss = kMixLoss * (config.info_loss_multiplier * loss1) +
|
||||||
sqrt(num_blocks * GetLane(SumOfLanes(df, info_loss2)))));
|
(1.0 - kMixLoss) * (config.info_loss_multiplier2 * loss2);
|
||||||
return ret;
|
const float kRegulateSurface = 11.5f;
|
||||||
|
float large_surface_error_mul =
|
||||||
|
(kRegulateSurface + sqrt(num_blocks)) * (1.0f / (kRegulateSurface + 1));
|
||||||
|
return entropy + large_surface_error_mul * masking * loss;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t FindBest8x8Transform(size_t x, size_t y, int encoding_speed_tier,
|
uint8_t FindBest8x8Transform(size_t x, size_t y, int encoding_speed_tier,
|
||||||
|
@ -513,7 +547,7 @@ uint8_t FindBest8x8Transform(size_t x, size_t y, int encoding_speed_tier,
|
||||||
AcStrategy::Type::DCT,
|
AcStrategy::Type::DCT,
|
||||||
9,
|
9,
|
||||||
3.0f,
|
3.0f,
|
||||||
0.745f,
|
0.785f,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
AcStrategy::Type::DCT4X4,
|
AcStrategy::Type::DCT4X4,
|
||||||
|
@ -525,19 +559,19 @@ uint8_t FindBest8x8Transform(size_t x, size_t y, int encoding_speed_tier,
|
||||||
AcStrategy::Type::DCT2X2,
|
AcStrategy::Type::DCT2X2,
|
||||||
5,
|
5,
|
||||||
0.0f,
|
0.0f,
|
||||||
0.66f,
|
0.685f,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
AcStrategy::Type::DCT4X8,
|
AcStrategy::Type::DCT4X8,
|
||||||
4,
|
4,
|
||||||
0.0f,
|
3.0f,
|
||||||
0.700754622182473063f,
|
0.745f,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
AcStrategy::Type::DCT8X4,
|
AcStrategy::Type::DCT8X4,
|
||||||
4,
|
4,
|
||||||
0.0f,
|
3.0f,
|
||||||
0.700754622182473063f,
|
0.745f,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
AcStrategy::Type::IDENTITY,
|
AcStrategy::Type::IDENTITY,
|
||||||
|
@ -871,14 +905,14 @@ void ProcessRectACS(PassesEncoderState* JXL_RESTRICT enc_state,
|
||||||
float entropy_mul;
|
float entropy_mul;
|
||||||
};
|
};
|
||||||
static const float k8X16mul1 = -0.55;
|
static const float k8X16mul1 = -0.55;
|
||||||
static const float k8X16mul2 = 0.865;
|
static const float k8X16mul2 = 0.885;
|
||||||
static const float k8X16base = 1.6;
|
static const float k8X16base = 1.6;
|
||||||
const float entropy_mul16X8 =
|
const float entropy_mul16X8 =
|
||||||
k8X16mul2 + k8X16mul1 / (butteraugli_target + k8X16base);
|
k8X16mul2 + k8X16mul1 / (butteraugli_target + k8X16base);
|
||||||
// const float entropy_mul16X8 = mul8X16 * 0.91195782912371126f;
|
// const float entropy_mul16X8 = mul8X16 * 0.91195782912371126f;
|
||||||
|
|
||||||
static const float k16X16mul1 = -0.35;
|
static const float k16X16mul1 = -0.35;
|
||||||
static const float k16X16mul2 = 0.798;
|
static const float k16X16mul2 = 0.808;
|
||||||
static const float k16X16base = 2.0;
|
static const float k16X16base = 2.0;
|
||||||
const float entropy_mul16X16 =
|
const float entropy_mul16X16 =
|
||||||
k16X16mul2 + k16X16mul1 / (butteraugli_target + k16X16base);
|
k16X16mul2 + k16X16mul1 / (butteraugli_target + k16X16base);
|
||||||
|
@ -1067,7 +1101,6 @@ void AcStrategyHeuristics::Init(const Image3F& src,
|
||||||
this->enc_state = enc_state;
|
this->enc_state = enc_state;
|
||||||
config.dequant = &enc_state->shared.matrices;
|
config.dequant = &enc_state->shared.matrices;
|
||||||
const CompressParams& cparams = enc_state->cparams;
|
const CompressParams& cparams = enc_state->cparams;
|
||||||
const float butteraugli_target = cparams.butteraugli_distance;
|
|
||||||
|
|
||||||
if (cparams.speed_tier >= SpeedTier::kCheetah) {
|
if (cparams.speed_tier >= SpeedTier::kCheetah) {
|
||||||
JXL_CHECK(enc_state->shared.matrices.EnsureComputed(1)); // DCT8 only
|
JXL_CHECK(enc_state->shared.matrices.EnsureComputed(1)); // DCT8 only
|
||||||
|
@ -1098,20 +1131,10 @@ void AcStrategyHeuristics::Init(const Image3F& src,
|
||||||
// - estimate of the number of bits that will be used by the block
|
// - estimate of the number of bits that will be used by the block
|
||||||
// - information loss due to quantization
|
// - information loss due to quantization
|
||||||
// The following constant controls the relative weights of these components.
|
// The following constant controls the relative weights of these components.
|
||||||
config.info_loss_multiplier = 138.0f;
|
config.info_loss_multiplier = 58.67516723857484f;
|
||||||
config.info_loss_multiplier2 = 50.46839691767866;
|
config.info_loss_multiplier2 = 43.0f;
|
||||||
// TODO(jyrki): explore base_entropy setting more.
|
config.zeros_mul = 2.55f;
|
||||||
// A small value (0?) works better at high distance, while a larger value
|
config.cost_delta = 4.9425062806007478f;
|
||||||
// may be more effective at low distance/high bpp.
|
|
||||||
config.base_entropy = 0.0;
|
|
||||||
config.zeros_mul = 7.565053364251793f;
|
|
||||||
// Lots of +1 and -1 coefficients at high quality, it is
|
|
||||||
// beneficial to favor them. At low qualities zeros matter more
|
|
||||||
// and +1 / -1 coefficients are already quite harmful.
|
|
||||||
float slope = std::min<float>(1.0f, butteraugli_target * (1.0f / 3));
|
|
||||||
config.cost1 = 1 + slope * 8.8703248061477744f;
|
|
||||||
config.cost2 = 4.4628149885273363f;
|
|
||||||
config.cost_delta = 5.3359184934516337f;
|
|
||||||
JXL_ASSERT(enc_state->shared.ac_strategy.xsize() ==
|
JXL_ASSERT(enc_state->shared.ac_strategy.xsize() ==
|
||||||
enc_state->shared.frame_dim.xsize_blocks);
|
enc_state->shared.frame_dim.xsize_blocks);
|
||||||
JXL_ASSERT(enc_state->shared.ac_strategy.ysize() ==
|
JXL_ASSERT(enc_state->shared.ac_strategy.ysize() ==
|
||||||
|
|
|
@ -37,12 +37,7 @@ struct ACSConfig {
|
||||||
size_t masking_field_stride;
|
size_t masking_field_stride;
|
||||||
const float* JXL_RESTRICT src_rows[3];
|
const float* JXL_RESTRICT src_rows[3];
|
||||||
size_t src_stride;
|
size_t src_stride;
|
||||||
// Cost for 1 (-1), 2 (-2) explicitly, cost for others computed with cost1 +
|
|
||||||
// cost2 + sqrt(q) * cost_delta.
|
|
||||||
float cost1;
|
|
||||||
float cost2;
|
|
||||||
float cost_delta;
|
float cost_delta;
|
||||||
float base_entropy;
|
|
||||||
float zeros_mul;
|
float zeros_mul;
|
||||||
const float& Pixel(size_t c, size_t x, size_t y) const {
|
const float& Pixel(size_t c, size_t x, size_t y) const {
|
||||||
return src_rows[c][y * src_stride + x];
|
return src_rows[c][y * src_stride + x];
|
||||||
|
|
|
@ -736,7 +736,7 @@ ImageF TileDistMap(const ImageF& distmap, int tile_size, int margin,
|
||||||
|
|
||||||
static const float kDcQuantPow = 0.83;
|
static const float kDcQuantPow = 0.83;
|
||||||
static const float kDcQuant = 1.095924047623553f;
|
static const float kDcQuant = 1.095924047623553f;
|
||||||
static const float kAcQuant = 0.7784;
|
static const float kAcQuant = 0.7635;
|
||||||
|
|
||||||
// Computes the decoded image for a given set of compression parameters.
|
// Computes the decoded image for a given set of compression parameters.
|
||||||
ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
||||||
|
|
|
@ -209,39 +209,39 @@ void AdjustQuantBlockAC(const Quantizer& quantizer, size_t c,
|
||||||
{
|
{
|
||||||
static const double kMul1[3][3] = {
|
static const double kMul1[3][3] = {
|
||||||
{
|
{
|
||||||
0.30628347689416235,
|
0.13289977307244785,
|
||||||
0.19096514988140451,
|
0.13991489841351781,
|
||||||
0.10092267072278764,
|
0.083900681804010419,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
0.68175730483344243,
|
0.69938583107168562,
|
||||||
0.19038660767376803,
|
0.19612117586770869,
|
||||||
0.14069887255219371,
|
0.15307492924107463,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
0.74599469660659012,
|
0.099160801461836312,
|
||||||
0.10465705596003883,
|
0.16684944507307059,
|
||||||
0.075491104183520744,
|
0.16608517854968413,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
static const double kMul2[3][3] = {
|
static const double kMul2[3][3] = {
|
||||||
{
|
{
|
||||||
0.022707896753424779,
|
0.24773711435293466,
|
||||||
0.84465309720205983,
|
0.65189637683223112,
|
||||||
5.2275313293658812,
|
1.0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
0.17545973555482378,
|
0.46465181913392556,
|
||||||
0.97395015736868384,
|
0.3142440606068525,
|
||||||
1.9659234163151995,
|
0.30128806880068809,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
0.75243833661051895,
|
0.45203398366713637,
|
||||||
1.7774383804879366,
|
0.15063329382779103,
|
||||||
0.3793181712352986,
|
0.067846407329923752,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const float kQuantNormalizer = 2.9037220690527175;
|
const float kQuantNormalizer = 2.8261379721245263;
|
||||||
sum_of_error *= kQuantNormalizer;
|
sum_of_error *= kQuantNormalizer;
|
||||||
sum_of_vals *= kQuantNormalizer;
|
sum_of_vals *= kQuantNormalizer;
|
||||||
if (quant_kind >= AcStrategy::Type::DCT16X16) {
|
if (quant_kind >= AcStrategy::Type::DCT16X16) {
|
||||||
|
@ -252,9 +252,15 @@ void AdjustQuantBlockAC(const Quantizer& quantizer, size_t c,
|
||||||
} else if (quant_kind == AcStrategy::Type::DCT16X16) {
|
} else if (quant_kind == AcStrategy::Type::DCT16X16) {
|
||||||
ix = 0;
|
ix = 0;
|
||||||
}
|
}
|
||||||
if (sum_of_error > kMul1[ix][c] * xsize * ysize * kBlockDim * kBlockDim &&
|
int step =
|
||||||
sum_of_error > kMul2[ix][c] * sum_of_vals) {
|
sum_of_error / (kMul1[ix][c] * xsize * ysize * kBlockDim * kBlockDim +
|
||||||
*quant += 1;
|
kMul2[ix][c] * sum_of_vals);
|
||||||
|
if (step >= 2) {
|
||||||
|
step = 2;
|
||||||
|
}
|
||||||
|
if (sum_of_error > kMul1[ix][c] * xsize * ysize * kBlockDim * kBlockDim +
|
||||||
|
kMul2[ix][c] * sum_of_vals) {
|
||||||
|
*quant += step;
|
||||||
if (*quant >= Quantizer::kQuantMax) {
|
if (*quant >= Quantizer::kQuantMax) {
|
||||||
*quant = Quantizer::kQuantMax - 1;
|
*quant = Quantizer::kQuantMax - 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,11 @@ struct CompressParams {
|
||||||
// Use brotli compression for any boxes derived from a JPEG frame.
|
// Use brotli compression for any boxes derived from a JPEG frame.
|
||||||
bool jpeg_compress_boxes = true;
|
bool jpeg_compress_boxes = true;
|
||||||
|
|
||||||
|
// Preserve this metadata when doing JPEG recompression.
|
||||||
|
bool jpeg_keep_exif = true;
|
||||||
|
bool jpeg_keep_xmp = true;
|
||||||
|
bool jpeg_keep_jumbf = true;
|
||||||
|
|
||||||
// Set the noise to what it would approximately be if shooting at the nominal
|
// Set the noise to what it would approximately be if shooting at the nominal
|
||||||
// exposure for a given ISO setting on a 35mm camera.
|
// exposure for a given ISO setting on a 35mm camera.
|
||||||
float photon_noise_iso = 0;
|
float photon_noise_iso = 0;
|
||||||
|
|
|
@ -802,6 +802,7 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
|
||||||
info->exponent_bits_per_sample)) {
|
info->exponent_bits_per_sample)) {
|
||||||
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
|
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid bit depth");
|
||||||
}
|
}
|
||||||
|
|
||||||
enc->metadata.m.bit_depth.bits_per_sample = info->bits_per_sample;
|
enc->metadata.m.bit_depth.bits_per_sample = info->bits_per_sample;
|
||||||
enc->metadata.m.bit_depth.exponent_bits_per_sample =
|
enc->metadata.m.bit_depth.exponent_bits_per_sample =
|
||||||
info->exponent_bits_per_sample;
|
info->exponent_bits_per_sample;
|
||||||
|
@ -919,6 +920,55 @@ void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type,
|
||||||
info->cfa_channel = 0;
|
info->cfa_channel = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JXL_EXPORT JxlEncoderStatus JxlEncoderSetUpsamplingMode(JxlEncoder* enc,
|
||||||
|
const int64_t factor,
|
||||||
|
const int64_t mode) {
|
||||||
|
// for convenience, allow calling this with factor 1 and just make it a no-op
|
||||||
|
if (factor == 1) return JXL_ENC_SUCCESS;
|
||||||
|
if (factor != 2 && factor != 4 && factor != 8)
|
||||||
|
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
|
||||||
|
"Invalid upsampling factor");
|
||||||
|
if (mode < -1)
|
||||||
|
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE, "Invalid upsampling mode");
|
||||||
|
if (mode > 1)
|
||||||
|
return JXL_API_ERROR(enc, JXL_ENC_ERR_NOT_SUPPORTED,
|
||||||
|
"Unsupported upsampling mode");
|
||||||
|
|
||||||
|
const size_t count = (factor == 2 ? 15 : (factor == 4 ? 55 : 210));
|
||||||
|
auto& td = enc->metadata.transform_data;
|
||||||
|
float* weights = (factor == 2 ? td.upsampling2_weights
|
||||||
|
: (factor == 4 ? td.upsampling4_weights
|
||||||
|
: td.upsampling8_weights));
|
||||||
|
if (mode == -1) {
|
||||||
|
// Default fancy upsampling: don't signal custom weights
|
||||||
|
enc->metadata.transform_data.custom_weights_mask &= ~(factor >> 1);
|
||||||
|
} else if (mode == 0) {
|
||||||
|
// Nearest neighbor upsampling
|
||||||
|
enc->metadata.transform_data.custom_weights_mask |= (factor >> 1);
|
||||||
|
memset(weights, 0, sizeof(float) * count);
|
||||||
|
if (factor == 2) {
|
||||||
|
weights[9] = 1.f;
|
||||||
|
} else if (factor == 4) {
|
||||||
|
for (int i : {19, 24, 49}) weights[i] = 1.f;
|
||||||
|
} else if (factor == 8) {
|
||||||
|
for (int i : {39, 44, 49, 54, 119, 124, 129, 174, 179, 204}) {
|
||||||
|
weights[i] = 1.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mode == 1) {
|
||||||
|
// 'Pixel dots' upsampling (nearest-neighbor with cut corners)
|
||||||
|
JxlEncoderSetUpsamplingMode(enc, factor, 0);
|
||||||
|
if (factor == 4) {
|
||||||
|
weights[19] = 0.f;
|
||||||
|
weights[24] = 0.5f;
|
||||||
|
} else if (factor == 8) {
|
||||||
|
for (int i : {39, 44, 49, 119}) weights[i] = 0.f;
|
||||||
|
for (int i : {54, 124}) weights[i] = 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JXL_ENC_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
|
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
|
||||||
JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) {
|
JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info) {
|
||||||
if (index >= enc->metadata.m.num_extra_channels) {
|
if (index >= enc->metadata.m.num_extra_channels) {
|
||||||
|
@ -1285,6 +1335,15 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
|
||||||
"Buffering has to be in [0..3]");
|
"Buffering has to be in [0..3]");
|
||||||
}
|
}
|
||||||
return JXL_ENC_SUCCESS;
|
return JXL_ENC_SUCCESS;
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
|
||||||
|
frame_settings->values.cparams.jpeg_keep_exif = value;
|
||||||
|
return JXL_ENC_SUCCESS;
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
|
||||||
|
frame_settings->values.cparams.jpeg_keep_xmp = value;
|
||||||
|
return JXL_ENC_SUCCESS;
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
|
||||||
|
frame_settings->values.cparams.jpeg_keep_jumbf = value;
|
||||||
|
return JXL_ENC_SUCCESS;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
|
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
|
||||||
|
@ -1376,6 +1435,9 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption(
|
||||||
case JXL_ENC_FRAME_SETTING_FILL_ENUM:
|
case JXL_ENC_FRAME_SETTING_FILL_ENUM:
|
||||||
case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
|
case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
|
||||||
case JXL_ENC_FRAME_SETTING_BUFFERING:
|
case JXL_ENC_FRAME_SETTING_BUFFERING:
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
|
||||||
|
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
|
||||||
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
|
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
|
||||||
"Int option, try setting it with "
|
"Int option, try setting it with "
|
||||||
"JxlEncoderFrameSettingsSetOption");
|
"JxlEncoderFrameSettingsSetOption");
|
||||||
|
@ -1567,7 +1629,8 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
||||||
frame_settings->enc->metadata.m.orientation);
|
frame_settings->enc->metadata.m.orientation);
|
||||||
jxl::InterpretExif(io.blobs.exif, &orientation);
|
jxl::InterpretExif(io.blobs.exif, &orientation);
|
||||||
frame_settings->enc->metadata.m.orientation = orientation;
|
frame_settings->enc->metadata.m.orientation = orientation;
|
||||||
|
}
|
||||||
|
if (!io.blobs.exif.empty() && frame_settings->values.cparams.jpeg_keep_exif) {
|
||||||
size_t exif_size = io.blobs.exif.size();
|
size_t exif_size = io.blobs.exif.size();
|
||||||
// Exif data in JPEG is limited to 64k
|
// Exif data in JPEG is limited to 64k
|
||||||
if (exif_size > 0xFFFF) {
|
if (exif_size > 0xFFFF) {
|
||||||
|
@ -1581,19 +1644,26 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
||||||
JxlEncoderAddBox(frame_settings->enc, "Exif", exif.data(), exif_size,
|
JxlEncoderAddBox(frame_settings->enc, "Exif", exif.data(), exif_size,
|
||||||
frame_settings->values.cparams.jpeg_compress_boxes);
|
frame_settings->values.cparams.jpeg_compress_boxes);
|
||||||
}
|
}
|
||||||
if (!io.blobs.xmp.empty()) {
|
if (!io.blobs.xmp.empty() && frame_settings->values.cparams.jpeg_keep_xmp) {
|
||||||
JxlEncoderUseBoxes(frame_settings->enc);
|
JxlEncoderUseBoxes(frame_settings->enc);
|
||||||
JxlEncoderAddBox(frame_settings->enc, "xml ", io.blobs.xmp.data(),
|
JxlEncoderAddBox(frame_settings->enc, "xml ", io.blobs.xmp.data(),
|
||||||
io.blobs.xmp.size(),
|
io.blobs.xmp.size(),
|
||||||
frame_settings->values.cparams.jpeg_compress_boxes);
|
frame_settings->values.cparams.jpeg_compress_boxes);
|
||||||
}
|
}
|
||||||
if (!io.blobs.jumbf.empty()) {
|
if (!io.blobs.jumbf.empty() &&
|
||||||
|
frame_settings->values.cparams.jpeg_keep_jumbf) {
|
||||||
JxlEncoderUseBoxes(frame_settings->enc);
|
JxlEncoderUseBoxes(frame_settings->enc);
|
||||||
JxlEncoderAddBox(frame_settings->enc, "jumb", io.blobs.jumbf.data(),
|
JxlEncoderAddBox(frame_settings->enc, "jumb", io.blobs.jumbf.data(),
|
||||||
io.blobs.jumbf.size(),
|
io.blobs.jumbf.size(),
|
||||||
frame_settings->values.cparams.jpeg_compress_boxes);
|
frame_settings->values.cparams.jpeg_compress_boxes);
|
||||||
}
|
}
|
||||||
if (frame_settings->enc->store_jpeg_metadata) {
|
if (frame_settings->enc->store_jpeg_metadata) {
|
||||||
|
if (!frame_settings->values.cparams.jpeg_keep_exif ||
|
||||||
|
!frame_settings->values.cparams.jpeg_keep_xmp) {
|
||||||
|
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
|
||||||
|
"Need to preserve EXIF and XMP to allow JPEG "
|
||||||
|
"bitstream reconstruction");
|
||||||
|
}
|
||||||
jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
|
jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
|
||||||
jxl::PaddedBytes jpeg_data;
|
jxl::PaddedBytes jpeg_data;
|
||||||
if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
|
if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
|
||||||
|
|
|
@ -523,7 +523,7 @@ TEST(EncodeTest, LossyEncoderUseOriginalProfileTest) {
|
||||||
ASSERT_EQ(JXL_ENC_SUCCESS,
|
ASSERT_EQ(JXL_ENC_SUCCESS,
|
||||||
JxlEncoderFrameSettingsSetOption(
|
JxlEncoderFrameSettingsSetOption(
|
||||||
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8));
|
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8));
|
||||||
VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7173, true);
|
VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7228, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,6 +842,7 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
|
||||||
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
|
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
|
||||||
|
|
||||||
jxl::extras::JXLDecompressParams dparams;
|
jxl::extras::JXLDecompressParams dparams;
|
||||||
|
jxl::test::DefaultAcceptedFormats(dparams);
|
||||||
std::vector<uint8_t> decoded_jpeg_bytes;
|
std::vector<uint8_t> decoded_jpeg_bytes;
|
||||||
jxl::extras::PackedPixelFile ppf;
|
jxl::extras::PackedPixelFile ppf;
|
||||||
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
||||||
|
@ -883,6 +884,7 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(ProgressiveJPEGReconstructionTest)) {
|
||||||
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
|
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
|
||||||
|
|
||||||
jxl::extras::JXLDecompressParams dparams;
|
jxl::extras::JXLDecompressParams dparams;
|
||||||
|
jxl::test::DefaultAcceptedFormats(dparams);
|
||||||
std::vector<uint8_t> decoded_jpeg_bytes;
|
std::vector<uint8_t> decoded_jpeg_bytes;
|
||||||
jxl::extras::PackedPixelFile ppf;
|
jxl::extras::PackedPixelFile ppf;
|
||||||
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
||||||
|
|
|
@ -111,9 +111,15 @@ static JXL_INLINE void DischargeBitBuffer(JpegBitWriter* bw, int nbits,
|
||||||
}
|
}
|
||||||
|
|
||||||
static JXL_INLINE void WriteBits(JpegBitWriter* bw, int nbits, uint64_t bits) {
|
static JXL_INLINE void WriteBits(JpegBitWriter* bw, int nbits, uint64_t bits) {
|
||||||
|
JXL_DASSERT(nbits > 0);
|
||||||
bw->put_bits -= nbits;
|
bw->put_bits -= nbits;
|
||||||
if (JXL_UNLIKELY(bw->put_bits < 0)) {
|
if (JXL_UNLIKELY(bw->put_bits < 0)) {
|
||||||
DischargeBitBuffer(bw, nbits, bits);
|
if (JXL_UNLIKELY(nbits > 64)) {
|
||||||
|
bw->put_bits += nbits;
|
||||||
|
bw->healthy = false;
|
||||||
|
} else {
|
||||||
|
DischargeBitBuffer(bw, nbits, bits);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bw->put_buffer |= (bits << bw->put_bits);
|
bw->put_buffer |= (bits << bw->put_bits);
|
||||||
}
|
}
|
||||||
|
@ -180,50 +186,25 @@ void DCTCodingStateInit(DCTCodingState* s) {
|
||||||
s->refinement_bits_.reserve(kJPEGMaxCorrectionBits);
|
s->refinement_bits_.reserve(kJPEGMaxCorrectionBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OutputModes {
|
|
||||||
kModeHistogram,
|
|
||||||
kModeWrite,
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
static JXL_INLINE void WriteSymbol(int symbol, HuffmanCodeTable* table,
|
static JXL_INLINE void WriteSymbol(int symbol, HuffmanCodeTable* table,
|
||||||
JpegBitWriter* bw) {
|
JpegBitWriter* bw) {
|
||||||
if (kOutputMode == OutputModes::kModeHistogram) {
|
WriteBits(bw, table->depth[symbol], table->code[symbol]);
|
||||||
++table->depth[symbol];
|
|
||||||
} else {
|
|
||||||
// This is an optimization; if everything goes well,
|
|
||||||
// then |nbits| is positive; if non-existing Huffman symbol is going to be
|
|
||||||
// encoded, its length should be zero; later the encoder could check the
|
|
||||||
// "health" of JpegBitWriter.
|
|
||||||
// Not checking this can cause bad output without error on corrupt input,
|
|
||||||
// but that's OK. The check causes a noticeable slowdown.
|
|
||||||
#if false
|
|
||||||
bw->healthy &= table->depth[symbol] != 0;
|
|
||||||
#endif
|
|
||||||
WriteBits(bw, table->depth[symbol], table->code[symbol]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
static JXL_INLINE void WriteSymbolBits(int symbol, HuffmanCodeTable* table,
|
static JXL_INLINE void WriteSymbolBits(int symbol, HuffmanCodeTable* table,
|
||||||
JpegBitWriter* bw, int nbits,
|
JpegBitWriter* bw, int nbits,
|
||||||
uint64_t bits) {
|
uint64_t bits) {
|
||||||
if (kOutputMode == OutputModes::kModeHistogram) {
|
WriteBits(bw, nbits + table->depth[symbol],
|
||||||
++table->depth[symbol];
|
bits | (table->code[symbol] << nbits));
|
||||||
} else {
|
|
||||||
WriteBits(bw, nbits + table->depth[symbol],
|
|
||||||
bits | (table->code[symbol] << nbits));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit all buffered data to the bit stream using the given Huffman code and
|
// Emit all buffered data to the bit stream using the given Huffman code and
|
||||||
// bit writer.
|
// bit writer.
|
||||||
template <int kOutputMode>
|
|
||||||
static JXL_INLINE void Flush(DCTCodingState* s, JpegBitWriter* bw) {
|
static JXL_INLINE void Flush(DCTCodingState* s, JpegBitWriter* bw) {
|
||||||
if (s->eob_run_ > 0) {
|
if (s->eob_run_ > 0) {
|
||||||
int nbits = FloorLog2Nonzero<uint32_t>(s->eob_run_);
|
int nbits = FloorLog2Nonzero<uint32_t>(s->eob_run_);
|
||||||
int symbol = nbits << 4u;
|
int symbol = nbits << 4u;
|
||||||
WriteSymbol<kOutputMode>(symbol, s->cur_ac_huff_, bw);
|
WriteSymbol(symbol, s->cur_ac_huff_, bw);
|
||||||
if (nbits > 0) {
|
if (nbits > 0) {
|
||||||
WriteBits(bw, nbits, s->eob_run_ & ((1 << nbits) - 1));
|
WriteBits(bw, nbits, s->eob_run_ & ((1 << nbits) - 1));
|
||||||
}
|
}
|
||||||
|
@ -237,7 +218,6 @@ static JXL_INLINE void Flush(DCTCodingState* s, JpegBitWriter* bw) {
|
||||||
|
|
||||||
// Buffer some more data at the end-of-band (the last non-zero or newly
|
// Buffer some more data at the end-of-band (the last non-zero or newly
|
||||||
// non-zero coefficient within the [Ss, Se] spectral band).
|
// non-zero coefficient within the [Ss, Se] spectral band).
|
||||||
template <int kOutputMode>
|
|
||||||
static JXL_INLINE void BufferEndOfBand(DCTCodingState* s,
|
static JXL_INLINE void BufferEndOfBand(DCTCodingState* s,
|
||||||
HuffmanCodeTable* ac_huff,
|
HuffmanCodeTable* ac_huff,
|
||||||
const std::vector<int>* new_bits,
|
const std::vector<int>* new_bits,
|
||||||
|
@ -252,7 +232,7 @@ static JXL_INLINE void BufferEndOfBand(DCTCodingState* s,
|
||||||
}
|
}
|
||||||
if (s->eob_run_ == 0x7FFF ||
|
if (s->eob_run_ == 0x7FFF ||
|
||||||
s->refinement_bits_.size() > kJPEGMaxCorrectionBits - kDCTBlockSize + 1) {
|
s->refinement_bits_.size() > kJPEGMaxCorrectionBits - kDCTBlockSize + 1) {
|
||||||
Flush<kOutputMode>(s, bw);
|
Flush(s, bw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,10 +375,11 @@ bool EncodeDHT(const JPEGData& jpg, SerializationState* state) {
|
||||||
huff_table = &state->dc_huff_table[index];
|
huff_table = &state->dc_huff_table[index];
|
||||||
}
|
}
|
||||||
// TODO(eustas): cache
|
// TODO(eustas): cache
|
||||||
// TODO(eustas): set up non-existing symbols
|
huff_table->InitDepths(127);
|
||||||
if (!BuildHuffmanCodeTable(huff, huff_table)) {
|
if (!BuildHuffmanCodeTable(huff, huff_table)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
huff_table->initialized = true;
|
||||||
size_t total_count = 0;
|
size_t total_count = 0;
|
||||||
size_t max_length = 0;
|
size_t max_length = 0;
|
||||||
for (size_t i = 0; i < huff.counts.size(); ++i) {
|
for (size_t i = 0; i < huff.counts.size(); ++i) {
|
||||||
|
@ -497,7 +478,6 @@ bool EncodeInterMarkerData(const JPEGData& jpg, SerializationState* state) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
HuffmanCodeTable* ac_huff, int num_zero_runs,
|
HuffmanCodeTable* ac_huff, int num_zero_runs,
|
||||||
coeff_t* last_dc_coeff, JpegBitWriter* bw) {
|
coeff_t* last_dc_coeff, JpegBitWriter* bw) {
|
||||||
|
@ -511,7 +491,7 @@ bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
temp2 ^= temp;
|
temp2 ^= temp;
|
||||||
|
|
||||||
int dc_nbits = (temp2 == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp2) + 1);
|
int dc_nbits = (temp2 == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp2) + 1);
|
||||||
WriteSymbol<kOutputMode>(dc_nbits, dc_huff, bw);
|
WriteSymbol(dc_nbits, dc_huff, bw);
|
||||||
#if false
|
#if false
|
||||||
// If the input is corrupt, this could be triggered. Checking is
|
// If the input is corrupt, this could be triggered. Checking is
|
||||||
// costly though, so it makes more sense to avoid this branch.
|
// costly though, so it makes more sense to avoid this branch.
|
||||||
|
@ -519,7 +499,9 @@ bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
// of catching it and returning error)
|
// of catching it and returning error)
|
||||||
if (dc_nbits >= 12) return false;
|
if (dc_nbits >= 12) return false;
|
||||||
#endif
|
#endif
|
||||||
WriteBits(bw, dc_nbits, temp & ((1u << dc_nbits) - 1));
|
if (dc_nbits) {
|
||||||
|
WriteBits(bw, dc_nbits, temp & ((1u << dc_nbits) - 1));
|
||||||
|
}
|
||||||
int16_t r = 0;
|
int16_t r = 0;
|
||||||
|
|
||||||
for (size_t i = 1; i < 64; i++) {
|
for (size_t i = 1; i < 64; i++) {
|
||||||
|
@ -530,36 +512,35 @@ bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
temp += temp2;
|
temp += temp2;
|
||||||
temp2 ^= temp;
|
temp2 ^= temp;
|
||||||
if (JXL_UNLIKELY(r > 15)) {
|
if (JXL_UNLIKELY(r > 15)) {
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
r -= 16;
|
|
||||||
}
|
|
||||||
if (JXL_UNLIKELY(r > 15)) {
|
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
|
||||||
r -= 16;
|
|
||||||
}
|
|
||||||
if (JXL_UNLIKELY(r > 15)) {
|
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
|
||||||
r -= 16;
|
r -= 16;
|
||||||
|
if (r > 15) {
|
||||||
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
|
r -= 16;
|
||||||
|
}
|
||||||
|
if (r > 15) {
|
||||||
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
|
r -= 16;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int ac_nbits = FloorLog2Nonzero<uint32_t>(temp2) + 1;
|
int ac_nbits = FloorLog2Nonzero<uint32_t>(temp2) + 1;
|
||||||
int symbol = (r << 4u) + ac_nbits;
|
int symbol = (r << 4u) + ac_nbits;
|
||||||
WriteSymbolBits<kOutputMode>(symbol, ac_huff, bw, ac_nbits,
|
WriteSymbolBits(symbol, ac_huff, bw, ac_nbits,
|
||||||
temp & ((1 << ac_nbits) - 1));
|
temp & ((1 << ac_nbits) - 1));
|
||||||
r = 0;
|
r = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_zero_runs; ++i) {
|
for (int i = 0; i < num_zero_runs; ++i) {
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
r -= 16;
|
r -= 16;
|
||||||
}
|
}
|
||||||
if (r > 0) {
|
if (r > 0) {
|
||||||
WriteSymbol<kOutputMode>(0, ac_huff, bw);
|
WriteSymbol(0, ac_huff, bw);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
bool EncodeDCTBlockProgressive(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
bool EncodeDCTBlockProgressive(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
HuffmanCodeTable* ac_huff, int Ss, int Se,
|
HuffmanCodeTable* ac_huff, int Ss, int Se,
|
||||||
int Al, int num_zero_runs,
|
int Al, int num_zero_runs,
|
||||||
|
@ -579,8 +560,8 @@ bool EncodeDCTBlockProgressive(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
temp2--;
|
temp2--;
|
||||||
}
|
}
|
||||||
int nbits = (temp == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp) + 1);
|
int nbits = (temp == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp) + 1);
|
||||||
WriteSymbol<kOutputMode>(nbits, dc_huff, bw);
|
WriteSymbol(nbits, dc_huff, bw);
|
||||||
if (nbits > 0) {
|
if (nbits) {
|
||||||
WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
|
WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
|
||||||
}
|
}
|
||||||
++Ss;
|
++Ss;
|
||||||
|
@ -607,34 +588,33 @@ bool EncodeDCTBlockProgressive(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
|
||||||
r++;
|
r++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
while (r > 15) {
|
while (r > 15) {
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
r -= 16;
|
r -= 16;
|
||||||
}
|
}
|
||||||
int nbits = FloorLog2Nonzero<uint32_t>(temp) + 1;
|
int nbits = FloorLog2Nonzero<uint32_t>(temp) + 1;
|
||||||
int symbol = (r << 4u) + nbits;
|
int symbol = (r << 4u) + nbits;
|
||||||
WriteSymbol<kOutputMode>(symbol, ac_huff, bw);
|
WriteSymbol(symbol, ac_huff, bw);
|
||||||
WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
|
WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
|
||||||
r = 0;
|
r = 0;
|
||||||
}
|
}
|
||||||
if (num_zero_runs > 0) {
|
if (num_zero_runs > 0) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
for (int i = 0; i < num_zero_runs; ++i) {
|
for (int i = 0; i < num_zero_runs; ++i) {
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
r -= 16;
|
r -= 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r > 0) {
|
if (r > 0) {
|
||||||
BufferEndOfBand<kOutputMode>(coding_state, ac_huff, nullptr, bw);
|
BufferEndOfBand(coding_state, ac_huff, nullptr, bw);
|
||||||
if (!eob_run_allowed) {
|
if (!eob_run_allowed) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
|
bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
|
||||||
int Ss, int Se, int Al, DCTCodingState* coding_state,
|
int Ss, int Se, int Al, DCTCodingState* coding_state,
|
||||||
JpegBitWriter* bw) {
|
JpegBitWriter* bw) {
|
||||||
|
@ -665,8 +645,8 @@ bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
while (r > 15 && k <= eob) {
|
while (r > 15 && k <= eob) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
|
WriteSymbol(0xf0, ac_huff, bw);
|
||||||
r -= 16;
|
r -= 16;
|
||||||
for (int bit : refinement_bits) {
|
for (int bit : refinement_bits) {
|
||||||
WriteBits(bw, 1, bit);
|
WriteBits(bw, 1, bit);
|
||||||
|
@ -677,10 +657,10 @@ bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
|
||||||
refinement_bits.push_back(abs_values[k] & 1u);
|
refinement_bits.push_back(abs_values[k] & 1u);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
int symbol = (r << 4u) + 1;
|
int symbol = (r << 4u) + 1;
|
||||||
int new_non_zero_bit = (coeffs[kJPEGNaturalOrder[k]] < 0) ? 0 : 1;
|
int new_non_zero_bit = (coeffs[kJPEGNaturalOrder[k]] < 0) ? 0 : 1;
|
||||||
WriteSymbol<kOutputMode>(symbol, ac_huff, bw);
|
WriteSymbol(symbol, ac_huff, bw);
|
||||||
WriteBits(bw, 1, new_non_zero_bit);
|
WriteBits(bw, 1, new_non_zero_bit);
|
||||||
for (int bit : refinement_bits) {
|
for (int bit : refinement_bits) {
|
||||||
WriteBits(bw, 1, bit);
|
WriteBits(bw, 1, bit);
|
||||||
|
@ -689,9 +669,9 @@ bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
|
||||||
r = 0;
|
r = 0;
|
||||||
}
|
}
|
||||||
if (r > 0 || !refinement_bits.empty()) {
|
if (r > 0 || !refinement_bits.empty()) {
|
||||||
BufferEndOfBand<kOutputMode>(coding_state, ac_huff, &refinement_bits, bw);
|
BufferEndOfBand(coding_state, ac_huff, &refinement_bits, bw);
|
||||||
if (!eob_run_allowed) {
|
if (!eob_run_allowed) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -714,7 +694,7 @@ size_t HistogramIndex(const JPEGData& jpg, size_t scan_index,
|
||||||
return idx + component_index;
|
return idx + component_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kMode, int kOutputMode>
|
template <int kMode>
|
||||||
SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
SerializationState* state) {
|
SerializationState* state) {
|
||||||
const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
|
const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
|
||||||
|
@ -772,6 +752,7 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
|
|
||||||
// DC-only is defined by [0..0] spectral range.
|
// DC-only is defined by [0..0] spectral range.
|
||||||
const bool want_ac = ((Ss != 0) || (Se != 0));
|
const bool want_ac = ((Ss != 0) || (Se != 0));
|
||||||
|
const bool want_dc = (Ss == 0);
|
||||||
// TODO: support streaming decoding again.
|
// TODO: support streaming decoding again.
|
||||||
const bool complete_ac = true;
|
const bool complete_ac = true;
|
||||||
const bool has_ac = true;
|
const bool has_ac = true;
|
||||||
|
@ -796,7 +777,7 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) {
|
for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) {
|
||||||
// Possibly emit a restart marker.
|
// Possibly emit a restart marker.
|
||||||
if (restart_interval > 0 && ss.restarts_to_go == 0) {
|
if (restart_interval > 0 && ss.restarts_to_go == 0) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
|
if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
|
||||||
return SerializationStatus::ERROR;
|
return SerializationStatus::ERROR;
|
||||||
}
|
}
|
||||||
|
@ -811,14 +792,16 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
for (size_t i = 0; i < scan_info.num_components; ++i) {
|
for (size_t i = 0; i < scan_info.num_components; ++i) {
|
||||||
const JPEGComponentScanInfo& si = scan_info.components[i];
|
const JPEGComponentScanInfo& si = scan_info.components[i];
|
||||||
const JPEGComponent& c = jpg.components[si.comp_idx];
|
const JPEGComponent& c = jpg.components[si.comp_idx];
|
||||||
size_t dc_tbl_idx = (kOutputMode == OutputModes::kModeHistogram
|
size_t dc_tbl_idx = si.dc_tbl_idx;
|
||||||
? HistogramIndex(jpg, state->scan_index, i)
|
size_t ac_tbl_idx = si.ac_tbl_idx;
|
||||||
: si.dc_tbl_idx);
|
|
||||||
size_t ac_tbl_idx = (kOutputMode == OutputModes::kModeHistogram
|
|
||||||
? HistogramIndex(jpg, state->scan_index, i)
|
|
||||||
: si.ac_tbl_idx);
|
|
||||||
HuffmanCodeTable* dc_huff = &state->dc_huff_table[dc_tbl_idx];
|
HuffmanCodeTable* dc_huff = &state->dc_huff_table[dc_tbl_idx];
|
||||||
HuffmanCodeTable* ac_huff = &state->ac_huff_table[ac_tbl_idx];
|
HuffmanCodeTable* ac_huff = &state->ac_huff_table[ac_tbl_idx];
|
||||||
|
if (want_dc && !dc_huff->initialized) {
|
||||||
|
return SerializationStatus::ERROR;
|
||||||
|
}
|
||||||
|
if (want_ac && !ac_huff->initialized) {
|
||||||
|
return SerializationStatus::ERROR;
|
||||||
|
}
|
||||||
int n_blocks_y = is_interleaved ? c.v_samp_factor : 1;
|
int n_blocks_y = is_interleaved ? c.v_samp_factor : 1;
|
||||||
int n_blocks_x = is_interleaved ? c.h_samp_factor : 1;
|
int n_blocks_x = is_interleaved ? c.h_samp_factor : 1;
|
||||||
// compressed size per block cannot be more than 512 bytes per component
|
// compressed size per block cannot be more than 512 bytes per component
|
||||||
|
@ -829,7 +812,7 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
int block_x = mcu_x * n_blocks_x + ix;
|
int block_x = mcu_x * n_blocks_x + ix;
|
||||||
int block_idx = block_y * c.width_in_blocks + block_x;
|
int block_idx = block_y * c.width_in_blocks + block_x;
|
||||||
if (ss.block_scan_index == ss.next_reset_point) {
|
if (ss.block_scan_index == ss.next_reset_point) {
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
ss.next_reset_point = get_next_reset_point();
|
ss.next_reset_point = get_next_reset_point();
|
||||||
}
|
}
|
||||||
int num_zero_runs = 0;
|
int num_zero_runs = 0;
|
||||||
|
@ -842,16 +825,16 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
const coeff_t* coeffs = &c.coeffs[block_idx << 6];
|
const coeff_t* coeffs = &c.coeffs[block_idx << 6];
|
||||||
bool ok;
|
bool ok;
|
||||||
if (kMode == 0) {
|
if (kMode == 0) {
|
||||||
ok = EncodeDCTBlockSequential<kOutputMode>(
|
ok = EncodeDCTBlockSequential(coeffs, dc_huff, ac_huff,
|
||||||
coeffs, dc_huff, ac_huff, num_zero_runs,
|
num_zero_runs,
|
||||||
ss.last_dc_coeff + si.comp_idx, bw);
|
ss.last_dc_coeff + si.comp_idx, bw);
|
||||||
} else if (kMode == 1) {
|
} else if (kMode == 1) {
|
||||||
ok = EncodeDCTBlockProgressive<kOutputMode>(
|
ok = EncodeDCTBlockProgressive(
|
||||||
coeffs, dc_huff, ac_huff, Ss, Se, Al, num_zero_runs,
|
coeffs, dc_huff, ac_huff, Ss, Se, Al, num_zero_runs,
|
||||||
coding_state, ss.last_dc_coeff + si.comp_idx, bw);
|
coding_state, ss.last_dc_coeff + si.comp_idx, bw);
|
||||||
} else {
|
} else {
|
||||||
ok = EncodeRefinementBits<kOutputMode>(coeffs, ac_huff, Ss, Se,
|
ok = EncodeRefinementBits(coeffs, ac_huff, Ss, Se, Al,
|
||||||
Al, coding_state, bw);
|
coding_state, bw);
|
||||||
}
|
}
|
||||||
if (!ok) return SerializationStatus::ERROR;
|
if (!ok) return SerializationStatus::ERROR;
|
||||||
++ss.block_scan_index;
|
++ss.block_scan_index;
|
||||||
|
@ -865,7 +848,7 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
if (!bw->healthy) return SerializationStatus::ERROR;
|
if (!bw->healthy) return SerializationStatus::ERROR;
|
||||||
return SerializationStatus::NEEDS_MORE_INPUT;
|
return SerializationStatus::NEEDS_MORE_INPUT;
|
||||||
}
|
}
|
||||||
Flush<kOutputMode>(coding_state, bw);
|
Flush(coding_state, bw);
|
||||||
if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
|
if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
|
||||||
return SerializationStatus::ERROR;
|
return SerializationStatus::ERROR;
|
||||||
}
|
}
|
||||||
|
@ -877,7 +860,6 @@ SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
|
||||||
return SerializationStatus::DONE;
|
return SerializationStatus::DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
static SerializationStatus JXL_INLINE EncodeScan(const JPEGData& jpg,
|
static SerializationStatus JXL_INLINE EncodeScan(const JPEGData& jpg,
|
||||||
SerializationState* state) {
|
SerializationState* state) {
|
||||||
const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
|
const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
|
||||||
|
@ -889,15 +871,14 @@ static SerializationStatus JXL_INLINE EncodeScan(const JPEGData& jpg,
|
||||||
const bool need_sequential =
|
const bool need_sequential =
|
||||||
!is_progressive || (Ah == 0 && Al == 0 && Ss == 0 && Se == 63);
|
!is_progressive || (Ah == 0 && Al == 0 && Ss == 0 && Se == 63);
|
||||||
if (need_sequential) {
|
if (need_sequential) {
|
||||||
return DoEncodeScan<0, kOutputMode>(jpg, state);
|
return DoEncodeScan<0>(jpg, state);
|
||||||
} else if (Ah == 0) {
|
} else if (Ah == 0) {
|
||||||
return DoEncodeScan<1, kOutputMode>(jpg, state);
|
return DoEncodeScan<1>(jpg, state);
|
||||||
} else {
|
} else {
|
||||||
return DoEncodeScan<2, kOutputMode>(jpg, state);
|
return DoEncodeScan<2>(jpg, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int kOutputMode>
|
|
||||||
SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
|
SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
|
||||||
const JPEGData& jpg) {
|
const JPEGData& jpg) {
|
||||||
const auto to_status = [](bool result) {
|
const auto to_status = [](bool result) {
|
||||||
|
@ -913,8 +894,7 @@ SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
|
||||||
return to_status(EncodeSOF(jpg, marker, state));
|
return to_status(EncodeSOF(jpg, marker, state));
|
||||||
|
|
||||||
case 0xC4:
|
case 0xC4:
|
||||||
return to_status((kOutputMode == OutputModes::kModeHistogram) ||
|
return to_status(EncodeDHT(jpg, state));
|
||||||
EncodeDHT(jpg, state));
|
|
||||||
|
|
||||||
case 0xD0:
|
case 0xD0:
|
||||||
case 0xD1:
|
case 0xD1:
|
||||||
|
@ -930,7 +910,7 @@ SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
|
||||||
return to_status(EncodeEOI(jpg, state));
|
return to_status(EncodeEOI(jpg, state));
|
||||||
|
|
||||||
case 0xDA:
|
case 0xDA:
|
||||||
return EncodeScan<kOutputMode>(jpg, state);
|
return EncodeScan(jpg, state);
|
||||||
|
|
||||||
case 0xDB:
|
case 0xDB:
|
||||||
return to_status(EncodeDQT(jpg, state));
|
return to_status(EncodeDQT(jpg, state));
|
||||||
|
@ -968,7 +948,6 @@ SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(veluca): add streaming support again.
|
// TODO(veluca): add streaming support again.
|
||||||
template <int kOutputMode>
|
|
||||||
Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
|
Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
|
||||||
SerializationState* ss) {
|
SerializationState* ss) {
|
||||||
const auto maybe_push_output = [&]() -> Status {
|
const auto maybe_push_output = [&]() -> Status {
|
||||||
|
@ -999,18 +978,8 @@ Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
|
||||||
ss->stage = SerializationState::STAGE_ERROR;
|
ss->stage = SerializationState::STAGE_ERROR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (kOutputMode == OutputModes::kModeHistogram) {
|
ss->dc_huff_table.resize(kMaxHuffmanTables);
|
||||||
size_t num_histo = NumHistograms(jpg);
|
ss->ac_huff_table.resize(kMaxHuffmanTables);
|
||||||
ss->dc_huff_table.resize(num_histo);
|
|
||||||
ss->ac_huff_table.resize(num_histo);
|
|
||||||
for (size_t i = 0; i < num_histo; ++i) {
|
|
||||||
ss->dc_huff_table[i].InitDepths();
|
|
||||||
ss->ac_huff_table[i].InitDepths();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ss->dc_huff_table.resize(kMaxHuffmanTables);
|
|
||||||
ss->ac_huff_table.resize(kMaxHuffmanTables);
|
|
||||||
}
|
|
||||||
if (jpg.has_zero_padding_bit) {
|
if (jpg.has_zero_padding_bit) {
|
||||||
ss->pad_bits = jpg.padding_bits.data();
|
ss->pad_bits = jpg.padding_bits.data();
|
||||||
ss->pad_bits_end = ss->pad_bits + jpg.padding_bits.size();
|
ss->pad_bits_end = ss->pad_bits + jpg.padding_bits.size();
|
||||||
|
@ -1028,8 +997,7 @@ Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
uint8_t marker = jpg.marker_order[ss->section_index];
|
uint8_t marker = jpg.marker_order[ss->section_index];
|
||||||
SerializationStatus status =
|
SerializationStatus status = SerializeSection(marker, ss, jpg);
|
||||||
SerializeSection<kOutputMode>(marker, ss, jpg);
|
|
||||||
if (status == SerializationStatus::ERROR) {
|
if (status == SerializationStatus::ERROR) {
|
||||||
JXL_WARNING("Failed to encode marker 0x%.2x", marker);
|
JXL_WARNING("Failed to encode marker 0x%.2x", marker);
|
||||||
ss->stage = SerializationState::STAGE_ERROR;
|
ss->stage = SerializationState::STAGE_ERROR;
|
||||||
|
@ -1064,12 +1032,7 @@ Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
|
||||||
|
|
||||||
Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out) {
|
Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out) {
|
||||||
SerializationState ss;
|
SerializationState ss;
|
||||||
return WriteJpegInternal<OutputModes::kModeWrite>(jpg, out, &ss);
|
return WriteJpegInternal(jpg, out, &ss);
|
||||||
}
|
|
||||||
|
|
||||||
Status ProcessJpeg(const JPEGData& jpg, SerializationState* ss) {
|
|
||||||
auto nullout = [](const uint8_t* buf, size_t len) { return len; };
|
|
||||||
return WriteJpegInternal<OutputModes::kModeHistogram>(jpg, nullout, ss);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace jpeg
|
} // namespace jpeg
|
||||||
|
|
|
@ -25,10 +25,6 @@ using JPEGOutput = std::function<size_t(const uint8_t* buf, size_t len)>;
|
||||||
|
|
||||||
Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out);
|
Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out);
|
||||||
|
|
||||||
// Same as WriteJpeg, but instead of writing to the output, collects statistics
|
|
||||||
// about the bit-stream into `ss`.
|
|
||||||
Status ProcessJpeg(const JPEGData& jpg, SerializationState* ss);
|
|
||||||
|
|
||||||
} // namespace jpeg
|
} // namespace jpeg
|
||||||
} // namespace jxl
|
} // namespace jxl
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,12 @@ namespace jxl {
|
||||||
namespace jpeg {
|
namespace jpeg {
|
||||||
|
|
||||||
struct HuffmanCodeTable {
|
struct HuffmanCodeTable {
|
||||||
int depth[256];
|
int8_t depth[256];
|
||||||
int code[256];
|
uint16_t code[256];
|
||||||
void InitDepths() { std::fill(std::begin(depth), std::end(depth), 0); }
|
bool initialized = false;
|
||||||
|
void InitDepths(int value = 0) {
|
||||||
|
std::fill(std::begin(depth), std::end(depth), value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the packing of bits into output bytes.
|
// Handles the packing of bits into output bytes.
|
||||||
|
|
|
@ -343,7 +343,7 @@ Status JPEGData::VisitFields(Visitor* visitor) {
|
||||||
}
|
}
|
||||||
uint32_t tail_data_len = tail_data.size();
|
uint32_t tail_data_len = tail_data.size();
|
||||||
if (!visitor->IsReading() && tail_data_len > 4260096) {
|
if (!visitor->IsReading() && tail_data_len > 4260096) {
|
||||||
return JXL_FAILURE("Tail data too large (max size = 4260096, size = %u).",
|
return JXL_FAILURE("Tail data too large (max size = 4260096, size = %u)",
|
||||||
tail_data_len);
|
tail_data_len);
|
||||||
}
|
}
|
||||||
JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(8, 1),
|
JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(8, 1),
|
||||||
|
@ -370,6 +370,46 @@ Status JPEGData::VisitFields(Visitor* visitor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
size_t dht_index = 0;
|
||||||
|
size_t scan_index = 0;
|
||||||
|
bool is_progressive = false;
|
||||||
|
bool ac_ok[kMaxHuffmanTables] = {false};
|
||||||
|
bool dc_ok[kMaxHuffmanTables] = {false};
|
||||||
|
for (uint8_t marker : marker_order) {
|
||||||
|
if (marker == 0xC2) {
|
||||||
|
is_progressive = true;
|
||||||
|
} else if (marker == 0xC4) {
|
||||||
|
for (; dht_index < huffman_code.size();) {
|
||||||
|
const JPEGHuffmanCode& huff = huffman_code[dht_index++];
|
||||||
|
size_t index = huff.slot_id;
|
||||||
|
if (index & 0x10) {
|
||||||
|
index -= 0x10;
|
||||||
|
ac_ok[index] = true;
|
||||||
|
} else {
|
||||||
|
dc_ok[index] = true;
|
||||||
|
}
|
||||||
|
if (huff.is_last) break;
|
||||||
|
}
|
||||||
|
} else if (marker == 0xDA) {
|
||||||
|
const JPEGScanInfo& si = scan_info[scan_index++];
|
||||||
|
for (size_t i = 0; i < si.num_components; ++i) {
|
||||||
|
const JPEGComponentScanInfo& csi = si.components[i];
|
||||||
|
size_t dc_tbl_idx = csi.dc_tbl_idx;
|
||||||
|
size_t ac_tbl_idx = csi.ac_tbl_idx;
|
||||||
|
bool want_dc = !is_progressive || (si.Ss == 0);
|
||||||
|
if (want_dc && !dc_ok[dc_tbl_idx]) {
|
||||||
|
return JXL_FAILURE("DC Huffman table used before defined");
|
||||||
|
}
|
||||||
|
bool want_ac = !is_progressive || (si.Ss != 0) || (si.Se != 0);
|
||||||
|
if (want_ac && !ac_ok[ac_tbl_idx]) {
|
||||||
|
return JXL_FAILURE("AC Huffman table used before defined");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply postponed actions.
|
// Apply postponed actions.
|
||||||
if (visitor->IsReading()) {
|
if (visitor->IsReading()) {
|
||||||
tail_data.resize(tail_data_len);
|
tail_data.resize(tail_data_len);
|
||||||
|
|
|
@ -122,8 +122,8 @@ TEST(JxlTest, RoundtripSmallD1) {
|
||||||
|
|
||||||
{
|
{
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 766, 40);
|
EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 816, 40);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.2));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.888));
|
||||||
}
|
}
|
||||||
|
|
||||||
// With a lower intensity target than the default, the bitrate should be
|
// With a lower intensity target than the default, the bitrate should be
|
||||||
|
@ -203,7 +203,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessing) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_EPF, 3);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 23553, 400);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 22999, 400);
|
||||||
EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 1.35);
|
EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 1.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, 2);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 10907, 200);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 11015, 200);
|
||||||
EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 2.9);
|
EXPECT_LE(ButteraugliDistance(t.ppf(), ppf_out), 2.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,9 +296,9 @@ TEST(JxlTest, RoundtripMultiGroup) {
|
||||||
};
|
};
|
||||||
|
|
||||||
auto run_kitten = std::async(std::launch::async, test, SpeedTier::kKitten,
|
auto run_kitten = std::async(std::launch::async, test, SpeedTier::kKitten,
|
||||||
1.0f, 56570u, 11.7);
|
1.0f, 55602u, 11.7);
|
||||||
auto run_wombat = std::async(std::launch::async, test, SpeedTier::kWombat,
|
auto run_wombat = std::async(std::launch::async, test, SpeedTier::kWombat,
|
||||||
2.0f, 35274u, 20.0);
|
2.0f, 33624u, 20.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(JxlTest, RoundtripRGBToGrayscale) {
|
TEST(JxlTest, RoundtripRGBToGrayscale) {
|
||||||
|
@ -357,7 +357,7 @@ TEST(JxlTest, RoundtripLargeFast) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 453713, 5000);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 445555, 5000);
|
||||||
EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(100));
|
EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ TEST(JxlTest, RoundtripDotsForceEpf) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 41087, 300);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 41777, 300);
|
||||||
EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(18));
|
EXPECT_THAT(ComputeDistance2(t.ppf(), ppf_out), IsSlightlyBelow(18));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +454,7 @@ TEST(JxlTest, RoundtripSmallNL) {
|
||||||
t.SetDimensions(xsize, ysize);
|
t.SetDimensions(xsize, ysize);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 753, 45);
|
EXPECT_NEAR(Roundtrip(t.ppf(), {}, {}, pool, &ppf_out), 801, 45);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.1));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +470,7 @@ TEST(JxlTest, RoundtripNoGaborishNoAR) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, 0);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 38009, 200);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 38900, 200);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.8));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +515,7 @@ TEST(JxlTest, RoundtripSmallPatchesAlpha) {
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 597, 100);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 597, 100);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.015f));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.018f));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(JxlTest, RoundtripSmallPatches) {
|
TEST(JxlTest, RoundtripSmallPatches) {
|
||||||
|
@ -540,7 +540,7 @@ TEST(JxlTest, RoundtripSmallPatches) {
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 486, 100);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 486, 100);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.015f));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.018f));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(szabadka) Add encoder and decoder API functions that accept frame
|
// TODO(szabadka) Add encoder and decoder API functions that accept frame
|
||||||
|
@ -846,14 +846,14 @@ TEST(JxlTest, RoundtripAlphaPremultiplied) {
|
||||||
EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames,
|
EXPECT_THAT(ButteraugliDistance(io.frames, io2.frames,
|
||||||
ButteraugliParams(), GetJxlCms(),
|
ButteraugliParams(), GetJxlCms(),
|
||||||
/*distmap=*/nullptr),
|
/*distmap=*/nullptr),
|
||||||
IsSlightlyBelow(1.2));
|
IsSlightlyBelow(1.111));
|
||||||
EXPECT_TRUE(UnpremultiplyAlpha(io2));
|
EXPECT_TRUE(UnpremultiplyAlpha(io2));
|
||||||
EXPECT_FALSE(io2.Main().AlphaIsPremultiplied());
|
EXPECT_FALSE(io2.Main().AlphaIsPremultiplied());
|
||||||
}
|
}
|
||||||
EXPECT_THAT(ButteraugliDistance(io_nopremul.frames, io2.frames,
|
EXPECT_THAT(ButteraugliDistance(io_nopremul.frames, io2.frames,
|
||||||
ButteraugliParams(), GetJxlCms(),
|
ButteraugliParams(), GetJxlCms(),
|
||||||
/*distmap=*/nullptr),
|
/*distmap=*/nullptr),
|
||||||
IsSlightlyBelow(1.47));
|
IsSlightlyBelow(1.55));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -874,7 +874,7 @@ TEST(JxlTest, RoundtripAlphaResampling) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 2);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 12745, 130);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 13155, 130);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(5.2));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(5.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,7 +1129,7 @@ TEST(JxlTest, RoundtripDots) {
|
||||||
cparams.distance = 0.04;
|
cparams.distance = 0.04;
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 277976, 4000);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 273333, 4000);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.35));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(0.35));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1149,8 +1149,8 @@ TEST(JxlTest, RoundtripNoise) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_NOISE, 1);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_NOISE, 1);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 38244, 750);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool, &ppf_out), 39261, 750);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.3));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.35));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(JxlTest, RoundtripLossless8Gray) {
|
TEST(JxlTest, RoundtripLossless8Gray) {
|
||||||
|
@ -1262,6 +1262,7 @@ size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
|
||||||
&compressed));
|
&compressed));
|
||||||
|
|
||||||
jxl::JXLDecompressParams dparams;
|
jxl::JXLDecompressParams dparams;
|
||||||
|
test::DefaultAcceptedFormats(dparams);
|
||||||
test::SetThreadParallelRunner(dparams, pool);
|
test::SetThreadParallelRunner(dparams, pool);
|
||||||
std::vector<uint8_t> out;
|
std::vector<uint8_t> out;
|
||||||
jxl::PackedPixelFile ppf;
|
jxl::PackedPixelFile ppf;
|
||||||
|
@ -1290,6 +1291,7 @@ void RoundtripJpegToPixels(const PaddedBytes& jpeg_in,
|
||||||
EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_bytes,
|
EXPECT_TRUE(extras::EncodeImageJXL({}, extras::PackedPixelFile(), &jpeg_bytes,
|
||||||
&compressed));
|
&compressed));
|
||||||
|
|
||||||
|
test::DefaultAcceptedFormats(dparams);
|
||||||
test::SetThreadParallelRunner(dparams, pool);
|
test::SetThreadParallelRunner(dparams, pool);
|
||||||
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams,
|
||||||
nullptr, ppf_out, nullptr));
|
nullptr, ppf_out, nullptr));
|
||||||
|
@ -1473,7 +1475,7 @@ TEST(JxlTest, RoundtripProgressive) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 63570, 750);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 62160, 750);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.4));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1490,7 +1492,7 @@ TEST(JxlTest, RoundtripProgressiveLevel2Slow) {
|
||||||
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
|
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
|
||||||
|
|
||||||
PackedPixelFile ppf_out;
|
PackedPixelFile ppf_out;
|
||||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 74621, 1000);
|
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 71111, 1000);
|
||||||
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.17));
|
EXPECT_THAT(ButteraugliDistance(t.ppf(), ppf_out), IsSlightlyBelow(1.17));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
||||||
for (size_t x = 0; x < channel.w; x++) {
|
for (size_t x = 0; x < channel.w; x++) {
|
||||||
uint32_t v =
|
uint32_t v =
|
||||||
reader->ReadHybridUintClustered<uses_lz77>(ctx_id, br);
|
reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
|
||||||
r[x] = UnpackSigned(v);
|
r[x] = UnpackSigned(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,8 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
||||||
for (size_t x = 0; x < channel.w; x++) {
|
for (size_t x = 0; x < channel.w; x++) {
|
||||||
uint32_t v =
|
uint32_t v =
|
||||||
reader->ReadHybridUintClustered<uses_lz77>(ctx_id, br);
|
reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id,
|
||||||
|
br);
|
||||||
r[x] = make_pixel(v, multiplier, offset);
|
r[x] = make_pixel(v, multiplier, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +256,8 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
pixel_type top = (y ? *(r + x - onerow) : left);
|
pixel_type top = (y ? *(r + x - onerow) : left);
|
||||||
pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
|
pixel_type topleft = (x && y ? *(r + x - 1 - onerow) : left);
|
||||||
pixel_type guess = ClampedGradient(top, left, topleft);
|
pixel_type guess = ClampedGradient(top, left, topleft);
|
||||||
uint64_t v = reader->ReadHybridUintClustered<uses_lz77>(ctx_id, br);
|
uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
|
||||||
|
ctx_id, br);
|
||||||
r[x] = make_pixel(v, 1, guess);
|
r[x] = make_pixel(v, 1, guess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,34 +295,70 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
|
std::max<pixel_type_w>(-kPropRangeFast, top + left - topleft),
|
||||||
kPropRangeFast - 1);
|
kPropRangeFast - 1);
|
||||||
uint32_t ctx_id = context_lookup[pos];
|
uint32_t ctx_id = context_lookup[pos];
|
||||||
uint64_t v = reader->ReadHybridUintClustered<uses_lz77>(ctx_id, br);
|
uint64_t v =
|
||||||
|
reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(ctx_id, br);
|
||||||
r[x] = make_pixel(v, multipliers[pos],
|
r[x] = make_pixel(v, multipliers[pos],
|
||||||
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!uses_lz77 && is_wp_only) {
|
} else if (!uses_lz77 && is_wp_only && channel.w > 8) {
|
||||||
JXL_DEBUG_V(8, "WP fast track.");
|
JXL_DEBUG_V(8, "WP fast track.");
|
||||||
const intptr_t onerow = channel.plane.PixelsPerRow();
|
|
||||||
weighted::State wp_state(wp_header, channel.w, channel.h);
|
weighted::State wp_state(wp_header, channel.w, channel.h);
|
||||||
Properties properties(1);
|
Properties properties(1);
|
||||||
for (size_t y = 0; y < channel.h; y++) {
|
for (size_t y = 0; y < channel.h; y++) {
|
||||||
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
||||||
for (size_t x = 0; x < channel.w; x++) {
|
const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
|
||||||
|
const pixel_type *JXL_RESTRICT rtoptop =
|
||||||
|
(y > 1 ? channel.Row(y - 2) : rtop);
|
||||||
|
const pixel_type *JXL_RESTRICT rtopleft =
|
||||||
|
(y ? channel.Row(y - 1) - 1 : r - 1);
|
||||||
|
const pixel_type *JXL_RESTRICT rtopright =
|
||||||
|
(y ? channel.Row(y - 1) + 1 : r - 1);
|
||||||
|
size_t x = 0;
|
||||||
|
{
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
pixel_type_w left = (x ? r[x - 1] : y ? *(r + x - onerow) : 0);
|
pixel_type_w left = y ? rtop[x] : 0;
|
||||||
pixel_type_w top = (y ? *(r + x - onerow) : left);
|
pixel_type_w toptop = y ? rtoptop[x] : 0;
|
||||||
pixel_type_w topleft = (x && y ? *(r + x - 1 - onerow) : left);
|
pixel_type_w topright = (x + 1 < channel.w && y ? rtop[x + 1] : left);
|
||||||
pixel_type_w topright =
|
|
||||||
(x + 1 < channel.w && y ? *(r + x + 1 - onerow) : top);
|
|
||||||
pixel_type_w toptop = (y > 1 ? *(r + x - onerow - onerow) : top);
|
|
||||||
int32_t guess = wp_state.Predict</*compute_properties=*/true>(
|
int32_t guess = wp_state.Predict</*compute_properties=*/true>(
|
||||||
x, y, channel.w, top, left, topright, topleft, toptop, &properties,
|
x, y, channel.w, left, left, topright, left, toptop, &properties,
|
||||||
offset);
|
offset);
|
||||||
uint32_t pos =
|
uint32_t pos =
|
||||||
kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
|
kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
|
||||||
kPropRangeFast - 1);
|
kPropRangeFast - 1);
|
||||||
uint32_t ctx_id = context_lookup[pos];
|
uint32_t ctx_id = context_lookup[pos];
|
||||||
uint64_t v = reader->ReadHybridUintClustered<uses_lz77>(ctx_id, br);
|
uint64_t v =
|
||||||
|
reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
|
||||||
|
r[x] = make_pixel(v, multipliers[pos],
|
||||||
|
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
||||||
|
wp_state.UpdateErrors(r[x], x, y, channel.w);
|
||||||
|
}
|
||||||
|
for (x = 1; x + 1 < channel.w; x++) {
|
||||||
|
size_t offset = 0;
|
||||||
|
int32_t guess = wp_state.Predict</*compute_properties=*/true>(
|
||||||
|
x, y, channel.w, rtop[x], r[x - 1], rtopright[x], rtopleft[x],
|
||||||
|
rtoptop[x], &properties, offset);
|
||||||
|
uint32_t pos =
|
||||||
|
kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
|
||||||
|
kPropRangeFast - 1);
|
||||||
|
uint32_t ctx_id = context_lookup[pos];
|
||||||
|
uint64_t v =
|
||||||
|
reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
|
||||||
|
r[x] = make_pixel(v, multipliers[pos],
|
||||||
|
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
||||||
|
wp_state.UpdateErrors(r[x], x, y, channel.w);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
size_t offset = 0;
|
||||||
|
int32_t guess = wp_state.Predict</*compute_properties=*/true>(
|
||||||
|
x, y, channel.w, rtop[x], r[x - 1], rtop[x], rtopleft[x],
|
||||||
|
rtoptop[x], &properties, offset);
|
||||||
|
uint32_t pos =
|
||||||
|
kPropRangeFast + std::min(std::max(-kPropRangeFast, properties[0]),
|
||||||
|
kPropRangeFast - 1);
|
||||||
|
uint32_t ctx_id = context_lookup[pos];
|
||||||
|
uint64_t v =
|
||||||
|
reader->ReadHybridUintClusteredInlined<uses_lz77>(ctx_id, br);
|
||||||
r[x] = make_pixel(v, multipliers[pos],
|
r[x] = make_pixel(v, multipliers[pos],
|
||||||
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
static_cast<pixel_type_w>(offsets[pos]) + guess);
|
||||||
wp_state.UpdateErrors(r[x], x, y, channel.w);
|
wp_state.UpdateErrors(r[x], x, y, channel.w);
|
||||||
|
@ -351,8 +389,8 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
PredictionResult res =
|
PredictionResult res =
|
||||||
PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
|
PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
|
||||||
tree_lookup, references);
|
tree_lookup, references);
|
||||||
uint64_t v =
|
uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
|
||||||
reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
|
res.context, br);
|
||||||
p[x] = make_pixel(v, res.multiplier, res.guess);
|
p[x] = make_pixel(v, res.multiplier, res.guess);
|
||||||
}
|
}
|
||||||
for (size_t x = channel.w - 2; x < channel.w; x++) {
|
for (size_t x = channel.w - 2; x < channel.w; x++) {
|
||||||
|
@ -368,8 +406,8 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
PredictionResult res =
|
PredictionResult res =
|
||||||
PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
|
PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
|
||||||
tree_lookup, references);
|
tree_lookup, references);
|
||||||
uint64_t v =
|
uint64_t v = reader->ReadHybridUintClusteredMaybeInlined<uses_lz77>(
|
||||||
reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
|
res.context, br);
|
||||||
p[x] = make_pixel(v, res.multiplier, res.guess);
|
p[x] = make_pixel(v, res.multiplier, res.guess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,8 +437,8 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
||||||
PredictionResult res =
|
PredictionResult res =
|
||||||
PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
|
PredictTreeWPNEC(&properties, channel.w, p + x, onerow, x, y,
|
||||||
tree_lookup, references, &wp_state);
|
tree_lookup, references, &wp_state);
|
||||||
uint64_t v =
|
uint64_t v = reader->ReadHybridUintClusteredInlined<uses_lz77>(
|
||||||
reader->ReadHybridUintClustered<uses_lz77>(res.context, br);
|
res.context, br);
|
||||||
p[x] = make_pixel(v, res.multiplier, res.guess);
|
p[x] = make_pixel(v, res.multiplier, res.guess);
|
||||||
wp_state.UpdateErrors(p[x], x, y, channel.w);
|
wp_state.UpdateErrors(p[x], x, y, channel.w);
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,7 +232,7 @@ TEST_P(RenderPipelineTestParam, PipelineTest) {
|
||||||
#if JXL_HIGH_PRECISION
|
#if JXL_HIGH_PRECISION
|
||||||
constexpr float kMaxError = 1e-5;
|
constexpr float kMaxError = 1e-5;
|
||||||
#else
|
#else
|
||||||
constexpr float kMaxError = 1e-4;
|
constexpr float kMaxError = 5e-4;
|
||||||
#endif
|
#endif
|
||||||
Image3F def = std::move(*io_default.frames[i].color());
|
Image3F def = std::move(*io_default.frames[i].color());
|
||||||
Image3F pip = std::move(*io_slow_pipeline.frames[i].color());
|
Image3F pip = std::move(*io_slow_pipeline.frames[i].color());
|
||||||
|
|
|
@ -641,6 +641,9 @@ Status Splines::InitializeDrawCache(const size_t image_xsize,
|
||||||
JXL_WARNING(
|
JXL_WARNING(
|
||||||
"Large total_estimated_area_reached, expect slower decoding: %" PRIu64,
|
"Large total_estimated_area_reached, expect slower decoding: %" PRIu64,
|
||||||
total_estimated_area_reached);
|
total_estimated_area_reached);
|
||||||
|
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||||
|
return JXL_FAILURE("Total spline area is too large");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Spline& spline : splines) {
|
for (Spline& spline : splines) {
|
||||||
|
|
|
@ -56,9 +56,19 @@ PaddedBytes ReadTestData(const std::string& filename) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams) {
|
||||||
|
if (dparams.accepted_formats.empty()) {
|
||||||
|
for (const uint32_t num_channels : {1, 2, 3, 4}) {
|
||||||
|
dparams.accepted_formats.push_back(
|
||||||
|
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Status DecodeFile(extras::JXLDecompressParams dparams,
|
Status DecodeFile(extras::JXLDecompressParams dparams,
|
||||||
const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io,
|
const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io,
|
||||||
ThreadPool* pool) {
|
ThreadPool* pool) {
|
||||||
|
DefaultAcceptedFormats(dparams);
|
||||||
SetThreadParallelRunner(dparams, pool);
|
SetThreadParallelRunner(dparams, pool);
|
||||||
extras::PackedPixelFile ppf;
|
extras::PackedPixelFile ppf;
|
||||||
JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), dparams,
|
JXL_RETURN_IF_ERROR(DecodeImageJXL(file.data(), file.size(), dparams,
|
||||||
|
@ -139,6 +149,7 @@ bool Roundtrip(const CodecInOut* io, const CompressParams& cparams,
|
||||||
extras::JXLDecompressParams dparams,
|
extras::JXLDecompressParams dparams,
|
||||||
CodecInOut* JXL_RESTRICT io2, std::stringstream& failures,
|
CodecInOut* JXL_RESTRICT io2, std::stringstream& failures,
|
||||||
size_t* compressed_size, ThreadPool* pool, AuxOut* aux_out) {
|
size_t* compressed_size, ThreadPool* pool, AuxOut* aux_out) {
|
||||||
|
DefaultAcceptedFormats(dparams);
|
||||||
if (compressed_size) {
|
if (compressed_size) {
|
||||||
*compressed_size = static_cast<size_t>(-1);
|
*compressed_size = static_cast<size_t>(-1);
|
||||||
}
|
}
|
||||||
|
@ -203,6 +214,7 @@ size_t Roundtrip(const extras::PackedPixelFile& ppf_in,
|
||||||
extras::JXLCompressParams cparams,
|
extras::JXLCompressParams cparams,
|
||||||
extras::JXLDecompressParams dparams, ThreadPool* pool,
|
extras::JXLDecompressParams dparams, ThreadPool* pool,
|
||||||
extras::PackedPixelFile* ppf_out) {
|
extras::PackedPixelFile* ppf_out) {
|
||||||
|
DefaultAcceptedFormats(dparams);
|
||||||
SetThreadParallelRunner(cparams, pool);
|
SetThreadParallelRunner(cparams, pool);
|
||||||
SetThreadParallelRunner(dparams, pool);
|
SetThreadParallelRunner(dparams, pool);
|
||||||
std::vector<uint8_t> compressed;
|
std::vector<uint8_t> compressed;
|
||||||
|
|
|
@ -50,6 +50,8 @@ PaddedBytes ReadTestData(const std::string& filename);
|
||||||
void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info,
|
void JxlBasicInfoSetFromPixelFormat(JxlBasicInfo* basic_info,
|
||||||
const JxlPixelFormat* pixel_format);
|
const JxlPixelFormat* pixel_format);
|
||||||
|
|
||||||
|
void DefaultAcceptedFormats(extras::JXLDecompressParams& dparams);
|
||||||
|
|
||||||
template <typename Params>
|
template <typename Params>
|
||||||
void SetThreadParallelRunner(Params params, ThreadPool* pool) {
|
void SetThreadParallelRunner(Params params, ThreadPool* pool) {
|
||||||
if (pool && !params.runner_opaque) {
|
if (pool && !params.runner_opaque) {
|
||||||
|
|
|
@ -358,8 +358,6 @@ bool JpegXlSaveGui::SaveDialog() {
|
||||||
gtk_widget_show(separator);
|
gtk_widget_show(separator);
|
||||||
|
|
||||||
// Advanced Settings Frame
|
// Advanced Settings Frame
|
||||||
std::vector<GtkWidget*> advanced_opts;
|
|
||||||
|
|
||||||
frame_advanced = gtk_frame_new("Advanced Settings");
|
frame_advanced = gtk_frame_new("Advanced Settings");
|
||||||
gimp_help_set_help_data(frame_advanced,
|
gimp_help_set_help_data(frame_advanced,
|
||||||
"Some advanced settings may produce malformed files.",
|
"Some advanced settings may produce malformed files.",
|
||||||
|
|
|
@ -31,7 +31,7 @@ if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/highway/CMakeLists.txt" AND
|
||||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/highway/LICENSE"
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/highway/LICENSE"
|
||||||
${PROJECT_BINARY_DIR}/LICENSE.highway COPYONLY)
|
${PROJECT_BINARY_DIR}/LICENSE.highway COPYONLY)
|
||||||
else()
|
else()
|
||||||
find_package(HWY 1.0.0)
|
find_package(HWY 1.0.4)
|
||||||
if (NOT HWY_FOUND)
|
if (NOT HWY_FOUND)
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
"Highway library (hwy) not found. Install libhwy-dev or download it "
|
"Highway library (hwy) not found. Install libhwy-dev or download it "
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
build/
|
Загрузка…
Ссылка в новой задаче