summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jpegli/encode.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/jpeg-xl/lib/jpegli/encode.cc')
-rw-r--r--third_party/jpeg-xl/lib/jpegli/encode.cc1153
1 files changed, 1153 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jpegli/encode.cc b/third_party/jpeg-xl/lib/jpegli/encode.cc
new file mode 100644
index 0000000000..6015d7d9bb
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jpegli/encode.cc
@@ -0,0 +1,1153 @@
+// 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/encode.h"
+
+#include <cmath>
+#include <initializer_list>
+#include <vector>
+
+#include "lib/jpegli/adaptive_quantization.h"
+#include "lib/jpegli/bit_writer.h"
+#include "lib/jpegli/bitstream.h"
+#include "lib/jpegli/color_transform.h"
+#include "lib/jpegli/dct.h"
+#include "lib/jpegli/downsample.h"
+#include "lib/jpegli/encode_internal.h"
+#include "lib/jpegli/entropy_coding.h"
+#include "lib/jpegli/error.h"
+#include "lib/jpegli/huffman.h"
+#include "lib/jpegli/input.h"
+#include "lib/jpegli/memory_manager.h"
+#include "lib/jpegli/quant.h"
+
+namespace jpegli {
+
+constexpr size_t kMaxBytesInMarker = 65533;
+
+void CheckState(j_compress_ptr cinfo, int state) {
+ if (cinfo->global_state != state) {
+ JPEGLI_ERROR("Unexpected global state %d [expected %d]",
+ cinfo->global_state, state);
+ }
+}
+
+void CheckState(j_compress_ptr cinfo, int state1, int state2) {
+ if (cinfo->global_state != state1 && cinfo->global_state != state2) {
+ JPEGLI_ERROR("Unexpected global state %d [expected %d or %d]",
+ cinfo->global_state, state1, state2);
+ }
+}
+
+// Initialize cinfo fields that are not dependent on input image. This is shared
+// between jpegli_CreateCompress() and jpegli_set_defaults()
+void InitializeCompressParams(j_compress_ptr cinfo) {
+ cinfo->data_precision = 8;
+ cinfo->num_scans = 0;
+ cinfo->scan_info = nullptr;
+ cinfo->raw_data_in = FALSE;
+ cinfo->arith_code = FALSE;
+ cinfo->optimize_coding = FALSE;
+ cinfo->CCIR601_sampling = FALSE;
+ cinfo->smoothing_factor = 0;
+ cinfo->dct_method = JDCT_FLOAT;
+ cinfo->restart_interval = 0;
+ cinfo->restart_in_rows = 0;
+ cinfo->write_JFIF_header = FALSE;
+ cinfo->JFIF_major_version = 1;
+ cinfo->JFIF_minor_version = 1;
+ cinfo->density_unit = 0;
+ cinfo->X_density = 1;
+ cinfo->Y_density = 1;
+#if JPEG_LIB_VERSION >= 70
+ cinfo->scale_num = 1;
+ cinfo->scale_denom = 1;
+ cinfo->do_fancy_downsampling = FALSE;
+ cinfo->min_DCT_h_scaled_size = DCTSIZE;
+ cinfo->min_DCT_v_scaled_size = DCTSIZE;
+#endif
+}
+
+float LinearQualityToDistance(int scale_factor) {
+ scale_factor = std::min(5000, std::max(0, scale_factor));
+ int quality =
+ scale_factor < 100 ? 100 - scale_factor / 2 : 5000 / scale_factor;
+ return jpegli_quality_to_distance(quality);
+}
+
+template <typename T>
+void SetSentTableFlag(T** table_ptrs, size_t num, boolean val) {
+ for (size_t i = 0; i < num; ++i) {
+ if (table_ptrs[i]) table_ptrs[i]->sent_table = val;
+ }
+}
+
+struct ProgressiveScan {
+ int Ss, Se, Ah, Al;
+ bool interleaved;
+};
+
+void SetDefaultScanScript(j_compress_ptr cinfo) {
+ int level = cinfo->master->progressive_level;
+ std::vector<ProgressiveScan> progressive_mode;
+ bool interleave_dc =
+ (cinfo->max_h_samp_factor == 1 && cinfo->max_v_samp_factor == 1);
+ if (level == 0) {
+ progressive_mode.push_back({0, 63, 0, 0, true});
+ } else if (level == 1) {
+ progressive_mode.push_back({0, 0, 0, 0, interleave_dc});
+ progressive_mode.push_back({1, 63, 0, 1, false});
+ progressive_mode.push_back({1, 63, 1, 0, false});
+ } else {
+ progressive_mode.push_back({0, 0, 0, 0, interleave_dc});
+ progressive_mode.push_back({1, 2, 0, 0, false});
+ progressive_mode.push_back({3, 63, 0, 2, false});
+ progressive_mode.push_back({3, 63, 2, 1, false});
+ progressive_mode.push_back({3, 63, 1, 0, false});
+ }
+
+ cinfo->script_space_size = 0;
+ for (const auto& scan : progressive_mode) {
+ int comps = scan.interleaved ? MAX_COMPS_IN_SCAN : 1;
+ cinfo->script_space_size += DivCeil(cinfo->num_components, comps);
+ }
+ cinfo->script_space =
+ Allocate<jpeg_scan_info>(cinfo, cinfo->script_space_size);
+
+ jpeg_scan_info* next_scan = cinfo->script_space;
+ for (const auto& scan : progressive_mode) {
+ int comps = scan.interleaved ? MAX_COMPS_IN_SCAN : 1;
+ for (int c = 0; c < cinfo->num_components; c += comps) {
+ next_scan->Ss = scan.Ss;
+ next_scan->Se = scan.Se;
+ next_scan->Ah = scan.Ah;
+ next_scan->Al = scan.Al;
+ next_scan->comps_in_scan = std::min(comps, cinfo->num_components - c);
+ for (int j = 0; j < next_scan->comps_in_scan; ++j) {
+ next_scan->component_index[j] = c + j;
+ }
+ ++next_scan;
+ }
+ }
+ JXL_ASSERT(next_scan - cinfo->script_space == cinfo->script_space_size);
+ cinfo->scan_info = cinfo->script_space;
+ cinfo->num_scans = cinfo->script_space_size;
+}
+
+void ValidateScanScript(j_compress_ptr cinfo) {
+ // Mask of coefficient bits defined by the scan script, for each component
+ // and coefficient index.
+ uint16_t comp_mask[kMaxComponents][DCTSIZE2] = {};
+ static constexpr int kMaxRefinementBit = 10;
+
+ for (int i = 0; i < cinfo->num_scans; ++i) {
+ const jpeg_scan_info& si = cinfo->scan_info[i];
+ if (si.comps_in_scan < 1 || si.comps_in_scan > MAX_COMPS_IN_SCAN) {
+ JPEGLI_ERROR("Invalid number of components in scan %d", si.comps_in_scan);
+ }
+ int last_ci = -1;
+ for (int j = 0; j < si.comps_in_scan; ++j) {
+ int ci = si.component_index[j];
+ if (ci < 0 || ci >= cinfo->num_components) {
+ JPEGLI_ERROR("Invalid component index %d in scan", ci);
+ } else if (ci == last_ci) {
+ JPEGLI_ERROR("Duplicate component index %d in scan", ci);
+ } else if (ci < last_ci) {
+ JPEGLI_ERROR("Out of order component index %d in scan", ci);
+ }
+ last_ci = ci;
+ }
+ if (si.Ss < 0 || si.Se < si.Ss || si.Se >= DCTSIZE2) {
+ JPEGLI_ERROR("Invalid spectral range %d .. %d in scan", si.Ss, si.Se);
+ }
+ if (si.Ah < 0 || si.Al < 0 || si.Al > kMaxRefinementBit) {
+ JPEGLI_ERROR("Invalid refinement bits %d/%d", si.Ah, si.Al);
+ }
+ if (!cinfo->progressive_mode) {
+ if (si.Ss != 0 || si.Se != DCTSIZE2 - 1 || si.Ah != 0 || si.Al != 0) {
+ JPEGLI_ERROR("Invalid scan for sequential mode");
+ }
+ } else {
+ if (si.Ss == 0 && si.Se != 0) {
+ JPEGLI_ERROR("DC and AC together in progressive scan");
+ }
+ }
+ if (si.Ss != 0 && si.comps_in_scan != 1) {
+ JPEGLI_ERROR("Interleaved AC only scan.");
+ }
+ for (int j = 0; j < si.comps_in_scan; ++j) {
+ int ci = si.component_index[j];
+ if (si.Ss != 0 && comp_mask[ci][0] == 0) {
+ JPEGLI_ERROR("AC before DC in component %d of scan", ci);
+ }
+ for (int k = si.Ss; k <= si.Se; ++k) {
+ if (comp_mask[ci][k] == 0) {
+ if (si.Ah != 0) {
+ JPEGLI_ERROR("Invalid first scan refinement bit");
+ }
+ comp_mask[ci][k] = ((0xffff << si.Al) & 0xffff);
+ } else {
+ if (comp_mask[ci][k] != ((0xffff << si.Ah) & 0xffff) ||
+ si.Al != si.Ah - 1) {
+ JPEGLI_ERROR("Invalid refinement bit progression.");
+ }
+ comp_mask[ci][k] |= 1 << si.Al;
+ }
+ }
+ }
+ if (si.comps_in_scan > 1) {
+ size_t mcu_size = 0;
+ for (int j = 0; j < si.comps_in_scan; ++j) {
+ int ci = si.component_index[j];
+ jpeg_component_info* comp = &cinfo->comp_info[ci];
+ mcu_size += comp->h_samp_factor * comp->v_samp_factor;
+ }
+ if (mcu_size > C_MAX_BLOCKS_IN_MCU) {
+ JPEGLI_ERROR("MCU size too big");
+ }
+ }
+ }
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ for (int k = 0; k < DCTSIZE2; ++k) {
+ if (comp_mask[c][k] != 0xffff) {
+ JPEGLI_ERROR("Incomplete scan of component %d and frequency %d", c, k);
+ }
+ }
+ }
+}
+
+void ProcessCompressionParams(j_compress_ptr cinfo) {
+ if (cinfo->dest == nullptr) {
+ JPEGLI_ERROR("Missing destination.");
+ }
+ if (cinfo->image_width < 1 || cinfo->image_height < 1 ||
+ cinfo->input_components < 1) {
+ JPEGLI_ERROR("Empty input image.");
+ }
+ if (cinfo->image_width > static_cast<int>(JPEG_MAX_DIMENSION) ||
+ cinfo->image_height > static_cast<int>(JPEG_MAX_DIMENSION) ||
+ cinfo->input_components > static_cast<int>(kMaxComponents)) {
+ JPEGLI_ERROR("Input image too big.");
+ }
+ if (cinfo->num_components < 1 ||
+ cinfo->num_components > static_cast<int>(kMaxComponents)) {
+ JPEGLI_ERROR("Invalid number of components.");
+ }
+ if (cinfo->data_precision != kJpegPrecision) {
+ JPEGLI_ERROR("Invalid data precision");
+ }
+ if (cinfo->arith_code) {
+ JPEGLI_ERROR("Arithmetic coding is not implemented.");
+ }
+ if (cinfo->CCIR601_sampling) {
+ JPEGLI_ERROR("CCIR601 sampling is not implemented.");
+ }
+ if (cinfo->restart_interval > 65535u) {
+ JPEGLI_ERROR("Restart interval too big");
+ }
+ if (cinfo->smoothing_factor < 0 || cinfo->smoothing_factor > 100) {
+ JPEGLI_ERROR("Invalid smoothing factor %d", cinfo->smoothing_factor);
+ }
+ jpeg_comp_master* m = cinfo->master;
+ cinfo->max_h_samp_factor = cinfo->max_v_samp_factor = 1;
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ if (comp->component_index != c) {
+ JPEGLI_ERROR("Invalid component index");
+ }
+ for (int j = 0; j < c; ++j) {
+ if (cinfo->comp_info[j].component_id == comp->component_id) {
+ JPEGLI_ERROR("Duplicate component id %d", comp->component_id);
+ }
+ }
+ if (comp->h_samp_factor <= 0 || comp->v_samp_factor <= 0 ||
+ comp->h_samp_factor > MAX_SAMP_FACTOR ||
+ comp->v_samp_factor > MAX_SAMP_FACTOR) {
+ JPEGLI_ERROR("Invalid sampling factor %d x %d", comp->h_samp_factor,
+ comp->v_samp_factor);
+ }
+ cinfo->max_h_samp_factor =
+ std::max(comp->h_samp_factor, cinfo->max_h_samp_factor);
+ cinfo->max_v_samp_factor =
+ std::max(comp->v_samp_factor, cinfo->max_v_samp_factor);
+ }
+ if (cinfo->num_components == 1 &&
+ (cinfo->max_h_samp_factor != 1 || cinfo->max_v_samp_factor != 1)) {
+ JPEGLI_ERROR("Sampling is not supported for simgle component image.");
+ }
+ size_t iMCU_width = DCTSIZE * cinfo->max_h_samp_factor;
+ size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
+ size_t total_iMCU_cols = DivCeil(cinfo->image_width, iMCU_width);
+ cinfo->total_iMCU_rows = DivCeil(cinfo->image_height, iMCU_height);
+ m->xsize_blocks = total_iMCU_cols * cinfo->max_h_samp_factor;
+ m->ysize_blocks = cinfo->total_iMCU_rows * cinfo->max_v_samp_factor;
+
+ size_t blocks_per_iMCU = 0;
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ if (cinfo->max_h_samp_factor % comp->h_samp_factor != 0 ||
+ cinfo->max_v_samp_factor % comp->v_samp_factor != 0) {
+ JPEGLI_ERROR("Non-integral sampling ratios are not supported.");
+ }
+ m->h_factor[c] = cinfo->max_h_samp_factor / comp->h_samp_factor;
+ m->v_factor[c] = cinfo->max_v_samp_factor / comp->v_samp_factor;
+ comp->downsampled_width = DivCeil(cinfo->image_width, m->h_factor[c]);
+ comp->downsampled_height = DivCeil(cinfo->image_height, m->v_factor[c]);
+ comp->width_in_blocks = DivCeil(comp->downsampled_width, DCTSIZE);
+ comp->height_in_blocks = DivCeil(comp->downsampled_height, DCTSIZE);
+ blocks_per_iMCU += comp->h_samp_factor * comp->v_samp_factor;
+ }
+ m->blocks_per_iMCU_row = total_iMCU_cols * blocks_per_iMCU;
+ // Disable adaptive quantization for subsampled luma channel.
+ int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0;
+ jpeg_component_info* y_comp = &cinfo->comp_info[y_channel];
+ if (y_comp->h_samp_factor != cinfo->max_h_samp_factor ||
+ y_comp->v_samp_factor != cinfo->max_v_samp_factor) {
+ m->use_adaptive_quantization = false;
+ }
+ if (cinfo->scan_info == nullptr) {
+ SetDefaultScanScript(cinfo);
+ }
+ cinfo->progressive_mode =
+ cinfo->scan_info->Ss != 0 || cinfo->scan_info->Se != DCTSIZE2 - 1;
+ ValidateScanScript(cinfo);
+}
+
+void ResetForImage(j_compress_ptr cinfo) {
+ (*cinfo->err->reset_error_mgr)(reinterpret_cast<j_common_ptr>(cinfo));
+ (*cinfo->dest->init_destination)(cinfo);
+ jpeg_comp_master* m = cinfo->master;
+ m->next_iMCU_row = 0;
+ m->last_restart_interval = 0;
+ m->last_dht_index = 0;
+ m->num_huffman_codes = 0;
+ if (cinfo->num_scans > 0) {
+ m->scan_coding_info =
+ Allocate<ScanCodingInfo>(cinfo, cinfo->num_scans, JPOOL_IMAGE_ALIGNED);
+ }
+}
+
+bool IsStreamingSupported(j_compress_ptr cinfo) {
+ if (cinfo->global_state == kEncWriteCoeffs) {
+ return false;
+ }
+ // TODO(szabadka) Remove this restriction.
+ if (cinfo->restart_interval > 0 || cinfo->restart_in_rows > 0) {
+ return false;
+ }
+ if (cinfo->optimize_coding || cinfo->num_scans > 1) {
+ return false;
+ }
+ return true;
+}
+
+bool IsSinglePassOptimizerSupported(j_compress_ptr cinfo) {
+ return cinfo->num_scans == 1 && cinfo->optimize_coding &&
+ cinfo->restart_interval == 0 && cinfo->restart_in_rows == 0;
+}
+
+void AllocateBuffers(j_compress_ptr cinfo) {
+ jpeg_comp_master* m = cinfo->master;
+ size_t iMCU_width = DCTSIZE * cinfo->max_h_samp_factor;
+ size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
+ size_t total_iMCU_cols = DivCeil(cinfo->image_width, iMCU_width);
+ size_t xsize_full = total_iMCU_cols * iMCU_width;
+ size_t ysize_full = 3 * iMCU_height;
+ if (!cinfo->raw_data_in) {
+ int num_all_components =
+ std::max(cinfo->input_components, cinfo->num_components);
+ for (int c = 0; c < num_all_components; ++c) {
+ m->input_buffer[c].Allocate(cinfo, ysize_full, xsize_full);
+ }
+ }
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ size_t xsize = total_iMCU_cols * comp->h_samp_factor * DCTSIZE;
+ size_t ysize = 3 * comp->v_samp_factor * DCTSIZE;
+ if (cinfo->raw_data_in) {
+ m->input_buffer[c].Allocate(cinfo, ysize, xsize);
+ }
+ m->smooth_input[c] = &m->input_buffer[c];
+ if (!cinfo->raw_data_in && cinfo->smoothing_factor) {
+ m->smooth_input[c] = Allocate<RowBuffer<float>>(cinfo, 1, JPOOL_IMAGE);
+ m->smooth_input[c]->Allocate(cinfo, ysize_full, xsize_full);
+ }
+ m->raw_data[c] = m->smooth_input[c];
+ if (!cinfo->raw_data_in && (m->h_factor[c] > 1 || m->v_factor[c] > 1)) {
+ m->raw_data[c] = Allocate<RowBuffer<float>>(cinfo, 1, JPOOL_IMAGE);
+ m->raw_data[c]->Allocate(cinfo, ysize, xsize);
+ }
+ m->quant_mul[c] = Allocate<float>(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED);
+ }
+ m->dct_buffer = Allocate<float>(cinfo, 2 * DCTSIZE2, JPOOL_IMAGE_ALIGNED);
+ m->block_tmp = Allocate<int32_t>(cinfo, DCTSIZE2 * 4, JPOOL_IMAGE_ALIGNED);
+ if (!IsStreamingSupported(cinfo)) {
+ m->coeff_buffers =
+ Allocate<jvirt_barray_ptr>(cinfo, cinfo->num_components, JPOOL_IMAGE);
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ const size_t xsize_blocks = comp->width_in_blocks;
+ const size_t ysize_blocks = comp->height_in_blocks;
+ m->coeff_buffers[c] = (*cinfo->mem->request_virt_barray)(
+ reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE,
+ /*pre_zero=*/false, xsize_blocks, ysize_blocks, comp->v_samp_factor);
+ }
+ }
+ if (m->use_adaptive_quantization) {
+ int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0;
+ jpeg_component_info* y_comp = &cinfo->comp_info[y_channel];
+ const size_t xsize_blocks = y_comp->width_in_blocks;
+ const size_t vecsize = VectorSize();
+ const size_t xsize_padded = DivCeil(2 * xsize_blocks, vecsize) * vecsize;
+ m->diff_buffer =
+ Allocate<float>(cinfo, xsize_blocks * DCTSIZE + 8, JPOOL_IMAGE_ALIGNED);
+ m->fuzzy_erosion_tmp.Allocate(cinfo, 2, xsize_padded);
+ m->pre_erosion.Allocate(cinfo, 6 * cinfo->max_v_samp_factor, xsize_padded);
+ m->quant_field.Allocate(cinfo, cinfo->max_v_samp_factor, xsize_blocks);
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ m->zero_bias_offset[c] =
+ Allocate<float>(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED);
+ m->zero_bias_mul[c] =
+ Allocate<float>(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED);
+ }
+ }
+}
+
+void ReadInputRow(j_compress_ptr cinfo, const uint8_t* scanline,
+ float* row[kMaxComponents]) {
+ jpeg_comp_master* m = cinfo->master;
+ int num_all_components =
+ std::max(cinfo->input_components, cinfo->num_components);
+ for (int c = 0; c < num_all_components; ++c) {
+ row[c] = m->input_buffer[c].Row(m->next_input_row);
+ }
+ ++m->next_input_row;
+ if (scanline == nullptr) {
+ for (int c = 0; c < cinfo->input_components; ++c) {
+ memset(row[c], 0, cinfo->image_width * sizeof(row[c][0]));
+ }
+ return;
+ }
+ (*m->input_method)(scanline, cinfo->image_width, row);
+}
+
+void PadInputBuffer(j_compress_ptr cinfo, float* row[kMaxComponents]) {
+ jpeg_comp_master* m = cinfo->master;
+ const size_t len0 = cinfo->image_width;
+ const size_t len1 = m->xsize_blocks * DCTSIZE;
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ // Pad row to a multiple of the iMCU width, plus create a border of 1
+ // repeated pixel for adaptive quant field calculation.
+ float last_val = row[c][len0 - 1];
+ for (size_t x = len0; x <= len1; ++x) {
+ row[c][x] = last_val;
+ }
+ row[c][-1] = row[c][0];
+ }
+ if (m->next_input_row == cinfo->image_height) {
+ size_t num_rows = m->ysize_blocks * DCTSIZE - cinfo->image_height;
+ for (size_t i = 0; i < num_rows; ++i) {
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ float* dest = m->input_buffer[c].Row(m->next_input_row) - 1;
+ memcpy(dest, row[c] - 1, (len1 + 2) * sizeof(dest[0]));
+ }
+ ++m->next_input_row;
+ }
+ }
+}
+
+void ProcessiMCURow(j_compress_ptr cinfo) {
+ JXL_ASSERT(cinfo->master->next_iMCU_row < cinfo->total_iMCU_rows);
+ if (!cinfo->raw_data_in) {
+ ApplyInputSmoothing(cinfo);
+ DownsampleInputBuffer(cinfo);
+ }
+ ComputeAdaptiveQuantField(cinfo);
+ if (IsStreamingSupported(cinfo)) {
+ WriteiMCURow(cinfo);
+ } else {
+ ComputeDCTCoefficients(cinfo);
+ }
+ ++cinfo->master->next_iMCU_row;
+}
+
+void ProcessiMCURows(j_compress_ptr cinfo) {
+ jpeg_comp_master* m = cinfo->master;
+ size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
+ // To have context rows both above and below the current iMCU row, we delay
+ // processing the first iMCU row and process two iMCU rows after we receive
+ // the last input row.
+ if (m->next_input_row % iMCU_height == 0 && m->next_input_row > iMCU_height) {
+ ProcessiMCURow(cinfo);
+ }
+ if (m->next_input_row >= cinfo->image_height) {
+ ProcessiMCURow(cinfo);
+ }
+}
+
+void InitProgressMonitor(j_compress_ptr cinfo) {
+ if (cinfo->progress == nullptr) {
+ return;
+ }
+ if (IsStreamingSupported(cinfo)) {
+ // We have only one input pass.
+ cinfo->progress->total_passes = 1;
+ } else if (IsSinglePassOptimizerSupported(cinfo)) {
+ // We have one input pass and an encode pass for each scan.
+ cinfo->progress->total_passes = 1 + cinfo->num_scans;
+ } else {
+ // We have one input pass, a histogram pass for each scan, and an encode
+ // pass for each scan.
+ cinfo->progress->total_passes = 1 + 2 * cinfo->num_scans;
+ }
+}
+
+void ProgressMonitorInputPass(j_compress_ptr cinfo) {
+ if (cinfo->progress == nullptr) {
+ return;
+ }
+ cinfo->progress->completed_passes = 0;
+ cinfo->progress->pass_counter = cinfo->next_scanline;
+ cinfo->progress->pass_limit = cinfo->image_height;
+ (*cinfo->progress->progress_monitor)(reinterpret_cast<j_common_ptr>(cinfo));
+}
+
+void WriteFileHeader(j_compress_ptr cinfo) {
+ WriteOutput(cinfo, {0xFF, 0xD8}); // SOI
+ if (cinfo->write_JFIF_header) {
+ EncodeAPP0(cinfo);
+ }
+ if (cinfo->write_Adobe_marker) {
+ EncodeAPP14(cinfo);
+ }
+}
+
+void WriteScanHeader(j_compress_ptr cinfo, size_t scan_idx) {
+ jpeg_comp_master* m = cinfo->master;
+ cinfo->restart_interval = RestartIntervalForScan(cinfo, scan_idx);
+ if (cinfo->restart_interval != m->last_restart_interval) {
+ EncodeDRI(cinfo);
+ m->last_restart_interval = cinfo->restart_interval;
+ }
+ size_t num_dht = cinfo->master->scan_coding_info[scan_idx].num_huffman_codes;
+ if (num_dht > 0) {
+ bool pre_shifted = IsStreamingSupported(cinfo);
+ EncodeDHT(cinfo, m->huffman_codes + m->last_dht_index, num_dht,
+ pre_shifted);
+ m->last_dht_index += num_dht;
+ }
+ EncodeSOS(cinfo, scan_idx);
+}
+
+void WriteHeaderMarkers(j_compress_ptr cinfo) {
+ bool is_baseline = true;
+ CopyHuffmanCodes(cinfo, &is_baseline);
+ EncodeDQT(cinfo, /*write_all_tables=*/false, &is_baseline);
+ EncodeSOF(cinfo, is_baseline);
+ WriteScanHeader(cinfo, 0);
+ memset(cinfo->master->last_dc_coeff, 0, sizeof(cinfo->master->last_dc_coeff));
+}
+
+void EncodeScans(j_compress_ptr cinfo) {
+ if (IsSinglePassOptimizerSupported(cinfo)) {
+ EncodeSingleScan(cinfo);
+ return;
+ }
+ bool is_baseline = false;
+ if (cinfo->optimize_coding || cinfo->progressive_mode) {
+ OptimizeHuffmanCodes(cinfo, &is_baseline);
+ } else {
+ CopyHuffmanCodes(cinfo, &is_baseline);
+ }
+ EncodeDQT(cinfo, /*write_all_tables=*/false, &is_baseline);
+ EncodeSOF(cinfo, is_baseline);
+ for (int i = 0; i < cinfo->num_scans; ++i) {
+ WriteScanHeader(cinfo, i);
+ if (!EncodeScan(cinfo, i)) {
+ JPEGLI_ERROR("Failed to encode scan.");
+ }
+ }
+}
+
+} // namespace jpegli
+
+void jpegli_CreateCompress(j_compress_ptr cinfo, int version,
+ size_t structsize) {
+ cinfo->mem = nullptr;
+ if (structsize != sizeof(*cinfo)) {
+ JPEGLI_ERROR("jpegli_compress_struct has wrong size.");
+ }
+ jpegli::InitMemoryManager(reinterpret_cast<j_common_ptr>(cinfo));
+ cinfo->progress = nullptr;
+ cinfo->is_decompressor = FALSE;
+ cinfo->global_state = jpegli::kEncStart;
+ cinfo->dest = nullptr;
+ cinfo->image_width = 0;
+ cinfo->image_height = 0;
+ cinfo->input_components = 0;
+ cinfo->in_color_space = JCS_UNKNOWN;
+ cinfo->input_gamma = 1.0f;
+ cinfo->num_components = 0;
+ cinfo->jpeg_color_space = JCS_UNKNOWN;
+ cinfo->comp_info = nullptr;
+ for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
+ cinfo->quant_tbl_ptrs[i] = nullptr;
+ }
+ for (int i = 0; i < NUM_HUFF_TBLS; ++i) {
+ cinfo->dc_huff_tbl_ptrs[i] = nullptr;
+ cinfo->ac_huff_tbl_ptrs[i] = nullptr;
+ }
+ memset(cinfo->arith_dc_L, 0, sizeof(cinfo->arith_dc_L));
+ memset(cinfo->arith_dc_U, 0, sizeof(cinfo->arith_dc_U));
+ memset(cinfo->arith_ac_K, 0, sizeof(cinfo->arith_ac_K));
+ cinfo->write_Adobe_marker = false;
+ jpegli::InitializeCompressParams(cinfo);
+ cinfo->master = jpegli::Allocate<jpeg_comp_master>(cinfo, 1);
+ cinfo->master->force_baseline = true;
+ cinfo->master->xyb_mode = false;
+ cinfo->master->cicp_transfer_function = 2; // unknown transfer function code
+ cinfo->master->use_std_tables = false;
+ cinfo->master->use_adaptive_quantization = true;
+ cinfo->master->progressive_level = jpegli::kDefaultProgressiveLevel;
+ cinfo->master->data_type = JPEGLI_TYPE_UINT8;
+ cinfo->master->endianness = JPEGLI_NATIVE_ENDIAN;
+ cinfo->master->coeff_buffers = nullptr;
+}
+
+void jpegli_set_xyb_mode(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->xyb_mode = true;
+}
+
+void jpegli_set_cicp_transfer_function(j_compress_ptr cinfo, int code) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->cicp_transfer_function = code;
+}
+
+void jpegli_set_defaults(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ jpegli::InitializeCompressParams(cinfo);
+ jpegli_default_colorspace(cinfo);
+ jpegli_set_quality(cinfo, 90, TRUE);
+ jpegli_set_progressive_level(cinfo, jpegli::kDefaultProgressiveLevel);
+ jpegli::AddStandardHuffmanTables(reinterpret_cast<j_common_ptr>(cinfo),
+ /*is_dc=*/false);
+ jpegli::AddStandardHuffmanTables(reinterpret_cast<j_common_ptr>(cinfo),
+ /*is_dc=*/true);
+}
+
+void jpegli_default_colorspace(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ switch (cinfo->in_color_space) {
+ case JCS_GRAYSCALE:
+ jpegli_set_colorspace(cinfo, JCS_GRAYSCALE);
+ break;
+ case JCS_RGB: {
+ if (cinfo->master->xyb_mode) {
+ jpegli_set_colorspace(cinfo, JCS_RGB);
+ } else {
+ jpegli_set_colorspace(cinfo, JCS_YCbCr);
+ }
+ break;
+ }
+ case JCS_YCbCr:
+ jpegli_set_colorspace(cinfo, JCS_YCbCr);
+ break;
+ case JCS_CMYK:
+ jpegli_set_colorspace(cinfo, JCS_CMYK);
+ break;
+ case JCS_YCCK:
+ jpegli_set_colorspace(cinfo, JCS_YCCK);
+ break;
+ case JCS_UNKNOWN:
+ jpegli_set_colorspace(cinfo, JCS_UNKNOWN);
+ break;
+ default:
+ JPEGLI_ERROR("Unsupported input colorspace %d", cinfo->in_color_space);
+ }
+}
+
+void jpegli_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->jpeg_color_space = colorspace;
+ switch (colorspace) {
+ case JCS_GRAYSCALE:
+ cinfo->num_components = 1;
+ break;
+ case JCS_RGB:
+ case JCS_YCbCr:
+ cinfo->num_components = 3;
+ break;
+ case JCS_CMYK:
+ case JCS_YCCK:
+ cinfo->num_components = 4;
+ break;
+ case JCS_UNKNOWN:
+ cinfo->num_components =
+ std::min<int>(jpegli::kMaxComponents, cinfo->input_components);
+ break;
+ default:
+ JPEGLI_ERROR("Unsupported jpeg colorspace %d", colorspace);
+ }
+ // Adobe marker is only needed to distinguish CMYK and YCCK JPEGs.
+ cinfo->write_Adobe_marker = (cinfo->jpeg_color_space == JCS_YCCK);
+ if (cinfo->comp_info == nullptr) {
+ cinfo->comp_info =
+ jpegli::Allocate<jpeg_component_info>(cinfo, MAX_COMPONENTS);
+ }
+ memset(cinfo->comp_info, 0,
+ jpegli::kMaxComponents * sizeof(jpeg_component_info));
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ comp->component_index = c;
+ comp->component_id = c + 1;
+ comp->h_samp_factor = 1;
+ comp->v_samp_factor = 1;
+ comp->quant_tbl_no = 0;
+ comp->dc_tbl_no = 0;
+ comp->ac_tbl_no = 0;
+ }
+ if (colorspace == JCS_RGB) {
+ cinfo->comp_info[0].component_id = 'R';
+ cinfo->comp_info[1].component_id = 'G';
+ cinfo->comp_info[2].component_id = 'B';
+ if (cinfo->master->xyb_mode) {
+ // Subsample blue channel.
+ cinfo->comp_info[0].h_samp_factor = cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = cinfo->comp_info[1].v_samp_factor = 2;
+ cinfo->comp_info[2].h_samp_factor = cinfo->comp_info[2].v_samp_factor = 1;
+ // Use separate quantization tables for each component
+ cinfo->comp_info[1].quant_tbl_no = 1;
+ cinfo->comp_info[2].quant_tbl_no = 2;
+ }
+ } else if (colorspace == JCS_CMYK) {
+ cinfo->comp_info[0].component_id = 'C';
+ cinfo->comp_info[1].component_id = 'M';
+ cinfo->comp_info[2].component_id = 'Y';
+ cinfo->comp_info[3].component_id = 'K';
+ } else if (colorspace == JCS_YCbCr || colorspace == JCS_YCCK) {
+ // Use separate quantization and Huffman tables for luma and chroma
+ cinfo->comp_info[1].quant_tbl_no = 1;
+ cinfo->comp_info[2].quant_tbl_no = 1;
+ cinfo->comp_info[1].dc_tbl_no = cinfo->comp_info[1].ac_tbl_no = 1;
+ cinfo->comp_info[2].dc_tbl_no = cinfo->comp_info[2].ac_tbl_no = 1;
+ }
+}
+
+void jpegli_set_distance(j_compress_ptr cinfo, float distance,
+ boolean force_baseline) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->force_baseline = force_baseline;
+ float distances[NUM_QUANT_TBLS] = {distance, distance, distance};
+ jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/true);
+}
+
+float jpegli_quality_to_distance(int quality) {
+ return (quality >= 100 ? 0.01f
+ : quality >= 30 ? 0.1f + (100 - quality) * 0.09f
+ : 53.0f / 3000.0f * quality * quality -
+ 23.0f / 20.0f * quality + 25.0f);
+}
+
+void jpegli_set_quality(j_compress_ptr cinfo, int quality,
+ boolean force_baseline) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->force_baseline = force_baseline;
+ float distance = jpegli_quality_to_distance(quality);
+ float distances[NUM_QUANT_TBLS] = {distance, distance, distance};
+ jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false);
+}
+
+void jpegli_set_linear_quality(j_compress_ptr cinfo, int scale_factor,
+ boolean force_baseline) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->force_baseline = force_baseline;
+ float distance = jpegli::LinearQualityToDistance(scale_factor);
+ float distances[NUM_QUANT_TBLS] = {distance, distance, distance};
+ jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false);
+}
+
+#if JPEG_LIB_VERSION >= 70
+void jpegli_default_qtables(j_compress_ptr cinfo, boolean force_baseline) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->force_baseline = force_baseline;
+ float distances[NUM_QUANT_TBLS];
+ for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
+ distances[i] = jpegli::LinearQualityToDistance(cinfo->q_scale_factor[i]);
+ }
+ jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false);
+}
+#endif
+
+int jpegli_quality_scaling(int quality) {
+ quality = std::min(100, std::max(1, quality));
+ return quality < 50 ? 5000 / quality : 200 - 2 * quality;
+}
+
+void jpegli_use_standard_quant_tables(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->use_std_tables = true;
+}
+
+void jpegli_add_quant_table(j_compress_ptr cinfo, int which_tbl,
+ const unsigned int* basic_table, int scale_factor,
+ boolean force_baseline) {
+ CheckState(cinfo, jpegli::kEncStart);
+ if (which_tbl < 0 || which_tbl > NUM_QUANT_TBLS) {
+ JPEGLI_ERROR("Invalid quant table index %d", which_tbl);
+ }
+ if (cinfo->quant_tbl_ptrs[which_tbl] == nullptr) {
+ cinfo->quant_tbl_ptrs[which_tbl] =
+ jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
+ }
+ int max_qval = force_baseline ? 255 : 32767U;
+ JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[which_tbl];
+ for (int k = 0; k < DCTSIZE2; ++k) {
+ int qval = (basic_table[k] * scale_factor + 50) / 100;
+ qval = std::max(1, std::min(qval, max_qval));
+ quant_table->quantval[k] = qval;
+ }
+ quant_table->sent_table = FALSE;
+}
+
+void jpegli_enable_adaptive_quantization(j_compress_ptr cinfo, boolean value) {
+ CheckState(cinfo, jpegli::kEncStart);
+ cinfo->master->use_adaptive_quantization = value;
+}
+
+void jpegli_simple_progression(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ jpegli_set_progressive_level(cinfo, 2);
+}
+
+void jpegli_set_progressive_level(j_compress_ptr cinfo, int level) {
+ CheckState(cinfo, jpegli::kEncStart);
+ if (level < 0) {
+ JPEGLI_ERROR("Invalid progressive level %d", level);
+ }
+ cinfo->master->progressive_level = level;
+}
+
+void jpegli_set_input_format(j_compress_ptr cinfo, JpegliDataType data_type,
+ JpegliEndianness endianness) {
+ CheckState(cinfo, jpegli::kEncStart);
+ switch (data_type) {
+ case JPEGLI_TYPE_UINT8:
+ case JPEGLI_TYPE_UINT16:
+ case JPEGLI_TYPE_FLOAT:
+ cinfo->master->data_type = data_type;
+ break;
+ default:
+ JPEGLI_ERROR("Unsupported data type %d", data_type);
+ }
+ switch (endianness) {
+ case JPEGLI_NATIVE_ENDIAN:
+ case JPEGLI_LITTLE_ENDIAN:
+ case JPEGLI_BIG_ENDIAN:
+ cinfo->master->endianness = endianness;
+ break;
+ default:
+ JPEGLI_ERROR("Unsupported endianness %d", endianness);
+ }
+}
+
+#if JPEG_LIB_VERSION >= 70
+void jpegli_calc_jpeg_dimensions(j_compress_ptr cinfo) {
+ // Since input scaling is not supported, we just copy the image dimensions.
+ cinfo->jpeg_width = cinfo->image_width;
+ cinfo->jpeg_height = cinfo->image_height;
+}
+#endif
+
+void jpegli_copy_critical_parameters(j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo) {
+ CheckState(dstinfo, jpegli::kEncStart);
+ // Image parameters.
+ dstinfo->image_width = srcinfo->image_width;
+ dstinfo->image_height = srcinfo->image_height;
+ dstinfo->input_components = srcinfo->num_components;
+ dstinfo->in_color_space = srcinfo->jpeg_color_space;
+ dstinfo->input_gamma = srcinfo->output_gamma;
+ // Compression parameters.
+ jpegli_set_defaults(dstinfo);
+ jpegli_set_colorspace(dstinfo, srcinfo->jpeg_color_space);
+ if (dstinfo->num_components != srcinfo->num_components) {
+ const auto& cinfo = dstinfo;
+ return JPEGLI_ERROR("Mismatch between src colorspace and components");
+ }
+ dstinfo->data_precision = srcinfo->data_precision;
+ dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling;
+ dstinfo->JFIF_major_version = srcinfo->JFIF_major_version;
+ dstinfo->JFIF_minor_version = srcinfo->JFIF_minor_version;
+ dstinfo->density_unit = srcinfo->density_unit;
+ dstinfo->X_density = srcinfo->X_density;
+ dstinfo->Y_density = srcinfo->Y_density;
+ for (int c = 0; c < dstinfo->num_components; ++c) {
+ jpeg_component_info* srccomp = &srcinfo->comp_info[c];
+ jpeg_component_info* dstcomp = &dstinfo->comp_info[c];
+ dstcomp->component_id = srccomp->component_id;
+ dstcomp->h_samp_factor = srccomp->h_samp_factor;
+ dstcomp->v_samp_factor = srccomp->v_samp_factor;
+ dstcomp->quant_tbl_no = srccomp->quant_tbl_no;
+ }
+ for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
+ if (!srcinfo->quant_tbl_ptrs[i]) continue;
+ if (dstinfo->quant_tbl_ptrs[i] == nullptr) {
+ dstinfo->quant_tbl_ptrs[i] = jpegli::Allocate<JQUANT_TBL>(dstinfo, 1);
+ }
+ memcpy(dstinfo->quant_tbl_ptrs[i], srcinfo->quant_tbl_ptrs[i],
+ sizeof(JQUANT_TBL));
+ dstinfo->quant_tbl_ptrs[i]->sent_table = FALSE;
+ }
+}
+
+void jpegli_suppress_tables(j_compress_ptr cinfo, boolean suppress) {
+ jpegli::SetSentTableFlag(cinfo->quant_tbl_ptrs, NUM_QUANT_TBLS, suppress);
+ jpegli::SetSentTableFlag(cinfo->dc_huff_tbl_ptrs, NUM_HUFF_TBLS, suppress);
+ jpegli::SetSentTableFlag(cinfo->ac_huff_tbl_ptrs, NUM_HUFF_TBLS, suppress);
+}
+
+void jpegli_start_compress(j_compress_ptr cinfo, boolean write_all_tables) {
+ CheckState(cinfo, jpegli::kEncStart);
+ jpegli::ProcessCompressionParams(cinfo);
+ jpegli::InitProgressMonitor(cinfo);
+ jpegli::AllocateBuffers(cinfo);
+ jpegli::ChooseInputMethod(cinfo);
+ if (!cinfo->raw_data_in) {
+ jpegli::ChooseColorTransform(cinfo);
+ jpegli::ChooseDownsampleMethods(cinfo);
+ }
+ jpegli::InitQuantizer(cinfo);
+ if (write_all_tables) {
+ jpegli_suppress_tables(cinfo, FALSE);
+ }
+ (*cinfo->mem->realize_virt_arrays)(reinterpret_cast<j_common_ptr>(cinfo));
+ jpegli::ResetForImage(cinfo);
+ jpegli::WriteFileHeader(cinfo);
+ jpegli::JpegBitWriterInit(cinfo);
+ cinfo->next_scanline = 0;
+ cinfo->master->next_input_row = 0;
+ cinfo->global_state = jpegli::kEncHeader;
+}
+
+void jpegli_write_coefficients(j_compress_ptr cinfo,
+ jvirt_barray_ptr* coef_arrays) {
+ CheckState(cinfo, jpegli::kEncStart);
+ jpegli::ProcessCompressionParams(cinfo);
+ jpegli::InitProgressMonitor(cinfo);
+ (*cinfo->mem->realize_virt_arrays)(reinterpret_cast<j_common_ptr>(cinfo));
+ cinfo->master->coeff_buffers = coef_arrays;
+ jpegli_suppress_tables(cinfo, FALSE);
+ jpegli::ResetForImage(cinfo);
+ jpegli::WriteFileHeader(cinfo);
+ jpegli::JpegBitWriterInit(cinfo);
+ cinfo->next_scanline = cinfo->image_height;
+ cinfo->master->next_input_row = cinfo->image_height;
+ cinfo->global_state = jpegli::kEncWriteCoeffs;
+}
+
+void jpegli_write_tables(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncStart);
+ if (cinfo->dest == nullptr) {
+ JPEGLI_ERROR("Missing destination.");
+ }
+ jpegli::ResetForImage(cinfo);
+ bool is_baseline = true;
+ jpeg_comp_master* m = cinfo->master;
+ jpegli::WriteOutput(cinfo, {0xFF, 0xD8}); // SOI
+ jpegli::EncodeDQT(cinfo, /*write_all_tables=*/true, &is_baseline);
+ jpegli::CopyHuffmanCodes(cinfo, &is_baseline);
+ jpegli::EncodeDHT(cinfo, m->huffman_codes, m->num_huffman_codes);
+ jpegli::WriteOutput(cinfo, {0xFF, 0xD9}); // EOI
+ (*cinfo->dest->term_destination)(cinfo);
+ jpegli_suppress_tables(cinfo, TRUE);
+}
+
+void jpegli_write_m_header(j_compress_ptr cinfo, int marker,
+ unsigned int datalen) {
+ CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncWriteCoeffs);
+ if (datalen > jpegli::kMaxBytesInMarker) {
+ JPEGLI_ERROR("Invalid marker length %u", datalen);
+ }
+ if (marker != 0xfe && (marker < 0xe0 || marker > 0xef)) {
+ JPEGLI_ERROR(
+ "jpegli_write_m_header: Only APP and COM markers are supported.");
+ }
+ std::vector<uint8_t> marker_data(4 + datalen);
+ marker_data[0] = 0xff;
+ marker_data[1] = marker;
+ marker_data[2] = (datalen + 2) >> 8;
+ marker_data[3] = (datalen + 2) & 0xff;
+ jpegli::WriteOutput(cinfo, &marker_data[0], 4);
+}
+
+void jpegli_write_m_byte(j_compress_ptr cinfo, int val) {
+ uint8_t data = val;
+ jpegli::WriteOutput(cinfo, &data, 1);
+}
+
+void jpegli_write_marker(j_compress_ptr cinfo, int marker,
+ const JOCTET* dataptr, unsigned int datalen) {
+ jpegli_write_m_header(cinfo, marker, datalen);
+ jpegli::WriteOutput(cinfo, dataptr, datalen);
+}
+
+void jpegli_write_icc_profile(j_compress_ptr cinfo, const JOCTET* icc_data_ptr,
+ unsigned int icc_data_len) {
+ constexpr size_t kMaxIccBytesInMarker =
+ jpegli::kMaxBytesInMarker - sizeof jpegli::kICCSignature - 2;
+ const int num_markers =
+ static_cast<int>(jpegli::DivCeil(icc_data_len, kMaxIccBytesInMarker));
+ size_t begin = 0;
+ for (int current_marker = 0; current_marker < num_markers; ++current_marker) {
+ const size_t length = std::min(kMaxIccBytesInMarker, icc_data_len - begin);
+ jpegli_write_m_header(
+ cinfo, jpegli::kICCMarker,
+ static_cast<unsigned int>(length + sizeof jpegli::kICCSignature + 2));
+ for (const unsigned char c : jpegli::kICCSignature) {
+ jpegli_write_m_byte(cinfo, c);
+ }
+ jpegli_write_m_byte(cinfo, current_marker + 1);
+ jpegli_write_m_byte(cinfo, num_markers);
+ for (size_t i = 0; i < length; ++i) {
+ jpegli_write_m_byte(cinfo, icc_data_ptr[begin]);
+ ++begin;
+ }
+ }
+}
+
+JDIMENSION jpegli_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,
+ JDIMENSION num_lines) {
+ CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncReadImage);
+ if (cinfo->raw_data_in) {
+ JPEGLI_ERROR("jpegli_write_raw_data() must be called for raw data mode.");
+ }
+ jpegli::ProgressMonitorInputPass(cinfo);
+ if (cinfo->global_state == jpegli::kEncHeader &&
+ jpegli::IsStreamingSupported(cinfo)) {
+ jpegli::WriteHeaderMarkers(cinfo);
+ }
+ cinfo->global_state = jpegli::kEncReadImage;
+ jpeg_comp_master* m = cinfo->master;
+ if (num_lines + cinfo->next_scanline > cinfo->image_height) {
+ num_lines = cinfo->image_height - cinfo->next_scanline;
+ }
+ JDIMENSION prev_scanline = cinfo->next_scanline;
+ size_t input_lag = (std::min<size_t>(cinfo->image_height, m->next_input_row) -
+ cinfo->next_scanline);
+ if (input_lag > num_lines) {
+ JPEGLI_ERROR("Need at least %u lines to continue", input_lag);
+ }
+ if (input_lag > 0) {
+ if (!jpegli::EmptyBitWriterBuffer(&m->bw)) {
+ return 0;
+ }
+ cinfo->next_scanline += input_lag;
+ }
+ float* rows[jpegli::kMaxComponents];
+ for (size_t i = input_lag; i < num_lines; ++i) {
+ jpegli::ReadInputRow(cinfo, scanlines[i], rows);
+ (*m->color_transform)(rows, cinfo->image_width);
+ jpegli::PadInputBuffer(cinfo, rows);
+ jpegli::ProcessiMCURows(cinfo);
+ if (!jpegli::EmptyBitWriterBuffer(&m->bw)) {
+ break;
+ }
+ ++cinfo->next_scanline;
+ }
+ return cinfo->next_scanline - prev_scanline;
+}
+
+JDIMENSION jpegli_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data,
+ JDIMENSION num_lines) {
+ CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncReadImage);
+ if (!cinfo->raw_data_in) {
+ JPEGLI_ERROR("jpegli_write_raw_data(): raw data mode was not set");
+ }
+ jpegli::ProgressMonitorInputPass(cinfo);
+ if (cinfo->global_state == jpegli::kEncHeader &&
+ jpegli::IsStreamingSupported(cinfo)) {
+ jpegli::WriteHeaderMarkers(cinfo);
+ }
+ cinfo->global_state = jpegli::kEncReadImage;
+ jpeg_comp_master* m = cinfo->master;
+ if (cinfo->next_scanline >= cinfo->image_height) {
+ return 0;
+ }
+ size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
+ if (num_lines < iMCU_height) {
+ JPEGLI_ERROR("Missing input lines, minimum is %u", iMCU_height);
+ }
+ if (cinfo->next_scanline < m->next_input_row) {
+ JXL_ASSERT(m->next_input_row - cinfo->next_scanline == iMCU_height);
+ if (!jpegli::EmptyBitWriterBuffer(&m->bw)) {
+ return 0;
+ }
+ cinfo->next_scanline = m->next_input_row;
+ return iMCU_height;
+ }
+ size_t iMCU_y = m->next_input_row / iMCU_height;
+ float* rows[jpegli::kMaxComponents];
+ for (int c = 0; c < cinfo->num_components; ++c) {
+ JSAMPARRAY plane = data[c];
+ jpeg_component_info* comp = &cinfo->comp_info[c];
+ size_t xsize = comp->width_in_blocks * DCTSIZE;
+ size_t ysize = comp->v_samp_factor * DCTSIZE;
+ size_t y0 = iMCU_y * ysize;
+ auto& buffer = m->input_buffer[c];
+ for (size_t i = 0; i < ysize; ++i) {
+ rows[0] = buffer.Row(y0 + i);
+ if (plane[i] == nullptr) {
+ memset(rows[0], 0, xsize * sizeof(rows[0][0]));
+ } else {
+ (*m->input_method)(plane[i], xsize, rows);
+ }
+ // We need a border of 1 repeated pixel for adaptive quant field.
+ buffer.PadRow(y0 + i, xsize, /*border=*/1);
+ }
+ }
+ m->next_input_row += iMCU_height;
+ jpegli::ProcessiMCURows(cinfo);
+ if (!jpegli::EmptyBitWriterBuffer(&m->bw)) {
+ return 0;
+ }
+ cinfo->next_scanline += iMCU_height;
+ return iMCU_height;
+}
+
+void jpegli_finish_compress(j_compress_ptr cinfo) {
+ CheckState(cinfo, jpegli::kEncReadImage, jpegli::kEncWriteCoeffs);
+ jpeg_comp_master* m = cinfo->master;
+ if (cinfo->next_scanline < cinfo->image_height) {
+ JPEGLI_ERROR("Incomplete image, expected %d rows, got %d",
+ cinfo->image_height, cinfo->next_scanline);
+ }
+
+ if (jpegli::IsStreamingSupported(cinfo)) {
+ jpegli::JumpToByteBoundary(&m->bw);
+ if (!jpegli::EmptyBitWriterBuffer(&m->bw)) {
+ JPEGLI_ERROR("Output suspension is not supported in finish_compress");
+ }
+ if (!m->bw.healthy) {
+ JPEGLI_ERROR("Failed to encode scan.");
+ }
+ } else {
+ jpegli::EncodeScans(cinfo);
+ }
+
+ jpegli::WriteOutput(cinfo, {0xFF, 0xD9}); // EOI
+ (*cinfo->dest->term_destination)(cinfo);
+
+ // Release memory and reset global state.
+ jpegli_abort_compress(cinfo);
+}
+
+void jpegli_abort_compress(j_compress_ptr cinfo) {
+ jpegli_abort(reinterpret_cast<j_common_ptr>(cinfo));
+}
+
+void jpegli_destroy_compress(j_compress_ptr cinfo) {
+ jpegli_destroy(reinterpret_cast<j_common_ptr>(cinfo));
+}