summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc')
-rw-r--r--third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc261
1 files changed, 261 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc b/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc
new file mode 100644
index 0000000000..de2303756e
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc
@@ -0,0 +1,261 @@
+// 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 "lib/jpegli/libjpeg_test_util.h"
+
+/* clang-format off */
+#include <stdio.h>
+#include <jpeglib.h>
+#include <setjmp.h>
+/* clang-format on */
+
+#include "lib/jxl/sanitizers.h"
+
+namespace jpegli {
+
+namespace {
+
+#define JPEG_API_FN(name) jpeg_##name
+#include "lib/jpegli/test_utils-inl.h"
+#undef JPEG_API_FN
+
+void ReadOutputPass(j_decompress_ptr cinfo, const DecompressParams& dparams,
+ 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;
+ jpeg_crop_scanline(cinfo, &xoffset, &xsize_cropped);
+ JXL_CHECK(xsize_cropped == cinfo->output_width);
+ }
+ output->xsize = xsize_cropped;
+ output->ysize = ysize_cropped;
+ output->components = cinfo->out_color_components;
+ if (cinfo->quantize_colors) {
+ jxl::msan::UnpoisonMemory(cinfo->colormap, cinfo->out_color_components *
+ sizeof(cinfo->colormap[0]));
+ for (int c = 0; c < cinfo->out_color_components; ++c) {
+ jxl::msan::UnpoisonMemory(
+ cinfo->colormap[c],
+ cinfo->actual_number_of_colors * sizeof(cinfo->colormap[c][0]));
+ }
+ }
+ if (!cinfo->raw_data_out) {
+ size_t stride = output->xsize * output->components;
+ output->pixels.resize(output->ysize * stride);
+ output->color_space = cinfo->out_color_space;
+ if (yoffset > 0) {
+ jpeg_skip_scanlines(cinfo, yoffset);
+ }
+ for (size_t y = 0; y < output->ysize; ++y) {
+ JSAMPROW rows[] = {
+ reinterpret_cast<JSAMPLE*>(&output->pixels[y * stride])};
+ JXL_CHECK(1 == jpeg_read_scanlines(cinfo, rows, 1));
+ jxl::msan::UnpoisonMemory(
+ rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize);
+ if (cinfo->quantize_colors) {
+ UnmapColors(rows[0], cinfo->output_width, cinfo->out_color_components,
+ cinfo->colormap, cinfo->actual_number_of_colors);
+ }
+ }
+ if (cinfo->output_scanline < cinfo->output_height) {
+ jpeg_skip_scanlines(cinfo, cinfo->output_height - cinfo->output_scanline);
+ }
+ } else {
+ 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);
+ output->raw_data.emplace_back(std::move(plane));
+ }
+ while (cinfo->output_scanline < cinfo->output_height) {
+ size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
+ JXL_CHECK(cinfo->output_scanline == cinfo->output_iMCU_row * 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];
+ }
+ JXL_CHECK(iMCU_height ==
+ jpeg_read_raw_data(cinfo, &data[0], iMCU_height));
+ }
+ }
+ JXL_CHECK(cinfo->total_iMCU_rows ==
+ DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE));
+}
+
+void DecodeWithLibjpeg(const CompressParams& jparams,
+ const DecompressParams& dparams, j_decompress_ptr cinfo,
+ TestImage* output) {
+ if (jparams.add_marker) {
+ jpeg_save_markers(cinfo, kSpecialMarker0, 0xffff);
+ jpeg_save_markers(cinfo, kSpecialMarker1, 0xffff);
+ }
+ if (!jparams.icc.empty()) {
+ jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xffff);
+ }
+ JXL_CHECK(JPEG_REACHED_SOS ==
+ jpeg_read_header(cinfo, /*require_image=*/TRUE));
+ if (!jparams.icc.empty()) {
+ uint8_t* icc_data = nullptr;
+ unsigned int icc_len;
+ JXL_CHECK(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len));
+ JXL_CHECK(icc_data);
+ jxl::msan::UnpoisonMemory(icc_data, icc_len);
+ JXL_CHECK(0 == memcmp(jparams.icc.data(), icc_data, icc_len));
+ free(icc_data);
+ }
+ SetDecompressParams(dparams, cinfo);
+ VerifyHeader(jparams, cinfo);
+ if (dparams.output_mode == COEFFICIENTS) {
+ jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo);
+ JXL_CHECK(coef_arrays != nullptr);
+ CopyCoefficients(cinfo, coef_arrays, output);
+ } else {
+ JXL_CHECK(jpeg_start_decompress(cinfo));
+ VerifyScanHeader(jparams, cinfo);
+ ReadOutputPass(cinfo, dparams, output);
+ }
+ JXL_CHECK(jpeg_finish_decompress(cinfo));
+}
+
+} // namespace
+
+// Verifies that an image encoded with libjpegli can be decoded with libjpeg,
+// and checks that the jpeg coding metadata matches jparams.
+void DecodeAllScansWithLibjpeg(const CompressParams& jparams,
+ const DecompressParams& dparams,
+ const std::vector<uint8_t>& compressed,
+ std::vector<TestImage>* output_progression) {
+ jpeg_decompress_struct cinfo = {};
+ const auto try_catch_block = [&]() {
+ jpeg_error_mgr jerr;
+ jmp_buf env;
+ cinfo.err = jpeg_std_error(&jerr);
+ if (setjmp(env)) {
+ return false;
+ }
+ cinfo.client_data = reinterpret_cast<void*>(&env);
+ cinfo.err->error_exit = [](j_common_ptr cinfo) {
+ (*cinfo->err->output_message)(cinfo);
+ jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data);
+ jpeg_destroy(cinfo);
+ longjmp(*env, 1);
+ };
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, compressed.data(), compressed.size());
+ if (jparams.add_marker) {
+ jpeg_save_markers(&cinfo, kSpecialMarker0, 0xffff);
+ jpeg_save_markers(&cinfo, kSpecialMarker1, 0xffff);
+ }
+ JXL_CHECK(JPEG_REACHED_SOS ==
+ jpeg_read_header(&cinfo, /*require_image=*/TRUE));
+ cinfo.buffered_image = TRUE;
+ SetDecompressParams(dparams, &cinfo);
+ VerifyHeader(jparams, &cinfo);
+ JXL_CHECK(jpeg_start_decompress(&cinfo));
+ // start decompress should not read the whole input in buffered image mode
+ JXL_CHECK(!jpeg_input_complete(&cinfo));
+ JXL_CHECK(cinfo.output_scan_number == 0);
+ int sos_marker_cnt = 1; // read header reads the first SOS marker
+ while (!jpeg_input_complete(&cinfo)) {
+ JXL_CHECK(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 = jpeg_consume_input(&cinfo);
+ }
+ if (result == JPEG_REACHED_SOS) ++sos_marker_cnt;
+ continue;
+ }
+ SetScanDecompressParams(dparams, &cinfo, cinfo.input_scan_number);
+ JXL_CHECK(jpeg_start_output(&cinfo, cinfo.input_scan_number));
+ // start output sets output_scan_number, but does not change
+ // input_scan_number
+ JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
+ JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
+ VerifyScanHeader(jparams, &cinfo);
+ TestImage output;
+ ReadOutputPass(&cinfo, dparams, &output);
+ output_progression->emplace_back(std::move(output));
+ // read scanlines/read raw data does not change input/output scan number
+ if (!cinfo.progressive_mode) {
+ JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
+ JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
+ }
+ JXL_CHECK(jpeg_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 = jpeg_read_coefficients(&cinfo);
+ JXL_CHECK(coef_arrays != nullptr);
+ CopyCoefficients(&cinfo, coef_arrays, &output_progression->back());
+ }
+ }
+ JXL_CHECK(jpeg_finish_decompress(&cinfo));
+ return true;
+ };
+ JXL_CHECK(try_catch_block());
+ jpeg_destroy_decompress(&cinfo);
+}
+
+// Returns the number of bytes read from compressed.
+size_t DecodeWithLibjpeg(const CompressParams& jparams,
+ const DecompressParams& dparams,
+ const uint8_t* table_stream, size_t table_stream_size,
+ const uint8_t* compressed, size_t len,
+ TestImage* output) {
+ jpeg_decompress_struct cinfo = {};
+ size_t bytes_read;
+ const auto try_catch_block = [&]() {
+ jpeg_error_mgr jerr;
+ jmp_buf env;
+ cinfo.err = jpeg_std_error(&jerr);
+ if (setjmp(env)) {
+ return false;
+ }
+ cinfo.client_data = reinterpret_cast<void*>(&env);
+ cinfo.err->error_exit = [](j_common_ptr cinfo) {
+ (*cinfo->err->output_message)(cinfo);
+ jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data);
+ jpeg_destroy(cinfo);
+ longjmp(*env, 1);
+ };
+ jpeg_create_decompress(&cinfo);
+ if (table_stream != nullptr) {
+ jpeg_mem_src(&cinfo, table_stream, table_stream_size);
+ jpeg_read_header(&cinfo, FALSE);
+ }
+ jpeg_mem_src(&cinfo, compressed, len);
+ DecodeWithLibjpeg(jparams, dparams, &cinfo, output);
+ bytes_read = len - cinfo.src->bytes_in_buffer;
+ return true;
+ };
+ JXL_CHECK(try_catch_block());
+ jpeg_destroy_decompress(&cinfo);
+ return bytes_read;
+}
+
+void DecodeWithLibjpeg(const CompressParams& jparams,
+ const DecompressParams& dparams,
+ const std::vector<uint8_t>& compressed,
+ TestImage* output) {
+ DecodeWithLibjpeg(jparams, dparams, nullptr, 0, compressed.data(),
+ compressed.size(), output);
+}
+
+} // namespace jpegli