summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/jpeg-xl/lib/jpegli/decode_api_test.cc')
-rw-r--r--third_party/jpeg-xl/lib/jpegli/decode_api_test.cc1305
1 files changed, 1305 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
new file mode 100644
index 0000000000..0bb7321db4
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
@@ -0,0 +1,1305 @@
+// 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 <cmath>
+#include <vector>
+
+#include "lib/jpegli/decode.h"
+#include "lib/jpegli/encode.h"
+#include "lib/jpegli/test_utils.h"
+#include "lib/jpegli/testing.h"
+#include "lib/jxl/base/byte_order.h"
+#include "lib/jxl/base/file_io.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/sanitizers.h"
+
+namespace jpegli {
+namespace {
+
+static constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
+static constexpr size_t kNumSourceBuffers = 4;
+
+// Custom source manager that refills the input buffer in chunks, simulating
+// a file reader with a fixed buffer size.
+class SourceManager {
+ public:
+ SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size)
+ : data_(data), len_(len), max_chunk_size_(max_chunk_size) {
+ pub_.skip_input_data = skip_input_data;
+ pub_.resync_to_restart = jpegli_resync_to_restart;
+ pub_.term_source = term_source;
+ pub_.init_source = init_source;
+ pub_.fill_input_buffer = fill_input_buffer;
+ if (max_chunk_size_ == 0) max_chunk_size_ = len;
+ buffers_.resize(kNumSourceBuffers, std::vector<uint8_t>(max_chunk_size_));
+ Reset();
+ }
+
+ void Reset() {
+ pub_.next_input_byte = nullptr;
+ pub_.bytes_in_buffer = 0;
+ pos_ = 0;
+ chunk_idx_ = 0;
+ }
+
+ ~SourceManager() {
+ EXPECT_EQ(0, pub_.bytes_in_buffer);
+ EXPECT_EQ(len_, pos_);
+ }
+
+ private:
+ jpeg_source_mgr pub_;
+ const uint8_t* data_;
+ size_t len_;
+ size_t chunk_idx_;
+ size_t pos_;
+ size_t max_chunk_size_;
+ std::vector<std::vector<uint8_t>> buffers_;
+
+ static void init_source(j_decompress_ptr cinfo) {}
+
+ static boolean fill_input_buffer(j_decompress_ptr cinfo) {
+ auto src = reinterpret_cast<SourceManager*>(cinfo->src);
+ if (src->pos_ < src->len_) {
+ size_t chunk_size = std::min(src->len_ - src->pos_, src->max_chunk_size_);
+ size_t next_idx = ++src->chunk_idx_ % kNumSourceBuffers;
+ uint8_t* next_buffer = src->buffers_[next_idx].data();
+ memcpy(next_buffer, src->data_ + src->pos_, chunk_size);
+ src->pub_.next_input_byte = next_buffer;
+ src->pub_.bytes_in_buffer = chunk_size;
+ } else {
+ src->pub_.next_input_byte = kFakeEoiMarker;
+ src->pub_.bytes_in_buffer = 2;
+ src->len_ += 2;
+ }
+ src->pos_ += src->pub_.bytes_in_buffer;
+ return TRUE;
+ }
+
+ static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ auto src = reinterpret_cast<SourceManager*>(cinfo->src);
+ if (num_bytes <= 0) {
+ return;
+ }
+ if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) {
+ src->pub_.bytes_in_buffer -= num_bytes;
+ src->pub_.next_input_byte += num_bytes;
+ } else {
+ src->pos_ += num_bytes - src->pub_.bytes_in_buffer;
+ src->pub_.bytes_in_buffer = 0;
+ }
+ }
+
+ static void term_source(j_decompress_ptr cinfo) {}
+};
+
+uint8_t markers_seen[kMarkerSequenceLen];
+size_t num_markers_seen = 0;
+
+uint8_t get_next_byte(j_decompress_ptr cinfo) {
+ if (cinfo->src->bytes_in_buffer == 0) {
+ (*cinfo->src->fill_input_buffer)(cinfo);
+ }
+ cinfo->src->bytes_in_buffer--;
+ return *cinfo->src->next_input_byte++;
+}
+
+boolean test_marker_processor(j_decompress_ptr cinfo) {
+ markers_seen[num_markers_seen] = cinfo->unread_marker;
+ size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo);
+ EXPECT_EQ(2 + ((num_markers_seen + 2) % sizeof(kMarkerData)), marker_len);
+ if (marker_len > 2) {
+ (*cinfo->src->skip_input_data)(cinfo, marker_len - 2);
+ }
+ ++num_markers_seen;
+ return TRUE;
+}
+
+void ReadOutputImage(const DecompressParams& dparams, j_decompress_ptr cinfo,
+ TestImage* output) {
+ JDIMENSION xoffset = 0;
+ JDIMENSION yoffset = 0;
+ JDIMENSION xsize_cropped = cinfo->output_width;
+ JDIMENSION ysize_cropped = cinfo->output_height;
+ if (dparams.crop_output) {
+ xoffset = xsize_cropped = cinfo->output_width / 3;
+ yoffset = ysize_cropped = cinfo->output_height / 3;
+ jpegli_crop_scanline(cinfo, &xoffset, &xsize_cropped);
+ }
+ output->ysize = ysize_cropped;
+ output->xsize = cinfo->output_width;
+ output->components = cinfo->out_color_components;
+ output->data_type = dparams.data_type;
+ output->endianness = dparams.endianness;
+ size_t bytes_per_sample = jpegli_bytes_per_sample(dparams.data_type);
+ if (cinfo->raw_data_out) {
+ output->color_space = cinfo->jpeg_color_space;
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
+ size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
+ std::vector<uint8_t> plane(ysize * xsize * bytes_per_sample);
+ output->raw_data.emplace_back(std::move(plane));
+ }
+ } else {
+ output->color_space = cinfo->out_color_space;
+ output->AllocatePixels();
+ }
+ size_t total_output_lines = 0;
+ while (cinfo->output_scanline < cinfo->output_height) {
+ size_t max_lines;
+ size_t num_output_lines;
+ if (cinfo->raw_data_out) {
+ size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
+ EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height);
+ max_lines = iMCU_height;
+ std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
+ std::vector<JSAMPARRAY> data(cinfo->num_components);
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
+ size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
+ size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE;
+ rowdata[c].resize(num_lines);
+ size_t y0 = cinfo->output_iMCU_row * num_lines;
+ for (size_t i = 0; i < num_lines; ++i) {
+ rowdata[c][i] =
+ y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr;
+ }
+ data[c] = &rowdata[c][0];
+ }
+ num_output_lines = jpegli_read_raw_data(cinfo, &data[0], max_lines);
+ } else {
+ size_t max_output_lines = dparams.max_output_lines;
+ if (max_output_lines == 0) max_output_lines = cinfo->output_height;
+ if (cinfo->output_scanline < yoffset) {
+ max_lines = yoffset - cinfo->output_scanline;
+ num_output_lines = jpegli_skip_scanlines(cinfo, max_lines);
+ } else if (cinfo->output_scanline >= yoffset + ysize_cropped) {
+ max_lines = cinfo->output_height - cinfo->output_scanline;
+ num_output_lines = jpegli_skip_scanlines(cinfo, max_lines);
+ } else {
+ size_t lines_left = yoffset + ysize_cropped - cinfo->output_scanline;
+ max_lines = std::min<size_t>(max_output_lines, lines_left);
+ size_t stride = cinfo->output_width * cinfo->out_color_components *
+ bytes_per_sample;
+ std::vector<JSAMPROW> scanlines(max_lines);
+ for (size_t i = 0; i < max_lines; ++i) {
+ size_t yidx = cinfo->output_scanline - yoffset + i;
+ scanlines[i] = &output->pixels[yidx * stride];
+ }
+ num_output_lines =
+ jpegli_read_scanlines(cinfo, &scanlines[0], max_lines);
+ if (cinfo->quantize_colors) {
+ for (size_t i = 0; i < num_output_lines; ++i) {
+ UnmapColors(scanlines[i], cinfo->output_width,
+ cinfo->out_color_components, cinfo->colormap,
+ cinfo->actual_number_of_colors);
+ }
+ }
+ }
+ }
+ total_output_lines += num_output_lines;
+ EXPECT_EQ(total_output_lines, cinfo->output_scanline);
+ EXPECT_EQ(num_output_lines, max_lines);
+ }
+ EXPECT_EQ(cinfo->total_iMCU_rows,
+ DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE));
+}
+
+struct TestConfig {
+ std::string fn;
+ std::string fn_desc;
+ TestImage input;
+ CompressParams jparams;
+ DecompressParams dparams;
+ bool compare_to_orig = false;
+ float max_tolerance_factor = 1.01f;
+ float max_rms_dist = 1.0f;
+ float max_diff = 35.0f;
+};
+
+std::vector<uint8_t> GetTestJpegData(TestConfig& config) {
+ std::vector<uint8_t> compressed;
+ if (!config.fn.empty()) {
+ compressed = ReadTestData(config.fn.c_str());
+ } else {
+ GeneratePixels(&config.input);
+ JXL_CHECK(EncodeWithJpegli(config.input, config.jparams, &compressed));
+ }
+ if (config.dparams.size_factor < 1.0f) {
+ compressed.resize(compressed.size() * config.dparams.size_factor);
+ }
+ return compressed;
+}
+
+void TestAPINonBuffered(const CompressParams& jparams,
+ const DecompressParams& dparams,
+ const TestImage& expected_output,
+ j_decompress_ptr cinfo, TestImage* output) {
+ if (jparams.add_marker) {
+ jpegli_save_markers(cinfo, kSpecialMarker0, 0xffff);
+ jpegli_save_markers(cinfo, kSpecialMarker1, 0xffff);
+ num_markers_seen = 0;
+ jpegli_set_marker_processor(cinfo, 0xe6, test_marker_processor);
+ jpegli_set_marker_processor(cinfo, 0xe7, test_marker_processor);
+ jpegli_set_marker_processor(cinfo, 0xe8, test_marker_processor);
+ }
+ if (!jparams.icc.empty()) {
+ jpegli_save_markers(cinfo, JPEG_APP0 + 2, 0xffff);
+ }
+ jpegli_read_header(cinfo, /*require_image=*/TRUE);
+ if (jparams.add_marker) {
+ EXPECT_EQ(num_markers_seen, kMarkerSequenceLen);
+ EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen));
+ }
+ if (!jparams.icc.empty()) {
+ uint8_t* icc_data = nullptr;
+ unsigned int icc_len;
+ JXL_CHECK(jpegli_read_icc_profile(cinfo, &icc_data, &icc_len));
+ JXL_CHECK(icc_data);
+ EXPECT_EQ(0, memcmp(jparams.icc.data(), icc_data, icc_len));
+ free(icc_data);
+ }
+ // Check that jpegli_calc_output_dimensions can be called multiple times
+ // even with different parameters.
+ if (!cinfo->raw_data_out) {
+ cinfo->scale_num = 1;
+ cinfo->scale_denom = 2;
+ }
+ jpegli_calc_output_dimensions(cinfo);
+ SetDecompressParams(dparams, cinfo, /*is_jpegli=*/true);
+ VerifyHeader(jparams, cinfo);
+ jpegli_calc_output_dimensions(cinfo);
+ EXPECT_LE(expected_output.xsize, cinfo->output_width);
+ if (!dparams.crop_output) {
+ EXPECT_EQ(expected_output.xsize, cinfo->output_width);
+ }
+ if (dparams.output_mode == COEFFICIENTS) {
+ jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo);
+ JXL_CHECK(coef_arrays != nullptr);
+ CopyCoefficients(cinfo, coef_arrays, output);
+ } else {
+ jpegli_start_decompress(cinfo);
+ VerifyScanHeader(jparams, cinfo);
+ ReadOutputImage(dparams, cinfo, output);
+ }
+ jpegli_finish_decompress(cinfo);
+}
+
+void TestAPIBuffered(const CompressParams& jparams,
+ const DecompressParams& dparams, j_decompress_ptr cinfo,
+ std::vector<TestImage>* output_progression) {
+ EXPECT_EQ(JPEG_REACHED_SOS,
+ jpegli_read_header(cinfo, /*require_image=*/TRUE));
+ cinfo->buffered_image = TRUE;
+ SetDecompressParams(dparams, cinfo, /*is_jpegli=*/true);
+ VerifyHeader(jparams, cinfo);
+ EXPECT_TRUE(jpegli_start_decompress(cinfo));
+ // start decompress should not read the whole input in buffered image mode
+ EXPECT_FALSE(jpegli_input_complete(cinfo));
+ bool has_multiple_scans = jpegli_has_multiple_scans(cinfo);
+ EXPECT_EQ(0, cinfo->output_scan_number);
+ int sos_marker_cnt = 1; // read_header reads the first SOS marker
+ while (!jpegli_input_complete(cinfo)) {
+ EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
+ if (dparams.skip_scans && (cinfo->input_scan_number % 2) != 1) {
+ int result = JPEG_SUSPENDED;
+ while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) {
+ result = jpegli_consume_input(cinfo);
+ }
+ if (result == JPEG_REACHED_SOS) ++sos_marker_cnt;
+ continue;
+ }
+ SetScanDecompressParams(dparams, cinfo, cinfo->input_scan_number,
+ /*is_jpegli=*/true);
+ EXPECT_TRUE(jpegli_start_output(cinfo, cinfo->input_scan_number));
+ // start output sets output_scan_number, but does not change
+ // input_scan_number
+ EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number);
+ EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
+ VerifyScanHeader(jparams, cinfo);
+ TestImage output;
+ ReadOutputImage(dparams, cinfo, &output);
+ output_progression->emplace_back(std::move(output));
+ // read scanlines/read raw data does not change input/output scan number
+ EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt);
+ EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number);
+ EXPECT_TRUE(jpegli_finish_output(cinfo));
+ ++sos_marker_cnt; // finish output reads the next SOS marker or EOI
+ if (dparams.output_mode == COEFFICIENTS) {
+ jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo);
+ JXL_CHECK(coef_arrays != nullptr);
+ CopyCoefficients(cinfo, coef_arrays, &output_progression->back());
+ }
+ }
+ jpegli_finish_decompress(cinfo);
+ if (dparams.size_factor == 1.0f) {
+ EXPECT_EQ(has_multiple_scans, cinfo->input_scan_number > 1);
+ }
+}
+
+TEST(DecodeAPITest, ReuseCinfo) {
+ TestImage input, output, expected;
+ std::vector<TestImage> output_progression, expected_output_progression;
+ CompressParams jparams;
+ DecompressParams dparams;
+ std::vector<uint8_t> compressed;
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ input.xsize = 129;
+ input.ysize = 73;
+ GeneratePixels(&input);
+ for (int h_samp : {2, 1}) {
+ for (int v_samp : {2, 1}) {
+ for (int progr : {0, 2}) {
+ jparams.h_sampling = {h_samp, 1, 1};
+ jparams.v_sampling = {v_samp, 1, 1};
+ jparams.progressive_mode = progr;
+ printf(
+ "Generating input with %dx%d chroma subsampling "
+ "progressive level %d\n",
+ h_samp, v_samp, progr);
+ JXL_CHECK(EncodeWithJpegli(input, jparams, &compressed));
+ for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) {
+ for (bool crop : {true, false}) {
+ if (crop && output_mode != PIXELS) continue;
+ for (int scale_num : {1, 2, 3, 4, 7, 8, 13, 16}) {
+ if (scale_num != 8 && output_mode != PIXELS) continue;
+ int scale_denom = 8;
+ while (scale_num % 2 == 0 && scale_denom % 2 == 0) {
+ scale_num /= 2;
+ scale_denom /= 2;
+ }
+ printf("Decoding with output mode %d output scaling %d/%d %s\n",
+ output_mode, scale_num, scale_denom,
+ crop ? "with cropped output" : "");
+ dparams.output_mode = output_mode;
+ dparams.scale_num = scale_num;
+ dparams.scale_denom = scale_denom;
+ expected.Clear();
+ DecodeWithLibjpeg(jparams, dparams, compressed, &expected);
+ output.Clear();
+ cinfo.buffered_image = false;
+ cinfo.raw_data_out = false;
+ cinfo.scale_num = cinfo.scale_denom = 1;
+ SourceManager src(compressed.data(), compressed.size(),
+ 1u << 12);
+ cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
+ jpegli_read_header(&cinfo, /*require_image=*/TRUE);
+ jpegli_abort_decompress(&cinfo);
+ src.Reset();
+ TestAPINonBuffered(jparams, dparams, expected, &cinfo, &output);
+ float max_rms = output_mode == COEFFICIENTS ? 0.0f : 1.0f;
+ if (scale_num == 1 && scale_denom == 8 && h_samp != v_samp) {
+ max_rms = 5.0f; // libjpeg does not do fancy upsampling
+ }
+ VerifyOutputImage(expected, output, max_rms);
+ printf("Decoding in buffered image mode\n");
+ expected_output_progression.clear();
+ DecodeAllScansWithLibjpeg(jparams, dparams, compressed,
+ &expected_output_progression);
+ output_progression.clear();
+ src.Reset();
+ TestAPIBuffered(jparams, dparams, &cinfo, &output_progression);
+ JXL_CHECK(output_progression.size() ==
+ expected_output_progression.size());
+ for (size_t i = 0; i < output_progression.size(); ++i) {
+ const TestImage& output = output_progression[i];
+ const TestImage& expected = expected_output_progression[i];
+ VerifyOutputImage(expected, output, max_rms);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return true;
+ };
+ ASSERT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+}
+
+std::vector<TestConfig> GenerateBasicConfigs() {
+ std::vector<TestConfig> all_configs;
+ for (int samp : {1, 2}) {
+ for (int progr : {0, 2}) {
+ TestConfig config;
+ config.input.xsize = 257 + samp * 37;
+ config.input.ysize = 265 + (progr / 2) * 17;
+ config.jparams.h_sampling = {samp, 1, 1};
+ config.jparams.v_sampling = {samp, 1, 1};
+ config.jparams.progressive_mode = progr;
+ GeneratePixels(&config.input);
+ all_configs.push_back(config);
+ }
+ }
+ return all_configs;
+}
+
+TEST(DecodeAPITest, ReuseCinfoSameMemSource) {
+ std::vector<TestConfig> all_configs = GenerateBasicConfigs();
+ uint8_t* buffer = nullptr;
+ unsigned long buffer_size = 0;
+ {
+ jpeg_compress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_compress(&cinfo);
+ jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
+ for (const TestConfig& config : all_configs) {
+ EncodeWithJpegli(config.input, config.jparams, &cinfo);
+ }
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ jpegli_destroy_compress(&cinfo);
+ }
+ std::vector<TestImage> all_outputs(all_configs.size());
+ {
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ jpegli_mem_src(&cinfo, buffer, buffer_size);
+ for (size_t i = 0; i < all_configs.size(); ++i) {
+ TestAPINonBuffered(all_configs[i].jparams, DecompressParams(),
+ all_configs[i].input, &cinfo, &all_outputs[i]);
+ }
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+ }
+ for (size_t i = 0; i < all_configs.size(); ++i) {
+ VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f);
+ }
+ if (buffer) free(buffer);
+}
+
+TEST(DecodeAPITest, ReuseCinfoSameStdSource) {
+ std::vector<TestConfig> all_configs = GenerateBasicConfigs();
+ FILE* tmpf = tmpfile();
+ JXL_CHECK(tmpf);
+ {
+ jpeg_compress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_compress(&cinfo);
+ jpegli_stdio_dest(&cinfo, tmpf);
+ for (const TestConfig& config : all_configs) {
+ EncodeWithJpegli(config.input, config.jparams, &cinfo);
+ }
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ jpegli_destroy_compress(&cinfo);
+ }
+ rewind(tmpf);
+ std::vector<TestImage> all_outputs(all_configs.size());
+ {
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ jpegli_stdio_src(&cinfo, tmpf);
+ for (size_t i = 0; i < all_configs.size(); ++i) {
+ TestAPINonBuffered(all_configs[i].jparams, DecompressParams(),
+ all_configs[i].input, &cinfo, &all_outputs[i]);
+ }
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+ }
+ for (size_t i = 0; i < all_configs.size(); ++i) {
+ VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f);
+ }
+ fclose(tmpf);
+}
+
+TEST(DecodeAPITest, AbbreviatedStreams) {
+ uint8_t* table_stream = nullptr;
+ unsigned long table_stream_size = 0;
+ uint8_t* data_stream = nullptr;
+ unsigned long data_stream_size = 0;
+ {
+ jpeg_compress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_compress(&cinfo);
+ jpegli_mem_dest(&cinfo, &table_stream, &table_stream_size);
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ jpegli_set_defaults(&cinfo);
+ jpegli_write_tables(&cinfo);
+ jpegli_mem_dest(&cinfo, &data_stream, &data_stream_size);
+ cinfo.image_width = 1;
+ cinfo.image_height = 1;
+ cinfo.optimize_coding = FALSE;
+ jpegli_set_progressive_level(&cinfo, 0);
+ jpegli_start_compress(&cinfo, FALSE);
+ JSAMPLE image[3] = {0};
+ JSAMPROW row[] = {image};
+ jpegli_write_scanlines(&cinfo, row, 1);
+ jpegli_finish_compress(&cinfo);
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ EXPECT_LT(data_stream_size, 50);
+ jpegli_destroy_compress(&cinfo);
+ }
+ {
+ jpeg_decompress_struct cinfo = {};
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ jpegli_mem_src(&cinfo, table_stream, table_stream_size);
+ jpegli_read_header(&cinfo, FALSE);
+ jpegli_mem_src(&cinfo, data_stream, data_stream_size);
+ jpegli_read_header(&cinfo, TRUE);
+ EXPECT_EQ(1, cinfo.image_width);
+ EXPECT_EQ(1, cinfo.image_height);
+ EXPECT_EQ(3, cinfo.num_components);
+ jpegli_start_decompress(&cinfo);
+ JSAMPLE image[3] = {0};
+ JSAMPROW row[] = {image};
+ jpegli_read_scanlines(&cinfo, row, 1);
+ EXPECT_EQ(0, image[0]);
+ EXPECT_EQ(0, image[1]);
+ EXPECT_EQ(0, image[2]);
+ jpegli_finish_decompress(&cinfo);
+ return true;
+ };
+ EXPECT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+ }
+ if (table_stream) free(table_stream);
+ if (data_stream) free(data_stream);
+}
+
+class DecodeAPITestParam : public ::testing::TestWithParam<TestConfig> {};
+
+TEST_P(DecodeAPITestParam, TestAPI) {
+ TestConfig config = GetParam();
+ const DecompressParams& dparams = config.dparams;
+ if (dparams.skip_scans) return;
+ const std::vector<uint8_t> compressed = GetTestJpegData(config);
+ SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size);
+
+ TestImage output1;
+ DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
+
+ TestImage output0;
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
+ TestAPINonBuffered(config.jparams, dparams, output1, &cinfo, &output0);
+ return true;
+ };
+ ASSERT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+
+ if (config.compare_to_orig) {
+ double rms0 = DistanceRms(config.input, output0);
+ double rms1 = DistanceRms(config.input, output1);
+ printf("rms: %f vs %f\n", rms0, rms1);
+ EXPECT_LE(rms0, rms1 * config.max_tolerance_factor);
+ } else {
+ VerifyOutputImage(output0, output1, config.max_rms_dist, config.max_diff);
+ }
+}
+
+class DecodeAPITestParamBuffered : public ::testing::TestWithParam<TestConfig> {
+};
+
+TEST_P(DecodeAPITestParamBuffered, TestAPI) {
+ TestConfig config = GetParam();
+ const DecompressParams& dparams = config.dparams;
+ const std::vector<uint8_t> compressed = GetTestJpegData(config);
+ SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size);
+
+ std::vector<TestImage> output_progression1;
+ DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
+ &output_progression1);
+
+ std::vector<TestImage> output_progression0;
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ ERROR_HANDLER_SETUP(jpegli);
+ jpegli_create_decompress(&cinfo);
+ cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
+ TestAPIBuffered(config.jparams, dparams, &cinfo, &output_progression0);
+ return true;
+ };
+ ASSERT_TRUE(try_catch_block());
+ jpegli_destroy_decompress(&cinfo);
+
+ ASSERT_EQ(output_progression0.size(), output_progression1.size());
+ for (size_t i = 0; i < output_progression0.size(); ++i) {
+ const TestImage& output = output_progression0[i];
+ const TestImage& expected = output_progression1[i];
+ if (config.compare_to_orig) {
+ double rms0 = DistanceRms(config.input, output);
+ double rms1 = DistanceRms(config.input, expected);
+ printf("rms: %f vs %f\n", rms0, rms1);
+ EXPECT_LE(rms0, rms1 * config.max_tolerance_factor);
+ } else {
+ VerifyOutputImage(expected, output, config.max_rms_dist, config.max_diff);
+ }
+ }
+}
+
+std::vector<TestConfig> GenerateTests(bool buffered) {
+ std::vector<TestConfig> all_tests;
+ {
+ std::vector<std::pair<std::string, std::string>> testfiles({
+ {"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"},
+ {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
+ {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
+ });
+ for (size_t i = 0; i < (buffered ? 1u : testfiles.size()); ++i) {
+ TestConfig config;
+ config.fn = testfiles[i].first;
+ config.fn_desc = testfiles[i].second;
+ for (size_t chunk_size : {0, 1, 64, 65536}) {
+ config.dparams.chunk_size = chunk_size;
+ for (size_t max_output_lines : {0, 1, 8, 16}) {
+ config.dparams.max_output_lines = max_output_lines;
+ config.dparams.output_mode = PIXELS;
+ all_tests.push_back(config);
+ }
+ {
+ config.dparams.max_output_lines = 16;
+ config.dparams.output_mode = RAW_DATA;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+
+ {
+ std::vector<std::pair<std::string, std::string>> testfiles({
+ {"jxl/flower/flower_small.q85_444_non_interleaved.jpg",
+ "Q85YUV444NonInterleaved"},
+ {"jxl/flower/flower_small.q85_420_non_interleaved.jpg",
+ "Q85YUV420NonInterleaved"},
+ {"jxl/flower/flower_small.q85_444_partially_interleaved.jpg",
+ "Q85YUV444PartiallyInterleaved"},
+ {"jxl/flower/flower_small.q85_420_partially_interleaved.jpg",
+ "Q85YUV420PartiallyInterleaved"},
+ {"jxl/flower/flower.png.im_q85_422.jpg", "Q85YUV422"},
+ {"jxl/flower/flower.png.im_q85_440.jpg", "Q85YUV440"},
+ {"jxl/flower/flower.png.im_q85_444_1x2.jpg", "Q85YUV444_1x2"},
+ {"jxl/flower/flower.png.im_q85_asymmetric.jpg", "Q85Asymmetric"},
+ {"jxl/flower/flower.png.im_q85_gray.jpg", "Q85Gray"},
+ {"jxl/flower/flower.png.im_q85_luma_subsample.jpg", "Q85LumaSubsample"},
+ {"jxl/flower/flower.png.im_q85_rgb.jpg", "Q85RGB"},
+ {"jxl/flower/flower.png.im_q85_rgb_subsample_blue.jpg",
+ "Q85RGBSubsampleBlue"},
+ {"jxl/flower/flower_small.cmyk.jpg", "CMYK"},
+ });
+ for (size_t i = 0; i < (buffered ? 4u : testfiles.size()); ++i) {
+ for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
+ TestConfig config;
+ config.fn = testfiles[i].first;
+ config.fn_desc = testfiles[i].second;
+ config.dparams.output_mode = output_mode;
+ all_tests.push_back(config);
+ }
+ }
+ }
+
+ // Tests for common chroma subsampling and output modes.
+ for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) {
+ for (int h_samp : {1, 2}) {
+ for (int v_samp : {1, 2}) {
+ for (bool fancy : {true, false}) {
+ if (!fancy && (output_mode != PIXELS || h_samp * v_samp == 1)) {
+ continue;
+ }
+ TestConfig config;
+ config.dparams.output_mode = output_mode;
+ config.dparams.do_fancy_upsampling = fancy;
+ config.jparams.progressive_mode = 2;
+ config.jparams.h_sampling = {h_samp, 1, 1};
+ config.jparams.v_sampling = {v_samp, 1, 1};
+ if (output_mode == COEFFICIENTS) {
+ config.max_rms_dist = 0.0f;
+ }
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+
+ // Tests for partial input.
+ for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) {
+ for (int progr : {0, 1, 3}) {
+ for (int samp : {1, 2}) {
+ for (bool skip_scans : {false, true}) {
+ if (skip_scans && (progr != 1 || size_factor < 0.5f)) continue;
+ for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
+ TestConfig config;
+ config.input.xsize = 517;
+ config.input.ysize = 523;
+ config.jparams.h_sampling = {samp, 1, 1};
+ config.jparams.v_sampling = {samp, 1, 1};
+ config.jparams.progressive_mode = progr;
+ config.dparams.size_factor = size_factor;
+ config.dparams.output_mode = output_mode;
+ config.dparams.skip_scans = skip_scans;
+ // The last partially available block can behave differently.
+ // TODO(szabadka) Figure out if we can make the behaviour more
+ // similar.
+ config.max_rms_dist = samp == 1 ? 1.75f : 3.0f;
+ config.max_diff = 255.0f;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ }
+
+ // Tests for block smoothing.
+ for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) {
+ for (int samp : {1, 2}) {
+ for (bool skip_scans : {false, true}) {
+ if (skip_scans && size_factor < 0.3f) continue;
+ TestConfig config;
+ config.input.xsize = 517;
+ config.input.ysize = 523;
+ config.jparams.h_sampling = {samp, 1, 1};
+ config.jparams.v_sampling = {samp, 1, 1};
+ config.jparams.progressive_mode = 2;
+ config.dparams.size_factor = size_factor;
+ config.dparams.do_block_smoothing = true;
+ config.dparams.skip_scans = skip_scans;
+ // libjpeg does smoothing for incomplete scans differently at
+ // the border between current and previous scans.
+ config.max_rms_dist = 8.0f;
+ config.max_diff = 255.0f;
+ all_tests.push_back(config);
+ }
+ }
+ }
+
+ // Test for switching output color quantization modes between scans.
+ if (buffered) {
+ TestConfig config;
+ config.jparams.progressive_mode = 2;
+ config.dparams.quantize_colors = true;
+ config.dparams.scan_params = {
+ {3, JDITHER_NONE, CQUANT_1PASS}, {4, JDITHER_ORDERED, CQUANT_1PASS},
+ {5, JDITHER_FS, CQUANT_1PASS}, {6, JDITHER_NONE, CQUANT_EXTERNAL},
+ {8, JDITHER_NONE, CQUANT_REUSE}, {9, JDITHER_NONE, CQUANT_EXTERNAL},
+ {10, JDITHER_NONE, CQUANT_2PASS}, {11, JDITHER_NONE, CQUANT_REUSE},
+ {12, JDITHER_NONE, CQUANT_2PASS}, {13, JDITHER_FS, CQUANT_2PASS},
+ };
+ config.compare_to_orig = true;
+ config.max_tolerance_factor = 1.04f;
+ all_tests.push_back(config);
+ }
+
+ if (buffered) {
+ return all_tests;
+ }
+
+ // Tests for output color quantization.
+ for (int num_colors : {8, 64, 256}) {
+ for (ColorQuantMode mode : {CQUANT_1PASS, CQUANT_EXTERNAL, CQUANT_2PASS}) {
+ if (mode == CQUANT_EXTERNAL && num_colors != 256) continue;
+ for (J_DITHER_MODE dither : {JDITHER_NONE, JDITHER_ORDERED, JDITHER_FS}) {
+ if (mode == CQUANT_EXTERNAL && dither != JDITHER_NONE) continue;
+ if (mode != CQUANT_1PASS && dither == JDITHER_ORDERED) continue;
+ for (bool crop : {false, true}) {
+ for (bool scale : {false, true}) {
+ for (bool samp : {false, true}) {
+ if ((num_colors != 256) && (crop || scale || samp)) {
+ continue;
+ }
+ if (mode == CQUANT_2PASS && crop) continue;
+ TestConfig config;
+ config.input.xsize = 1024;
+ config.input.ysize = 768;
+ config.dparams.quantize_colors = true;
+ config.dparams.desired_number_of_colors = num_colors;
+ config.dparams.scan_params = {{kLastScan, dither, mode}};
+ config.dparams.crop_output = crop;
+ if (scale) {
+ config.dparams.scale_num = 7;
+ config.dparams.scale_denom = 8;
+ }
+ if (samp) {
+ config.jparams.h_sampling = {2, 1, 1};
+ config.jparams.v_sampling = {2, 1, 1};
+ }
+ if (!scale && !crop) {
+ config.compare_to_orig = true;
+ if (dither != JDITHER_NONE) {
+ config.max_tolerance_factor = 1.05f;
+ }
+ if (mode == CQUANT_2PASS &&
+ (num_colors == 8 || dither == JDITHER_FS)) {
+ // TODO(szabadka) Lower this bound.
+ config.max_tolerance_factor = 1.5f;
+ }
+ } else {
+ // We only test for buffer overflows, etc.
+ config.max_rms_dist = 100.0f;
+ config.max_diff = 255.0f;
+ }
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Tests for output formats.
+ for (JpegliDataType type :
+ {JPEGLI_TYPE_UINT8, JPEGLI_TYPE_UINT16, JPEGLI_TYPE_FLOAT}) {
+ for (JpegliEndianness endianness :
+ {JPEGLI_NATIVE_ENDIAN, JPEGLI_LITTLE_ENDIAN, JPEGLI_BIG_ENDIAN}) {
+ if (type == JPEGLI_TYPE_UINT8 && endianness != JPEGLI_NATIVE_ENDIAN) {
+ continue;
+ }
+ for (int channels = 1; channels <= 4; ++channels) {
+ TestConfig config;
+ config.dparams.data_type = type;
+ config.dparams.endianness = endianness;
+ config.input.color_space = JCS_UNKNOWN;
+ config.input.components = channels;
+ config.dparams.set_out_color_space = true;
+ config.dparams.out_color_space = JCS_UNKNOWN;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ // Test for output cropping.
+ {
+ TestConfig config;
+ config.dparams.crop_output = true;
+ all_tests.push_back(config);
+ }
+ // Tests for color transforms.
+ for (J_COLOR_SPACE out_color_space : {JCS_RGB, JCS_GRAYSCALE}) {
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 256;
+ config.input.color_space = JCS_GRAYSCALE;
+ config.dparams.set_out_color_space = true;
+ config.dparams.out_color_space = out_color_space;
+ all_tests.push_back(config);
+ }
+ for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) {
+ for (J_COLOR_SPACE out_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) {
+ if (jpeg_color_space == JCS_RGB && out_color_space == JCS_YCbCr) continue;
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 256;
+ config.jparams.set_jpeg_colorspace = true;
+ config.jparams.jpeg_color_space = jpeg_color_space;
+ config.dparams.set_out_color_space = true;
+ config.dparams.out_color_space = out_color_space;
+ all_tests.push_back(config);
+ }
+ }
+ for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) {
+ for (J_COLOR_SPACE out_color_space : {JCS_CMYK, JCS_YCCK}) {
+ if (jpeg_color_space == JCS_CMYK && out_color_space == JCS_YCCK) continue;
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 256;
+ config.input.color_space = JCS_CMYK;
+ config.jparams.set_jpeg_colorspace = true;
+ config.jparams.jpeg_color_space = jpeg_color_space;
+ config.dparams.set_out_color_space = true;
+ config.dparams.out_color_space = out_color_space;
+ all_tests.push_back(config);
+ }
+ }
+ // Tests for progressive levels.
+ for (int p = 0; p < 3 + kNumTestScripts; ++p) {
+ TestConfig config;
+ config.jparams.progressive_mode = p;
+ all_tests.push_back(config);
+ }
+ // Tests for RST markers.
+ for (size_t r : {1, 17, 1024}) {
+ for (size_t chunk_size : {1, 65536}) {
+ for (int progr : {0, 2}) {
+ TestConfig config;
+ config.dparams.chunk_size = chunk_size;
+ config.jparams.progressive_mode = progr;
+ config.jparams.restart_interval = r;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ for (size_t rr : {1, 3, 8, 100}) {
+ TestConfig config;
+ config.jparams.restart_in_rows = rr;
+ all_tests.push_back(config);
+ }
+ // Tests for custom quantization tables.
+ for (int type : {0, 1, 10, 100, 10000}) {
+ for (int scale : {1, 50, 100, 200, 500}) {
+ for (bool add_raw : {false, true}) {
+ for (bool baseline : {true, false}) {
+ if (!baseline && (add_raw || type * scale < 25500)) continue;
+ TestConfig config;
+ config.input.xsize = 64;
+ config.input.ysize = 64;
+ CustomQuantTable table;
+ table.table_type = type;
+ table.scale_factor = scale;
+ table.force_baseline = baseline;
+ table.add_raw = add_raw;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ config.jparams.quant_indexes = {0, 0, 0};
+ config.compare_to_orig = true;
+ config.max_tolerance_factor = 1.02;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ for (int qidx = 0; qidx < 8; ++qidx) {
+ if (qidx == 3) continue;
+ TestConfig config;
+ config.input.xsize = 256;
+ config.input.ysize = 256;
+ config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
+ (qidx >> 0) & 1};
+ all_tests.push_back(config);
+ }
+ for (int qidx = 0; qidx < 8; ++qidx) {
+ for (int slot_idx = 0; slot_idx < 2; ++slot_idx) {
+ if (qidx == 0 && slot_idx == 0) continue;
+ TestConfig config;
+ config.input.xsize = 256;
+ config.input.ysize = 256;
+ config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
+ (qidx >> 0) & 1};
+ CustomQuantTable table;
+ table.slot_idx = slot_idx;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ all_tests.push_back(config);
+ }
+ }
+ for (int qidx = 0; qidx < 8; ++qidx) {
+ for (bool xyb : {false, true}) {
+ TestConfig config;
+ config.input.xsize = 256;
+ config.input.ysize = 256;
+ config.jparams.xyb_mode = xyb;
+ config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
+ (qidx >> 0) & 1};
+ {
+ CustomQuantTable table;
+ table.slot_idx = 0;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ }
+ {
+ CustomQuantTable table;
+ table.slot_idx = 1;
+ table.table_type = 20;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ }
+ config.compare_to_orig = true;
+ all_tests.push_back(config);
+ }
+ }
+ for (bool xyb : {false, true}) {
+ TestConfig config;
+ config.input.xsize = 256;
+ config.input.ysize = 256;
+ config.jparams.xyb_mode = xyb;
+ config.jparams.quant_indexes = {0, 1, 2};
+ {
+ CustomQuantTable table;
+ table.slot_idx = 0;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ }
+ {
+ CustomQuantTable table;
+ table.slot_idx = 1;
+ table.table_type = 20;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ }
+ {
+ CustomQuantTable table;
+ table.slot_idx = 2;
+ table.table_type = 30;
+ table.Generate();
+ config.jparams.quant_tables.push_back(table);
+ }
+ config.compare_to_orig = true;
+ all_tests.push_back(config);
+ }
+ // Tests for fixed (and custom) prefix codes.
+ for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) {
+ for (bool flat_dc_luma : {false, true}) {
+ TestConfig config;
+ config.jparams.set_jpeg_colorspace = true;
+ config.jparams.jpeg_color_space = jpeg_color_space;
+ config.jparams.progressive_mode = 0;
+ config.jparams.optimize_coding = 0;
+ config.jparams.use_flat_dc_luma_code = flat_dc_luma;
+ all_tests.push_back(config);
+ }
+ }
+ for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) {
+ for (bool flat_dc_luma : {false, true}) {
+ TestConfig config;
+ config.input.color_space = JCS_CMYK;
+ config.jparams.set_jpeg_colorspace = true;
+ config.jparams.jpeg_color_space = jpeg_color_space;
+ config.jparams.progressive_mode = 0;
+ config.jparams.optimize_coding = 0;
+ config.jparams.use_flat_dc_luma_code = flat_dc_luma;
+ all_tests.push_back(config);
+ }
+ }
+ // Test for jpeg without DHT marker.
+ {
+ TestConfig config;
+ config.jparams.progressive_mode = 0;
+ config.jparams.optimize_coding = 0;
+ config.jparams.omit_standard_tables = true;
+ all_tests.push_back(config);
+ }
+ // Test for custom component ids.
+ {
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 128;
+ config.jparams.comp_ids = {7, 17, 177};
+ all_tests.push_back(config);
+ }
+ // Tests for JFIF/Adobe markers.
+ for (int override_JFIF : {-1, 0, 1}) {
+ for (int override_Adobe : {-1, 0, 1}) {
+ if (override_JFIF == -1 && override_Adobe == -1) continue;
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 128;
+ config.jparams.override_JFIF = override_JFIF;
+ config.jparams.override_Adobe = override_Adobe;
+ all_tests.push_back(config);
+ }
+ }
+ // Tests for small images.
+ for (int xsize : {1, 7, 8, 9, 15, 16, 17}) {
+ for (int ysize : {1, 7, 8, 9, 15, 16, 17}) {
+ TestConfig config;
+ config.input.xsize = xsize;
+ config.input.ysize = ysize;
+ all_tests.push_back(config);
+ }
+ }
+ // Tests for custom marker processor.
+ for (size_t chunk_size : {0, 1, 64, 65536}) {
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 256;
+ config.dparams.chunk_size = chunk_size;
+ config.jparams.add_marker = true;
+ all_tests.push_back(config);
+ }
+ // Tests for icc profile decoding.
+ for (size_t icc_size : {728, 70000, 1000000}) {
+ TestConfig config;
+ config.input.xsize = config.input.ysize = 256;
+ config.jparams.icc.resize(icc_size);
+ for (size_t i = 0; i < icc_size; ++i) {
+ config.jparams.icc[i] = (i * 17) & 0xff;
+ }
+ all_tests.push_back(config);
+ }
+ // Tests for unusual sampling factors.
+ for (int h0_samp : {1, 2, 3, 4}) {
+ for (int v0_samp : {1, 2, 3, 4}) {
+ for (int dxb = 0; dxb < h0_samp; ++dxb) {
+ for (int dyb = 0; dyb < v0_samp; ++dyb) {
+ for (int dx = 0; dx < 2; ++dx) {
+ for (int dy = 0; dy < 2; ++dy) {
+ TestConfig config;
+ config.input.xsize = 128 + dyb * 8 + dy;
+ config.input.ysize = 256 + dxb * 8 + dx;
+ config.jparams.progressive_mode = 2;
+ config.jparams.h_sampling = {h0_samp, 1, 1};
+ config.jparams.v_sampling = {v0_samp, 1, 1};
+ config.compare_to_orig = true;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ }
+ }
+ for (int h0_samp : {1, 2, 4}) {
+ for (int v0_samp : {1, 2, 4}) {
+ for (int h2_samp : {1, 2, 4}) {
+ for (int v2_samp : {1, 2, 4}) {
+ TestConfig config;
+ config.input.xsize = 137;
+ config.input.ysize = 75;
+ config.jparams.progressive_mode = 2;
+ config.jparams.h_sampling = {h0_samp, 1, h2_samp};
+ config.jparams.v_sampling = {v0_samp, 1, v2_samp};
+ config.compare_to_orig = true;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ for (int h0_samp : {1, 3}) {
+ for (int v0_samp : {1, 3}) {
+ for (int h2_samp : {1, 3}) {
+ for (int v2_samp : {1, 3}) {
+ TestConfig config;
+ config.input.xsize = 205;
+ config.input.ysize = 99;
+ config.jparams.progressive_mode = 2;
+ config.jparams.h_sampling = {h0_samp, 1, h2_samp};
+ config.jparams.v_sampling = {v0_samp, 1, v2_samp};
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ // Tests for output scaling.
+ for (int scale_num = 1; scale_num <= 16; ++scale_num) {
+ if (scale_num == 8) continue;
+ for (bool crop : {false, true}) {
+ for (int samp : {1, 2}) {
+ for (int progr : {0, 2}) {
+ TestConfig config;
+ config.jparams.h_sampling = {samp, 1, 1};
+ config.jparams.v_sampling = {samp, 1, 1};
+ config.jparams.progressive_mode = progr;
+ config.dparams.scale_num = scale_num;
+ config.dparams.scale_denom = 8;
+ config.dparams.crop_output = crop;
+ all_tests.push_back(config);
+ }
+ }
+ }
+ }
+ return all_tests;
+}
+
+std::string QuantMode(ColorQuantMode mode) {
+ switch (mode) {
+ case CQUANT_1PASS:
+ return "1pass";
+ case CQUANT_EXTERNAL:
+ return "External";
+ case CQUANT_2PASS:
+ return "2pass";
+ case CQUANT_REUSE:
+ return "Reuse";
+ }
+ return "";
+}
+
+std::string DitherMode(J_DITHER_MODE mode) {
+ switch (mode) {
+ case JDITHER_NONE:
+ return "No";
+ case JDITHER_ORDERED:
+ return "Ordered";
+ case JDITHER_FS:
+ return "FS";
+ }
+ return "";
+}
+
+std::ostream& operator<<(std::ostream& os, const DecompressParams& dparams) {
+ if (dparams.chunk_size == 0) {
+ os << "CompleteInput";
+ } else {
+ os << "InputChunks" << dparams.chunk_size;
+ }
+ if (dparams.size_factor < 1.0f) {
+ os << "Partial" << static_cast<int>(dparams.size_factor * 100) << "p";
+ }
+ if (dparams.max_output_lines == 0) {
+ os << "CompleteOutput";
+ } else {
+ os << "OutputLines" << dparams.max_output_lines;
+ }
+ if (dparams.output_mode == RAW_DATA) {
+ os << "RawDataOut";
+ } else if (dparams.output_mode == COEFFICIENTS) {
+ os << "CoeffsOut";
+ }
+ os << IOMethodName(dparams.data_type, dparams.endianness);
+ if (dparams.set_out_color_space) {
+ os << "OutColor" << ColorSpaceName(dparams.out_color_space);
+ }
+ if (dparams.crop_output) {
+ os << "Crop";
+ }
+ if (dparams.do_block_smoothing) {
+ os << "BlockSmoothing";
+ }
+ if (!dparams.do_fancy_upsampling) {
+ os << "NoFancyUpsampling";
+ }
+ if (dparams.scale_num != 1 || dparams.scale_denom != 1) {
+ os << "Scale" << dparams.scale_num << "_" << dparams.scale_denom;
+ }
+ if (dparams.quantize_colors) {
+ os << "Quant" << dparams.desired_number_of_colors << "colors";
+ for (size_t i = 0; i < dparams.scan_params.size(); ++i) {
+ if (i > 0) os << "_";
+ const auto& sparam = dparams.scan_params[i];
+ os << QuantMode(sparam.color_quant_mode);
+ os << DitherMode(sparam.dither_mode) << "Dither";
+ }
+ }
+ if (dparams.skip_scans) {
+ os << "SkipScans";
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
+ if (!c.fn.empty()) {
+ os << c.fn_desc;
+ } else {
+ os << c.input;
+ }
+ os << c.jparams;
+ os << c.dparams;
+ return os;
+}
+
+std::string TestDescription(const testing::TestParamInfo<TestConfig>& info) {
+ std::stringstream name;
+ name << info.param;
+ return name.str();
+}
+
+JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITest, DecodeAPITestParam,
+ testing::ValuesIn(GenerateTests(false)),
+ TestDescription);
+
+JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITestBuffered,
+ DecodeAPITestParamBuffered,
+ testing::ValuesIn(GenerateTests(true)),
+ TestDescription);
+
+} // namespace
+} // namespace jpegli