summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jxl/jpeg
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/jpeg-xl/lib/jxl/jpeg
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/jpeg-xl/lib/jxl/jpeg')
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc145
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.h19
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc1050
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.h35
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_output_chunk.h72
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_serialization_state.h96
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc384
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h31
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc1053
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.h36
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.cc103
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.h41
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc451
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.h216
14 files changed, 3732 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc
new file mode 100644
index 0000000000..db49a1c215
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc
@@ -0,0 +1,145 @@
+// 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/jxl/jpeg/dec_jpeg_data.h"
+
+#include <brotli/decode.h>
+
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/dec_bit_reader.h"
+#include "lib/jxl/sanitizers.h"
+
+namespace jxl {
+namespace jpeg {
+Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data) {
+ Status ret = true;
+ const uint8_t* in = encoded.data();
+ size_t available_in = encoded.size();
+ {
+ BitReader br(encoded);
+ BitReaderScopedCloser br_closer(&br, &ret);
+ JXL_RETURN_IF_ERROR(Bundle::Read(&br, jpeg_data));
+ JXL_RETURN_IF_ERROR(br.JumpToByteBoundary());
+ in += br.TotalBitsConsumed() / 8;
+ available_in -= br.TotalBitsConsumed() / 8;
+ }
+ JXL_RETURN_IF_ERROR(ret);
+
+ BrotliDecoderState* brotli_dec =
+ BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+
+ struct BrotliDecDeleter {
+ BrotliDecoderState* brotli_dec;
+ ~BrotliDecDeleter() { BrotliDecoderDestroyInstance(brotli_dec); }
+ } brotli_dec_deleter{brotli_dec};
+
+ BrotliDecoderResult result =
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS;
+
+ auto br_read = [&](std::vector<uint8_t>& data) -> Status {
+ size_t available_out = data.size();
+ uint8_t* out = data.data();
+ while (available_out != 0) {
+ if (BrotliDecoderIsFinished(brotli_dec)) {
+ return JXL_FAILURE("Not enough decompressed output");
+ }
+ uint8_t* next_out_before = out;
+ size_t avail_out_before = available_out;
+ msan::MemoryIsInitialized(in, available_in);
+ result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in,
+ &available_out, &out, nullptr);
+ if (result !=
+ BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT &&
+ result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
+ return JXL_FAILURE(
+ "Brotli decoding error: %s\n",
+ BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotli_dec)));
+ }
+ msan::UnpoisonMemory(next_out_before, avail_out_before - available_out);
+ }
+ return true;
+ };
+ size_t num_icc = 0;
+ for (size_t i = 0; i < jpeg_data->app_data.size(); i++) {
+ auto& marker = jpeg_data->app_data[i];
+ if (jpeg_data->app_marker_type[i] != AppMarkerType::kUnknown) {
+ // Set the size of the marker.
+ size_t size_minus_1 = marker.size() - 1;
+ marker[1] = size_minus_1 >> 8;
+ marker[2] = size_minus_1 & 0xFF;
+ if (jpeg_data->app_marker_type[i] == AppMarkerType::kICC) {
+ if (marker.size() < 17) {
+ return JXL_FAILURE("ICC markers must be at least 17 bytes");
+ }
+ marker[0] = 0xE2;
+ memcpy(&marker[3], kIccProfileTag, sizeof kIccProfileTag);
+ marker[15] = ++num_icc;
+ }
+ } else {
+ JXL_RETURN_IF_ERROR(br_read(marker));
+ if (marker[1] * 256u + marker[2] + 1u != marker.size()) {
+ return JXL_FAILURE("Incorrect marker size");
+ }
+ }
+ }
+ for (size_t i = 0; i < jpeg_data->app_data.size(); i++) {
+ auto& marker = jpeg_data->app_data[i];
+ if (jpeg_data->app_marker_type[i] == AppMarkerType::kICC) {
+ marker[16] = num_icc;
+ }
+ if (jpeg_data->app_marker_type[i] == AppMarkerType::kExif) {
+ marker[0] = 0xE1;
+ if (marker.size() < 3 + sizeof kExifTag) {
+ return JXL_FAILURE("Incorrect Exif marker size");
+ }
+ memcpy(&marker[3], kExifTag, sizeof kExifTag);
+ }
+ if (jpeg_data->app_marker_type[i] == AppMarkerType::kXMP) {
+ marker[0] = 0xE1;
+ if (marker.size() < 3 + sizeof kXMPTag) {
+ return JXL_FAILURE("Incorrect XMP marker size");
+ }
+ memcpy(&marker[3], kXMPTag, sizeof kXMPTag);
+ }
+ }
+ // TODO(eustas): actually inject ICC profile and check it fits perfectly.
+ for (size_t i = 0; i < jpeg_data->com_data.size(); i++) {
+ auto& marker = jpeg_data->com_data[i];
+ JXL_RETURN_IF_ERROR(br_read(marker));
+ if (marker[1] * 256u + marker[2] + 1u != marker.size()) {
+ return JXL_FAILURE("Incorrect marker size");
+ }
+ }
+ for (size_t i = 0; i < jpeg_data->inter_marker_data.size(); i++) {
+ JXL_RETURN_IF_ERROR(br_read(jpeg_data->inter_marker_data[i]));
+ }
+ JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data));
+
+ // Check if there is more decompressed output.
+ size_t available_out = 1;
+ uint64_t dummy;
+ uint8_t* next_out = reinterpret_cast<uint8_t*>(&dummy);
+ result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in,
+ &available_out, &next_out, nullptr);
+ if (available_out == 0 ||
+ result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+ return JXL_FAILURE("Excess data in compressed stream");
+ }
+ if (result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
+ return JXL_FAILURE("Incomplete brotli-stream");
+ }
+ if (!BrotliDecoderIsFinished(brotli_dec) ||
+ result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) {
+ return JXL_FAILURE("Corrupted brotli-stream");
+ }
+ if (available_in != 0) {
+ return JXL_FAILURE("Unused data after brotli stream");
+ }
+
+ return true;
+}
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.h b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.h
new file mode 100644
index 0000000000..b9d50bf9f8
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef LIB_JXL_JPEG_DEC_JPEG_DATA_H_
+#define LIB_JXL_JPEG_DEC_JPEG_DATA_H_
+
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data);
+}
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_DEC_JPEG_DATA_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc
new file mode 100644
index 0000000000..f9ae755789
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc
@@ -0,0 +1,1050 @@
+// 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/jxl/jpeg/dec_jpeg_data_writer.h"
+
+#include <stdlib.h>
+#include <string.h> /* for memset, memcpy */
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "lib/jxl/base/bits.h"
+#include "lib/jxl/common.h"
+#include "lib/jxl/image_bundle.h"
+#include "lib/jxl/jpeg/dec_jpeg_serialization_state.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+namespace {
+
+enum struct SerializationStatus {
+ NEEDS_MORE_INPUT,
+ NEEDS_MORE_OUTPUT,
+ ERROR,
+ DONE
+};
+
+const int kJpegPrecision = 8;
+
+// JpegBitWriter: buffer size
+const size_t kJpegBitWriterChunkSize = 16384;
+
+// DCTCodingState: maximum number of correction bits to buffer
+const int kJPEGMaxCorrectionBits = 1u << 16;
+
+// Returns non-zero if and only if x has a zero byte, i.e. one of
+// x & 0xff, x & 0xff00, ..., x & 0xff00000000000000 is zero.
+static JXL_INLINE uint64_t HasZeroByte(uint64_t x) {
+ return (x - 0x0101010101010101ULL) & ~x & 0x8080808080808080ULL;
+}
+
+void JpegBitWriterInit(JpegBitWriter* bw,
+ std::deque<OutputChunk>* output_queue) {
+ bw->output = output_queue;
+ bw->chunk = OutputChunk(kJpegBitWriterChunkSize);
+ bw->pos = 0;
+ bw->put_buffer = 0;
+ bw->put_bits = 64;
+ bw->healthy = true;
+ bw->data = bw->chunk.buffer->data();
+}
+
+static JXL_NOINLINE void SwapBuffer(JpegBitWriter* bw) {
+ bw->chunk.len = bw->pos;
+ bw->output->emplace_back(std::move(bw->chunk));
+ bw->chunk = OutputChunk(kJpegBitWriterChunkSize);
+ bw->data = bw->chunk.buffer->data();
+ bw->pos = 0;
+}
+
+static JXL_INLINE void Reserve(JpegBitWriter* bw, size_t n_bytes) {
+ if (JXL_UNLIKELY((bw->pos + n_bytes) > kJpegBitWriterChunkSize)) {
+ SwapBuffer(bw);
+ }
+}
+
+/**
+ * Writes the given byte to the output, writes an extra zero if byte is 0xFF.
+ *
+ * This method is "careless" - caller must make sure that there is enough
+ * space in the output buffer. Emits up to 2 bytes to buffer.
+ */
+static JXL_INLINE void EmitByte(JpegBitWriter* bw, int byte) {
+ bw->data[bw->pos++] = byte;
+ if (byte == 0xFF) bw->data[bw->pos++] = 0;
+}
+
+static JXL_INLINE void DischargeBitBuffer(JpegBitWriter* bw) {
+ // At this point we are ready to emit the most significant 6 bytes of
+ // put_buffer_ to the output.
+ // The JPEG format requires that after every 0xff byte in the entropy
+ // coded section, there is a zero byte, therefore we first check if any of
+ // the 6 most significant bytes of put_buffer_ is 0xFF.
+ Reserve(bw, 12);
+ if (HasZeroByte(~bw->put_buffer | 0xFFFF)) {
+ // We have a 0xFF byte somewhere, examine each byte and append a zero
+ // byte if necessary.
+ EmitByte(bw, (bw->put_buffer >> 56) & 0xFF);
+ EmitByte(bw, (bw->put_buffer >> 48) & 0xFF);
+ EmitByte(bw, (bw->put_buffer >> 40) & 0xFF);
+ EmitByte(bw, (bw->put_buffer >> 32) & 0xFF);
+ EmitByte(bw, (bw->put_buffer >> 24) & 0xFF);
+ EmitByte(bw, (bw->put_buffer >> 16) & 0xFF);
+ } else {
+ // We don't have any 0xFF bytes, output all 6 bytes without checking.
+ bw->data[bw->pos] = (bw->put_buffer >> 56) & 0xFF;
+ bw->data[bw->pos + 1] = (bw->put_buffer >> 48) & 0xFF;
+ bw->data[bw->pos + 2] = (bw->put_buffer >> 40) & 0xFF;
+ bw->data[bw->pos + 3] = (bw->put_buffer >> 32) & 0xFF;
+ bw->data[bw->pos + 4] = (bw->put_buffer >> 24) & 0xFF;
+ bw->data[bw->pos + 5] = (bw->put_buffer >> 16) & 0xFF;
+ bw->pos += 6;
+ }
+ bw->put_buffer <<= 48;
+ bw->put_bits += 48;
+}
+
+static JXL_INLINE void WriteBits(JpegBitWriter* bw, int nbits, uint64_t bits) {
+ // This is an optimization; if everything goes well,
+ // then |nbits| is positive; if non-existing Huffman symbol is going to be
+ // encoded, its length should be zero; later encoder could check the
+ // "health" of JpegBitWriter.
+ if (nbits == 0) {
+ bw->healthy = false;
+ return;
+ }
+ bw->put_bits -= nbits;
+ bw->put_buffer |= (bits << bw->put_bits);
+ if (bw->put_bits <= 16) DischargeBitBuffer(bw);
+}
+
+void EmitMarker(JpegBitWriter* bw, int marker) {
+ Reserve(bw, 2);
+ JXL_DASSERT(marker != 0xFF);
+ bw->data[bw->pos++] = 0xFF;
+ bw->data[bw->pos++] = marker;
+}
+
+bool JumpToByteBoundary(JpegBitWriter* bw, const uint8_t** pad_bits,
+ const uint8_t* pad_bits_end) {
+ size_t n_bits = bw->put_bits & 7u;
+ uint8_t pad_pattern;
+ if (*pad_bits == nullptr) {
+ pad_pattern = (1u << n_bits) - 1;
+ } else {
+ pad_pattern = 0;
+ const uint8_t* src = *pad_bits;
+ // TODO(eustas): bitwise reading looks insanely ineffective...
+ while (n_bits--) {
+ pad_pattern <<= 1;
+ if (src >= pad_bits_end) return false;
+ // TODO(eustas): DCHECK *src == {0, 1}
+ pad_pattern |= !!*(src++);
+ }
+ *pad_bits = src;
+ }
+
+ Reserve(bw, 16);
+
+ while (bw->put_bits <= 56) {
+ int c = (bw->put_buffer >> 56) & 0xFF;
+ EmitByte(bw, c);
+ bw->put_buffer <<= 8;
+ bw->put_bits += 8;
+ }
+ if (bw->put_bits < 64) {
+ int pad_mask = 0xFFu >> (64 - bw->put_bits);
+ int c = ((bw->put_buffer >> 56) & ~pad_mask) | pad_pattern;
+ EmitByte(bw, c);
+ }
+ bw->put_buffer = 0;
+ bw->put_bits = 64;
+
+ return true;
+}
+
+void JpegBitWriterFinish(JpegBitWriter* bw) {
+ if (bw->pos == 0) return;
+ bw->chunk.len = bw->pos;
+ bw->output->emplace_back(std::move(bw->chunk));
+ bw->chunk = OutputChunk(nullptr, 0);
+ bw->data = nullptr;
+ bw->pos = 0;
+}
+
+void DCTCodingStateInit(DCTCodingState* s) {
+ s->eob_run_ = 0;
+ s->cur_ac_huff_ = nullptr;
+ s->refinement_bits_.clear();
+ s->refinement_bits_.reserve(kJPEGMaxCorrectionBits);
+}
+
+enum OutputModes {
+ kModeHistogram,
+ kModeWrite,
+};
+
+template <int kOutputMode>
+static JXL_INLINE void WriteSymbol(int symbol, HuffmanCodeTable* table,
+ JpegBitWriter* bw) {
+ if (kOutputMode == OutputModes::kModeHistogram) {
+ ++table->depth[symbol];
+ } else {
+ WriteBits(bw, table->depth[symbol], table->code[symbol]);
+ }
+}
+
+// Emit all buffered data to the bit stream using the given Huffman code and
+// bit writer.
+template <int kOutputMode>
+static JXL_INLINE void Flush(DCTCodingState* s, JpegBitWriter* bw) {
+ if (s->eob_run_ > 0) {
+ int nbits = FloorLog2Nonzero<uint32_t>(s->eob_run_);
+ int symbol = nbits << 4u;
+ WriteSymbol<kOutputMode>(symbol, s->cur_ac_huff_, bw);
+ if (nbits > 0) {
+ WriteBits(bw, nbits, s->eob_run_ & ((1 << nbits) - 1));
+ }
+ s->eob_run_ = 0;
+ }
+ for (size_t i = 0; i < s->refinement_bits_.size(); ++i) {
+ WriteBits(bw, 1, s->refinement_bits_[i]);
+ }
+ s->refinement_bits_.clear();
+}
+
+// Buffer some more data at the end-of-band (the last non-zero or newly
+// non-zero coefficient within the [Ss, Se] spectral band).
+template <int kOutputMode>
+static JXL_INLINE void BufferEndOfBand(DCTCodingState* s,
+ HuffmanCodeTable* ac_huff,
+ const std::vector<int>* new_bits,
+ JpegBitWriter* bw) {
+ if (s->eob_run_ == 0) {
+ s->cur_ac_huff_ = ac_huff;
+ }
+ ++s->eob_run_;
+ if (new_bits) {
+ s->refinement_bits_.insert(s->refinement_bits_.end(), new_bits->begin(),
+ new_bits->end());
+ }
+ if (s->eob_run_ == 0x7FFF ||
+ s->refinement_bits_.size() > kJPEGMaxCorrectionBits - kDCTBlockSize + 1) {
+ Flush<kOutputMode>(s, bw);
+ }
+}
+
+bool BuildHuffmanCodeTable(const JPEGHuffmanCode& huff,
+ HuffmanCodeTable* table) {
+ int huff_code[kJpegHuffmanAlphabetSize];
+ // +1 for a sentinel element.
+ uint32_t huff_size[kJpegHuffmanAlphabetSize + 1];
+ int p = 0;
+ for (size_t l = 1; l <= kJpegHuffmanMaxBitLength; ++l) {
+ int i = huff.counts[l];
+ if (p + i > kJpegHuffmanAlphabetSize + 1) {
+ return false;
+ }
+ while (i--) huff_size[p++] = l;
+ }
+
+ if (p == 0) {
+ return true;
+ }
+
+ // Reuse sentinel element.
+ int last_p = p - 1;
+ huff_size[last_p] = 0;
+
+ int code = 0;
+ uint32_t si = huff_size[0];
+ p = 0;
+ while (huff_size[p]) {
+ while ((huff_size[p]) == si) {
+ huff_code[p++] = code;
+ code++;
+ }
+ code <<= 1;
+ si++;
+ }
+ for (p = 0; p < last_p; p++) {
+ int i = huff.values[p];
+ table->depth[i] = huff_size[p];
+ table->code[i] = huff_code[p];
+ }
+ return true;
+}
+
+bool EncodeSOI(SerializationState* state) {
+ state->output_queue.push_back(OutputChunk({0xFF, 0xD8}));
+ return true;
+}
+
+bool EncodeEOI(const JPEGData& jpg, SerializationState* state) {
+ state->output_queue.push_back(OutputChunk({0xFF, 0xD9}));
+ state->output_queue.emplace_back(jpg.tail_data);
+ return true;
+}
+
+bool EncodeSOF(const JPEGData& jpg, uint8_t marker, SerializationState* state) {
+ if (marker <= 0xC2) state->is_progressive = (marker == 0xC2);
+
+ const size_t n_comps = jpg.components.size();
+ const size_t marker_len = 8 + 3 * n_comps;
+ state->output_queue.emplace_back(marker_len + 2);
+ uint8_t* data = state->output_queue.back().buffer->data();
+ size_t pos = 0;
+ data[pos++] = 0xFF;
+ data[pos++] = marker;
+ data[pos++] = marker_len >> 8u;
+ data[pos++] = marker_len & 0xFFu;
+ data[pos++] = kJpegPrecision;
+ data[pos++] = jpg.height >> 8u;
+ data[pos++] = jpg.height & 0xFFu;
+ data[pos++] = jpg.width >> 8u;
+ data[pos++] = jpg.width & 0xFFu;
+ data[pos++] = n_comps;
+ for (size_t i = 0; i < n_comps; ++i) {
+ data[pos++] = jpg.components[i].id;
+ data[pos++] = ((jpg.components[i].h_samp_factor << 4u) |
+ (jpg.components[i].v_samp_factor));
+ const size_t quant_idx = jpg.components[i].quant_idx;
+ if (quant_idx >= jpg.quant.size()) return false;
+ data[pos++] = jpg.quant[quant_idx].index;
+ }
+ return true;
+}
+
+bool EncodeSOS(const JPEGData& jpg, const JPEGScanInfo& scan_info,
+ SerializationState* state) {
+ const size_t n_scans = scan_info.num_components;
+ const size_t marker_len = 6 + 2 * n_scans;
+ state->output_queue.emplace_back(marker_len + 2);
+ uint8_t* data = state->output_queue.back().buffer->data();
+ size_t pos = 0;
+ data[pos++] = 0xFF;
+ data[pos++] = 0xDA;
+ data[pos++] = marker_len >> 8u;
+ data[pos++] = marker_len & 0xFFu;
+ data[pos++] = n_scans;
+ for (size_t i = 0; i < n_scans; ++i) {
+ const JPEGComponentScanInfo& si = scan_info.components[i];
+ if (si.comp_idx >= jpg.components.size()) return false;
+ data[pos++] = jpg.components[si.comp_idx].id;
+ data[pos++] = (si.dc_tbl_idx << 4u) + si.ac_tbl_idx;
+ }
+ data[pos++] = scan_info.Ss;
+ data[pos++] = scan_info.Se;
+ data[pos++] = ((scan_info.Ah << 4u) | (scan_info.Al));
+ return true;
+}
+
+bool EncodeDHT(const JPEGData& jpg, SerializationState* state) {
+ const std::vector<JPEGHuffmanCode>& huffman_code = jpg.huffman_code;
+
+ size_t marker_len = 2;
+ for (size_t i = state->dht_index; i < huffman_code.size(); ++i) {
+ const JPEGHuffmanCode& huff = huffman_code[i];
+ marker_len += kJpegHuffmanMaxBitLength;
+ for (size_t j = 0; j < huff.counts.size(); ++j) {
+ marker_len += huff.counts[j];
+ }
+ if (huff.is_last) break;
+ }
+ state->output_queue.emplace_back(marker_len + 2);
+ uint8_t* data = state->output_queue.back().buffer->data();
+ size_t pos = 0;
+ data[pos++] = 0xFF;
+ data[pos++] = 0xC4;
+ data[pos++] = marker_len >> 8u;
+ data[pos++] = marker_len & 0xFFu;
+ while (true) {
+ const size_t huffman_code_index = state->dht_index++;
+ if (huffman_code_index >= huffman_code.size()) {
+ return false;
+ }
+ const JPEGHuffmanCode& huff = huffman_code[huffman_code_index];
+ size_t index = huff.slot_id;
+ HuffmanCodeTable* huff_table;
+ if (index & 0x10) {
+ index -= 0x10;
+ huff_table = &state->ac_huff_table[index];
+ } else {
+ huff_table = &state->dc_huff_table[index];
+ }
+ // TODO(eustas): cache
+ // TODO(eustas): set up non-existing symbols
+ if (!BuildHuffmanCodeTable(huff, huff_table)) {
+ return false;
+ }
+ size_t total_count = 0;
+ size_t max_length = 0;
+ for (size_t i = 0; i < huff.counts.size(); ++i) {
+ if (huff.counts[i] != 0) {
+ max_length = i;
+ }
+ total_count += huff.counts[i];
+ }
+ --total_count;
+ data[pos++] = huff.slot_id;
+ for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
+ data[pos++] = (i == max_length ? huff.counts[i] - 1 : huff.counts[i]);
+ }
+ for (size_t i = 0; i < total_count; ++i) {
+ data[pos++] = huff.values[i];
+ }
+ if (huff.is_last) break;
+ }
+ return true;
+}
+
+bool EncodeDQT(const JPEGData& jpg, SerializationState* state) {
+ int marker_len = 2;
+ for (size_t i = state->dqt_index; i < jpg.quant.size(); ++i) {
+ const JPEGQuantTable& table = jpg.quant[i];
+ marker_len += 1 + (table.precision ? 2 : 1) * kDCTBlockSize;
+ if (table.is_last) break;
+ }
+ state->output_queue.emplace_back(marker_len + 2);
+ uint8_t* data = state->output_queue.back().buffer->data();
+ size_t pos = 0;
+ data[pos++] = 0xFF;
+ data[pos++] = 0xDB;
+ data[pos++] = marker_len >> 8u;
+ data[pos++] = marker_len & 0xFFu;
+ while (true) {
+ const size_t idx = state->dqt_index++;
+ if (idx >= jpg.quant.size()) {
+ return false; // corrupt input
+ }
+ const JPEGQuantTable& table = jpg.quant[idx];
+ data[pos++] = (table.precision << 4u) + table.index;
+ for (size_t i = 0; i < kDCTBlockSize; ++i) {
+ int val_idx = kJPEGNaturalOrder[i];
+ int val = table.values[val_idx];
+ if (table.precision) {
+ data[pos++] = val >> 8u;
+ }
+ data[pos++] = val & 0xFFu;
+ }
+ if (table.is_last) break;
+ }
+ return true;
+}
+
+bool EncodeDRI(const JPEGData& jpg, SerializationState* state) {
+ state->seen_dri_marker = true;
+ OutputChunk dri_marker = {0xFF,
+ 0xDD,
+ 0,
+ 4,
+ static_cast<uint8_t>(jpg.restart_interval >> 8),
+ static_cast<uint8_t>(jpg.restart_interval & 0xFF)};
+ state->output_queue.push_back(std::move(dri_marker));
+ return true;
+}
+
+bool EncodeRestart(uint8_t marker, SerializationState* state) {
+ state->output_queue.push_back(OutputChunk({0xFF, marker}));
+ return true;
+}
+
+bool EncodeAPP(const JPEGData& jpg, uint8_t marker, SerializationState* state) {
+ // TODO(eustas): check that marker corresponds to payload?
+ (void)marker;
+
+ size_t app_index = state->app_index++;
+ if (app_index >= jpg.app_data.size()) return false;
+ state->output_queue.push_back(OutputChunk({0xFF}));
+ state->output_queue.emplace_back(jpg.app_data[app_index]);
+ return true;
+}
+
+bool EncodeCOM(const JPEGData& jpg, SerializationState* state) {
+ size_t com_index = state->com_index++;
+ if (com_index >= jpg.com_data.size()) return false;
+ state->output_queue.push_back(OutputChunk({0xFF}));
+ state->output_queue.emplace_back(jpg.com_data[com_index]);
+ return true;
+}
+
+bool EncodeInterMarkerData(const JPEGData& jpg, SerializationState* state) {
+ size_t index = state->data_index++;
+ if (index >= jpg.inter_marker_data.size()) return false;
+ state->output_queue.emplace_back(jpg.inter_marker_data[index]);
+ return true;
+}
+
+template <int kOutputMode>
+bool EncodeDCTBlockSequential(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
+ HuffmanCodeTable* ac_huff, int num_zero_runs,
+ coeff_t* last_dc_coeff, JpegBitWriter* bw) {
+ coeff_t temp2;
+ coeff_t temp;
+ temp2 = coeffs[0];
+ temp = temp2 - *last_dc_coeff;
+ *last_dc_coeff = temp2;
+ temp2 = temp;
+ if (temp < 0) {
+ temp = -temp;
+ if (temp < 0) return false;
+ temp2--;
+ }
+ int dc_nbits = (temp == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp) + 1);
+ WriteSymbol<kOutputMode>(dc_nbits, dc_huff, bw);
+ if (dc_nbits >= 12) return false;
+ if (dc_nbits > 0) {
+ WriteBits(bw, dc_nbits, temp2 & ((1u << dc_nbits) - 1));
+ }
+ int r = 0;
+ for (int k = 1; k < 64; ++k) {
+ if ((temp = coeffs[kJPEGNaturalOrder[k]]) == 0) {
+ r++;
+ continue;
+ }
+ if (temp < 0) {
+ temp = -temp;
+ if (temp < 0) return false;
+ temp2 = ~temp;
+ } else {
+ temp2 = temp;
+ }
+ while (r > 15) {
+ WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
+ r -= 16;
+ }
+ int ac_nbits = FloorLog2Nonzero<uint32_t>(temp) + 1;
+ if (ac_nbits >= 16) return false;
+ int symbol = (r << 4u) + ac_nbits;
+ WriteSymbol<kOutputMode>(symbol, ac_huff, bw);
+ WriteBits(bw, ac_nbits, temp2 & ((1 << ac_nbits) - 1));
+ r = 0;
+ }
+ for (int i = 0; i < num_zero_runs; ++i) {
+ WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
+ r -= 16;
+ }
+ if (r > 0) {
+ WriteSymbol<kOutputMode>(0, ac_huff, bw);
+ }
+ return true;
+}
+
+template <int kOutputMode>
+bool EncodeDCTBlockProgressive(const coeff_t* coeffs, HuffmanCodeTable* dc_huff,
+ HuffmanCodeTable* ac_huff, int Ss, int Se,
+ int Al, int num_zero_runs,
+ DCTCodingState* coding_state,
+ coeff_t* last_dc_coeff, JpegBitWriter* bw) {
+ bool eob_run_allowed = Ss > 0;
+ coeff_t temp2;
+ coeff_t temp;
+ if (Ss == 0) {
+ temp2 = coeffs[0] >> Al;
+ temp = temp2 - *last_dc_coeff;
+ *last_dc_coeff = temp2;
+ temp2 = temp;
+ if (temp < 0) {
+ temp = -temp;
+ if (temp < 0) return false;
+ temp2--;
+ }
+ int nbits = (temp == 0) ? 0 : (FloorLog2Nonzero<uint32_t>(temp) + 1);
+ WriteSymbol<kOutputMode>(nbits, dc_huff, bw);
+ if (nbits > 0) {
+ WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
+ }
+ ++Ss;
+ }
+ if (Ss > Se) {
+ return true;
+ }
+ int r = 0;
+ for (int k = Ss; k <= Se; ++k) {
+ if ((temp = coeffs[kJPEGNaturalOrder[k]]) == 0) {
+ r++;
+ continue;
+ }
+ if (temp < 0) {
+ temp = -temp;
+ if (temp < 0) return false;
+ temp >>= Al;
+ temp2 = ~temp;
+ } else {
+ temp >>= Al;
+ temp2 = temp;
+ }
+ if (temp == 0) {
+ r++;
+ continue;
+ }
+ Flush<kOutputMode>(coding_state, bw);
+ while (r > 15) {
+ WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
+ r -= 16;
+ }
+ int nbits = FloorLog2Nonzero<uint32_t>(temp) + 1;
+ int symbol = (r << 4u) + nbits;
+ WriteSymbol<kOutputMode>(symbol, ac_huff, bw);
+ WriteBits(bw, nbits, temp2 & ((1 << nbits) - 1));
+ r = 0;
+ }
+ if (num_zero_runs > 0) {
+ Flush<kOutputMode>(coding_state, bw);
+ for (int i = 0; i < num_zero_runs; ++i) {
+ WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
+ r -= 16;
+ }
+ }
+ if (r > 0) {
+ BufferEndOfBand<kOutputMode>(coding_state, ac_huff, nullptr, bw);
+ if (!eob_run_allowed) {
+ Flush<kOutputMode>(coding_state, bw);
+ }
+ }
+ return true;
+}
+
+template <int kOutputMode>
+bool EncodeRefinementBits(const coeff_t* coeffs, HuffmanCodeTable* ac_huff,
+ int Ss, int Se, int Al, DCTCodingState* coding_state,
+ JpegBitWriter* bw) {
+ bool eob_run_allowed = Ss > 0;
+ if (Ss == 0) {
+ // Emit next bit of DC component.
+ WriteBits(bw, 1, (coeffs[0] >> Al) & 1);
+ ++Ss;
+ }
+ if (Ss > Se) {
+ return true;
+ }
+ int abs_values[kDCTBlockSize];
+ int eob = 0;
+ for (int k = Ss; k <= Se; k++) {
+ const coeff_t abs_val = std::abs(coeffs[kJPEGNaturalOrder[k]]);
+ abs_values[k] = abs_val >> Al;
+ if (abs_values[k] == 1) {
+ eob = k;
+ }
+ }
+ int r = 0;
+ std::vector<int> refinement_bits;
+ refinement_bits.reserve(kDCTBlockSize);
+ for (int k = Ss; k <= Se; k++) {
+ if (abs_values[k] == 0) {
+ r++;
+ continue;
+ }
+ while (r > 15 && k <= eob) {
+ Flush<kOutputMode>(coding_state, bw);
+ WriteSymbol<kOutputMode>(0xf0, ac_huff, bw);
+ r -= 16;
+ for (int bit : refinement_bits) {
+ WriteBits(bw, 1, bit);
+ }
+ refinement_bits.clear();
+ }
+ if (abs_values[k] > 1) {
+ refinement_bits.push_back(abs_values[k] & 1u);
+ continue;
+ }
+ Flush<kOutputMode>(coding_state, bw);
+ int symbol = (r << 4u) + 1;
+ int new_non_zero_bit = (coeffs[kJPEGNaturalOrder[k]] < 0) ? 0 : 1;
+ WriteSymbol<kOutputMode>(symbol, ac_huff, bw);
+ WriteBits(bw, 1, new_non_zero_bit);
+ for (int bit : refinement_bits) {
+ WriteBits(bw, 1, bit);
+ }
+ refinement_bits.clear();
+ r = 0;
+ }
+ if (r > 0 || !refinement_bits.empty()) {
+ BufferEndOfBand<kOutputMode>(coding_state, ac_huff, &refinement_bits, bw);
+ if (!eob_run_allowed) {
+ Flush<kOutputMode>(coding_state, bw);
+ }
+ }
+ return true;
+}
+
+size_t NumHistograms(const JPEGData& jpg) {
+ size_t num = 0;
+ for (const auto& si : jpg.scan_info) {
+ num += si.num_components;
+ }
+ return num;
+}
+
+size_t HistogramIndex(const JPEGData& jpg, size_t scan_index,
+ size_t component_index) {
+ size_t idx = 0;
+ for (size_t i = 0; i < scan_index; ++i) {
+ idx += jpg.scan_info[i].num_components;
+ }
+ return idx + component_index;
+}
+
+template <int kMode, int kOutputMode>
+SerializationStatus JXL_NOINLINE DoEncodeScan(const JPEGData& jpg,
+ SerializationState* state) {
+ const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
+ EncodeScanState& ss = state->scan_state;
+
+ const int restart_interval =
+ state->seen_dri_marker ? jpg.restart_interval : 0;
+
+ const auto get_next_extra_zero_run_index = [&ss, &scan_info]() -> int {
+ if (ss.extra_zero_runs_pos < scan_info.extra_zero_runs.size()) {
+ return scan_info.extra_zero_runs[ss.extra_zero_runs_pos].block_idx;
+ } else {
+ return -1;
+ }
+ };
+
+ const auto get_next_reset_point = [&ss, &scan_info]() -> int {
+ if (ss.next_reset_point_pos < scan_info.reset_points.size()) {
+ return scan_info.reset_points[ss.next_reset_point_pos++];
+ } else {
+ return -1;
+ }
+ };
+
+ if (ss.stage == EncodeScanState::HEAD) {
+ if (!EncodeSOS(jpg, scan_info, state)) return SerializationStatus::ERROR;
+ JpegBitWriterInit(&ss.bw, &state->output_queue);
+ DCTCodingStateInit(&ss.coding_state);
+ ss.restarts_to_go = restart_interval;
+ ss.next_restart_marker = 0;
+ ss.block_scan_index = 0;
+ ss.extra_zero_runs_pos = 0;
+ ss.next_extra_zero_run_index = get_next_extra_zero_run_index();
+ ss.next_reset_point_pos = 0;
+ ss.next_reset_point = get_next_reset_point();
+ ss.mcu_y = 0;
+ memset(ss.last_dc_coeff, 0, sizeof(ss.last_dc_coeff));
+ ss.stage = EncodeScanState::BODY;
+ }
+ JpegBitWriter* bw = &ss.bw;
+ DCTCodingState* coding_state = &ss.coding_state;
+
+ JXL_DASSERT(ss.stage == EncodeScanState::BODY);
+
+ // "Non-interleaved" means color data comes in separate scans, in other words
+ // each scan can contain only one color component.
+ const bool is_interleaved = (scan_info.num_components > 1);
+ int MCUs_per_row = 0;
+ int MCU_rows = 0;
+ jpg.CalculateMcuSize(scan_info, &MCUs_per_row, &MCU_rows);
+ const bool is_progressive = state->is_progressive;
+ const int Al = is_progressive ? scan_info.Al : 0;
+ const int Ss = is_progressive ? scan_info.Ss : 0;
+ const int Se = is_progressive ? scan_info.Se : 63;
+
+ // DC-only is defined by [0..0] spectral range.
+ const bool want_ac = ((Ss != 0) || (Se != 0));
+ // TODO: support streaming decoding again.
+ const bool complete_ac = true;
+ const bool has_ac = true;
+ if (want_ac && !has_ac) return SerializationStatus::NEEDS_MORE_INPUT;
+
+ // |has_ac| implies |complete_dc| but not vice versa; for the sake of
+ // simplicity we pretend they are equal, because they are separated by just a
+ // few bytes of input.
+ const bool complete_dc = has_ac;
+ const bool complete = want_ac ? complete_ac : complete_dc;
+ // When "incomplete" |ac_dc| tracks information about current ("incomplete")
+ // band parsing progress.
+
+ // FIXME: Is this always complete?
+ // const int last_mcu_y =
+ // complete ? MCU_rows : parsing_state.internal->ac_dc.next_mcu_y *
+ // v_group;
+ (void)complete;
+ const int last_mcu_y = complete ? MCU_rows : 0;
+
+ for (; ss.mcu_y < last_mcu_y; ++ss.mcu_y) {
+ for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) {
+ // Possibly emit a restart marker.
+ if (restart_interval > 0 && ss.restarts_to_go == 0) {
+ Flush<kOutputMode>(coding_state, bw);
+ if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
+ return SerializationStatus::ERROR;
+ }
+ EmitMarker(bw, 0xD0 + ss.next_restart_marker);
+ ss.next_restart_marker += 1;
+ ss.next_restart_marker &= 0x7;
+ ss.restarts_to_go = restart_interval;
+ memset(ss.last_dc_coeff, 0, sizeof(ss.last_dc_coeff));
+ }
+ // Encode one MCU
+ for (size_t i = 0; i < scan_info.num_components; ++i) {
+ const JPEGComponentScanInfo& si = scan_info.components[i];
+ const JPEGComponent& c = jpg.components[si.comp_idx];
+ size_t dc_tbl_idx = (kOutputMode == OutputModes::kModeHistogram
+ ? HistogramIndex(jpg, state->scan_index, i)
+ : si.dc_tbl_idx);
+ size_t ac_tbl_idx = (kOutputMode == OutputModes::kModeHistogram
+ ? HistogramIndex(jpg, state->scan_index, i)
+ : si.ac_tbl_idx);
+ HuffmanCodeTable* dc_huff = &state->dc_huff_table[dc_tbl_idx];
+ HuffmanCodeTable* ac_huff = &state->ac_huff_table[ac_tbl_idx];
+ int n_blocks_y = is_interleaved ? c.v_samp_factor : 1;
+ int n_blocks_x = is_interleaved ? c.h_samp_factor : 1;
+ for (int iy = 0; iy < n_blocks_y; ++iy) {
+ for (int ix = 0; ix < n_blocks_x; ++ix) {
+ int block_y = ss.mcu_y * n_blocks_y + iy;
+ int block_x = mcu_x * n_blocks_x + ix;
+ int block_idx = block_y * c.width_in_blocks + block_x;
+ if (ss.block_scan_index == ss.next_reset_point) {
+ Flush<kOutputMode>(coding_state, bw);
+ ss.next_reset_point = get_next_reset_point();
+ }
+ int num_zero_runs = 0;
+ if (ss.block_scan_index == ss.next_extra_zero_run_index) {
+ num_zero_runs = scan_info.extra_zero_runs[ss.extra_zero_runs_pos]
+ .num_extra_zero_runs;
+ ++ss.extra_zero_runs_pos;
+ ss.next_extra_zero_run_index = get_next_extra_zero_run_index();
+ }
+ const coeff_t* coeffs = &c.coeffs[block_idx << 6];
+ bool ok;
+ if (kMode == 0) {
+ ok = EncodeDCTBlockSequential<kOutputMode>(
+ coeffs, dc_huff, ac_huff, num_zero_runs,
+ ss.last_dc_coeff + si.comp_idx, bw);
+ } else if (kMode == 1) {
+ ok = EncodeDCTBlockProgressive<kOutputMode>(
+ coeffs, dc_huff, ac_huff, Ss, Se, Al, num_zero_runs,
+ coding_state, ss.last_dc_coeff + si.comp_idx, bw);
+ } else {
+ ok = EncodeRefinementBits<kOutputMode>(coeffs, ac_huff, Ss, Se,
+ Al, coding_state, bw);
+ }
+ if (!ok) return SerializationStatus::ERROR;
+ ++ss.block_scan_index;
+ }
+ }
+ }
+ --ss.restarts_to_go;
+ }
+ }
+ if (ss.mcu_y < MCU_rows) {
+ if (!bw->healthy) return SerializationStatus::ERROR;
+ return SerializationStatus::NEEDS_MORE_INPUT;
+ }
+ Flush<kOutputMode>(coding_state, bw);
+ if (!JumpToByteBoundary(bw, &state->pad_bits, state->pad_bits_end)) {
+ return SerializationStatus::ERROR;
+ }
+ JpegBitWriterFinish(bw);
+ ss.stage = EncodeScanState::HEAD;
+ state->scan_index++;
+ if (!bw->healthy) return SerializationStatus::ERROR;
+
+ return SerializationStatus::DONE;
+}
+
+template <int kOutputMode>
+static SerializationStatus JXL_INLINE EncodeScan(const JPEGData& jpg,
+ SerializationState* state) {
+ const JPEGScanInfo& scan_info = jpg.scan_info[state->scan_index];
+ const bool is_progressive = state->is_progressive;
+ const int Al = is_progressive ? scan_info.Al : 0;
+ const int Ah = is_progressive ? scan_info.Ah : 0;
+ const int Ss = is_progressive ? scan_info.Ss : 0;
+ const int Se = is_progressive ? scan_info.Se : 63;
+ const bool need_sequential =
+ !is_progressive || (Ah == 0 && Al == 0 && Ss == 0 && Se == 63);
+ if (need_sequential) {
+ return DoEncodeScan<0, kOutputMode>(jpg, state);
+ } else if (Ah == 0) {
+ return DoEncodeScan<1, kOutputMode>(jpg, state);
+ } else {
+ return DoEncodeScan<2, kOutputMode>(jpg, state);
+ }
+}
+
+template <int kOutputMode>
+SerializationStatus SerializeSection(uint8_t marker, SerializationState* state,
+ const JPEGData& jpg) {
+ const auto to_status = [](bool result) {
+ return result ? SerializationStatus::DONE : SerializationStatus::ERROR;
+ };
+ // TODO(eustas): add and use marker enum
+ switch (marker) {
+ case 0xC0:
+ case 0xC1:
+ case 0xC2:
+ case 0xC9:
+ case 0xCA:
+ return to_status(EncodeSOF(jpg, marker, state));
+
+ case 0xC4:
+ return to_status((kOutputMode == OutputModes::kModeHistogram) ||
+ EncodeDHT(jpg, state));
+
+ case 0xD0:
+ case 0xD1:
+ case 0xD2:
+ case 0xD3:
+ case 0xD4:
+ case 0xD5:
+ case 0xD6:
+ case 0xD7:
+ return to_status(EncodeRestart(marker, state));
+
+ case 0xD9:
+ return to_status(EncodeEOI(jpg, state));
+
+ case 0xDA:
+ return EncodeScan<kOutputMode>(jpg, state);
+
+ case 0xDB:
+ return to_status(EncodeDQT(jpg, state));
+
+ case 0xDD:
+ return to_status(EncodeDRI(jpg, state));
+
+ case 0xE0:
+ case 0xE1:
+ case 0xE2:
+ case 0xE3:
+ case 0xE4:
+ case 0xE5:
+ case 0xE6:
+ case 0xE7:
+ case 0xE8:
+ case 0xE9:
+ case 0xEA:
+ case 0xEB:
+ case 0xEC:
+ case 0xED:
+ case 0xEE:
+ case 0xEF:
+ return to_status(EncodeAPP(jpg, marker, state));
+
+ case 0xFE:
+ return to_status(EncodeCOM(jpg, state));
+
+ case 0xFF:
+ return to_status(EncodeInterMarkerData(jpg, state));
+
+ default:
+ return SerializationStatus::ERROR;
+ }
+}
+
+// TODO(veluca): add streaming support again.
+template <int kOutputMode>
+Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
+ SerializationState* ss) {
+ const auto maybe_push_output = [&]() -> Status {
+ if (ss->stage != SerializationState::STAGE_ERROR) {
+ while (!ss->output_queue.empty()) {
+ auto& chunk = ss->output_queue.front();
+ size_t num_written = out(chunk.next, chunk.len);
+ if (num_written == 0 && chunk.len > 0) {
+ return StatusMessage(Status(StatusCode::kNotEnoughBytes),
+ "Failed to write output");
+ }
+ chunk.len -= num_written;
+ if (chunk.len == 0) {
+ ss->output_queue.pop_front();
+ }
+ }
+ }
+ return true;
+ };
+
+ while (true) {
+ switch (ss->stage) {
+ case SerializationState::STAGE_INIT: {
+ // Valid Brunsli requires, at least, 0xD9 marker.
+ // This might happen on corrupted stream, or on unconditioned JPEGData.
+ // TODO(eustas): check D9 in the only one and is the last one.
+ if (jpg.marker_order.empty()) {
+ ss->stage = SerializationState::STAGE_ERROR;
+ break;
+ }
+ if (kOutputMode == OutputModes::kModeHistogram) {
+ size_t num_histo = NumHistograms(jpg);
+ ss->dc_huff_table.resize(num_histo);
+ ss->ac_huff_table.resize(num_histo);
+ for (size_t i = 0; i < num_histo; ++i) {
+ ss->dc_huff_table[i].InitDepths();
+ ss->ac_huff_table[i].InitDepths();
+ }
+ } else {
+ ss->dc_huff_table.resize(kMaxHuffmanTables);
+ ss->ac_huff_table.resize(kMaxHuffmanTables);
+ }
+ if (jpg.has_zero_padding_bit) {
+ ss->pad_bits = jpg.padding_bits.data();
+ ss->pad_bits_end = ss->pad_bits + jpg.padding_bits.size();
+ }
+
+ EncodeSOI(ss);
+ JXL_QUIET_RETURN_IF_ERROR(maybe_push_output());
+ ss->stage = SerializationState::STAGE_SERIALIZE_SECTION;
+ break;
+ }
+
+ case SerializationState::STAGE_SERIALIZE_SECTION: {
+ if (ss->section_index >= jpg.marker_order.size()) {
+ ss->stage = SerializationState::STAGE_DONE;
+ break;
+ }
+ uint8_t marker = jpg.marker_order[ss->section_index];
+ SerializationStatus status =
+ SerializeSection<kOutputMode>(marker, ss, jpg);
+ if (status == SerializationStatus::ERROR) {
+ JXL_WARNING("Failed to encode marker 0x%.2x", marker);
+ ss->stage = SerializationState::STAGE_ERROR;
+ break;
+ }
+ JXL_QUIET_RETURN_IF_ERROR(maybe_push_output());
+ if (status == SerializationStatus::NEEDS_MORE_INPUT) {
+ return JXL_FAILURE("Incomplete serialization data");
+ } else if (status != SerializationStatus::DONE) {
+ JXL_DASSERT(false);
+ ss->stage = SerializationState::STAGE_ERROR;
+ break;
+ }
+ ++ss->section_index;
+ break;
+ }
+
+ case SerializationState::STAGE_DONE:
+ JXL_ASSERT(ss->output_queue.empty());
+ if (ss->pad_bits != nullptr && ss->pad_bits != ss->pad_bits_end) {
+ return JXL_FAILURE("Invalid number of padding bits.");
+ }
+ return true;
+
+ case SerializationState::STAGE_ERROR:
+ return JXL_FAILURE("JPEG serialization error");
+ }
+ }
+}
+
+} // namespace
+
+Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out) {
+ SerializationState ss;
+ return WriteJpegInternal<OutputModes::kModeWrite>(jpg, out, &ss);
+}
+
+Status ProcessJpeg(const JPEGData& jpg, SerializationState* ss) {
+ auto nullout = [](const uint8_t* buf, size_t len) { return len; };
+ return WriteJpegInternal<OutputModes::kModeHistogram>(jpg, nullout, ss);
+}
+
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.h b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.h
new file mode 100644
index 0000000000..9ccfb749a8
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.h
@@ -0,0 +1,35 @@
+// 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.
+
+// Functions for writing a JPEGData object into a jpeg byte stream.
+
+#ifndef LIB_JXL_JPEG_DEC_JPEG_DATA_WRITER_H_
+#define LIB_JXL_JPEG_DEC_JPEG_DATA_WRITER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <functional>
+
+#include "lib/jxl/jpeg/dec_jpeg_serialization_state.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+// Function type used to write len bytes into buf. Returns the number of bytes
+// written.
+using JPEGOutput = std::function<size_t(const uint8_t* buf, size_t len)>;
+
+Status WriteJpeg(const JPEGData& jpg, const JPEGOutput& out);
+
+// Same as WriteJpeg, but instead of writing to the output, collects statistics
+// about the bit-stream into `ss`.
+Status ProcessJpeg(const JPEGData& jpg, SerializationState* ss);
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_DEC_JPEG_DATA_WRITER_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_output_chunk.h b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_output_chunk.h
new file mode 100644
index 0000000000..e003c04952
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_output_chunk.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef LIB_JXL_JPEG_DEC_JPEG_OUTPUT_CHUNK_H_
+#define LIB_JXL_JPEG_DEC_JPEG_OUTPUT_CHUNK_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <initializer_list>
+#include <memory>
+#include <vector>
+
+namespace jxl {
+namespace jpeg {
+
+/**
+ * A chunk of output data.
+ *
+ * Data producer creates OutputChunks and adds them to the end output queue.
+ * Once control flow leaves the producer code, it is considered that chunk of
+ * data is final and can not be changed; to underline this fact |next| is a
+ * const-pointer.
+ *
+ * Data consumer removes OutputChunks from the beginning of the output queue.
+ * It is possible to consume OutputChunks partially, by updating |next| and
+ * |len|.
+ *
+ * There are 2 types of output chunks:
+ * - owning: actual data is stored in |buffer| field; producer fills data after
+ * the instance it created; it is legal to reduce |len| to show that not all
+ * the capacity of |buffer| is used
+ * - non-owning: represents the data stored (owned) somewhere else
+ */
+struct OutputChunk {
+ // Non-owning
+ template <typename Bytes>
+ explicit OutputChunk(Bytes& bytes) : len(bytes.size()) {
+ // Deal both with const qualifier and data type.
+ const void* src = bytes.data();
+ next = reinterpret_cast<const uint8_t*>(src);
+ }
+
+ // Non-owning
+ OutputChunk(const uint8_t* data, size_t size) : next(data), len(size) {}
+
+ // Owning
+ explicit OutputChunk(size_t size = 0) {
+ buffer.reset(new std::vector<uint8_t>(size));
+ next = buffer->data();
+ len = size;
+ }
+
+ // Owning
+ OutputChunk(std::initializer_list<uint8_t> bytes) {
+ buffer.reset(new std::vector<uint8_t>(bytes));
+ next = buffer->data();
+ len = bytes.size();
+ }
+
+ const uint8_t* next;
+ size_t len;
+ // TODO(veluca): consider removing the unique_ptr.
+ std::unique_ptr<std::vector<uint8_t>> buffer;
+};
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_DEC_JPEG_OUTPUT_CHUNK_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_serialization_state.h b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_serialization_state.h
new file mode 100644
index 0000000000..40ce450a76
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_serialization_state.h
@@ -0,0 +1,96 @@
+// 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.
+
+#ifndef LIB_JXL_JPEG_DEC_JPEG_SERIALIZATION_STATE_H_
+#define LIB_JXL_JPEG_DEC_JPEG_SERIALIZATION_STATE_H_
+
+#include <deque>
+#include <vector>
+
+#include "lib/jxl/jpeg/dec_jpeg_output_chunk.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+struct HuffmanCodeTable {
+ int depth[256];
+ int code[256];
+ void InitDepths() { std::fill(std::begin(depth), std::end(depth), 0); }
+};
+
+// Handles the packing of bits into output bytes.
+struct JpegBitWriter {
+ bool healthy;
+ std::deque<OutputChunk>* output;
+ OutputChunk chunk;
+ uint8_t* data;
+ size_t pos;
+ uint64_t put_buffer;
+ int put_bits;
+};
+
+// Holds data that is buffered between 8x8 blocks in progressive mode.
+struct DCTCodingState {
+ // The run length of end-of-band symbols in a progressive scan.
+ int eob_run_;
+ // The huffman table to be used when flushing the state.
+ HuffmanCodeTable* cur_ac_huff_;
+ // The sequence of currently buffered refinement bits for a successive
+ // approximation scan (one where Ah > 0).
+ std::vector<int> refinement_bits_;
+};
+
+struct EncodeScanState {
+ enum Stage { HEAD, BODY };
+
+ Stage stage = HEAD;
+
+ int mcu_y;
+ JpegBitWriter bw;
+ coeff_t last_dc_coeff[kMaxComponents] = {0};
+ int restarts_to_go;
+ int next_restart_marker;
+ int block_scan_index;
+ DCTCodingState coding_state;
+ size_t extra_zero_runs_pos;
+ int next_extra_zero_run_index;
+ size_t next_reset_point_pos;
+ int next_reset_point;
+};
+
+struct SerializationState {
+ enum Stage {
+ STAGE_INIT,
+ STAGE_SERIALIZE_SECTION,
+ STAGE_DONE,
+ STAGE_ERROR,
+ };
+
+ Stage stage = STAGE_INIT;
+
+ std::deque<OutputChunk> output_queue;
+
+ size_t section_index = 0;
+ int dht_index = 0;
+ int dqt_index = 0;
+ int app_index = 0;
+ int com_index = 0;
+ int data_index = 0;
+ int scan_index = 0;
+ std::vector<HuffmanCodeTable> dc_huff_table;
+ std::vector<HuffmanCodeTable> ac_huff_table;
+ const uint8_t* pad_bits = nullptr;
+ const uint8_t* pad_bits_end = nullptr;
+ bool seen_dri_marker = false;
+ bool is_progressive = false;
+
+ EncodeScanState scan_state;
+};
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_DEC_JPEG_SERIALIZATION_STATE_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc
new file mode 100644
index 0000000000..842612f4ab
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc
@@ -0,0 +1,384 @@
+// 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/jxl/jpeg/enc_jpeg_data.h"
+
+#include <brotli/encode.h>
+#include <stdio.h>
+
+#include "lib/jxl/enc_fields.h"
+#include "lib/jxl/image_bundle.h"
+#include "lib/jxl/jpeg/enc_jpeg_data_reader.h"
+#include "lib/jxl/luminance.h"
+#include "lib/jxl/sanitizers.h"
+
+namespace jxl {
+namespace jpeg {
+
+namespace {
+
+constexpr int BITS_IN_JSAMPLE = 8;
+using ByteSpan = Span<const uint8_t>;
+
+// TODO(eustas): move to jpeg_data, to use from codec_jpg as well.
+// See if there is a canonically chunked ICC profile and mark corresponding
+// app-tags with AppMarkerType::kICC.
+Status DetectIccProfile(JPEGData& jpeg_data) {
+ JXL_DASSERT(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size());
+ size_t num_icc = 0;
+ size_t num_icc_jpeg = 0;
+ for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
+ const auto& app = jpeg_data.app_data[i];
+ size_t pos = 0;
+ if (app[pos++] != 0xE2) continue;
+ // At least APPn + size; otherwise it should be intermarker-data.
+ JXL_DASSERT(app.size() >= 3);
+ size_t tag_length = (app[pos] << 8) + app[pos + 1];
+ pos += 2;
+ JXL_DASSERT(app.size() == tag_length + 1);
+ // Empty payload is 2 bytes for tag length itself + signature
+ if (tag_length < 2 + sizeof kIccProfileTag) continue;
+
+ if (memcmp(&app[pos], kIccProfileTag, sizeof kIccProfileTag) != 0) continue;
+ pos += sizeof kIccProfileTag;
+ uint8_t chunk_id = app[pos++];
+ uint8_t num_chunks = app[pos++];
+ if (chunk_id != num_icc + 1) continue;
+ if (num_icc_jpeg == 0) num_icc_jpeg = num_chunks;
+ if (num_icc_jpeg != num_chunks) continue;
+ num_icc++;
+ jpeg_data.app_marker_type[i] = AppMarkerType::kICC;
+ }
+ if (num_icc != num_icc_jpeg) {
+ return JXL_FAILURE("Invalid ICC chunks");
+ }
+ return true;
+}
+
+bool GetMarkerPayload(const uint8_t* data, size_t size, ByteSpan* payload) {
+ if (size < 3) {
+ return false;
+ }
+ size_t hi = data[1];
+ size_t lo = data[2];
+ size_t internal_size = (hi << 8u) | lo;
+ // Second byte of marker is not counted towards size.
+ if (internal_size != size - 1) {
+ return false;
+ }
+ // cut second marker byte and "length" from payload.
+ *payload = ByteSpan(data, size);
+ payload->remove_prefix(3);
+ return true;
+}
+
+Status DetectBlobs(jpeg::JPEGData& jpeg_data) {
+ JXL_DASSERT(jpeg_data.app_data.size() == jpeg_data.app_marker_type.size());
+ bool have_exif = false, have_xmp = false;
+ for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
+ auto& marker = jpeg_data.app_data[i];
+ if (marker.empty() || marker[0] != kApp1) {
+ continue;
+ }
+ ByteSpan payload;
+ if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
+ // Something is wrong with this marker; does not care.
+ continue;
+ }
+ if (!have_exif && payload.size() >= sizeof kExifTag &&
+ !memcmp(payload.data(), kExifTag, sizeof kExifTag)) {
+ jpeg_data.app_marker_type[i] = AppMarkerType::kExif;
+ have_exif = true;
+ }
+ if (!have_xmp && payload.size() >= sizeof kXMPTag &&
+ !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) {
+ jpeg_data.app_marker_type[i] = AppMarkerType::kXMP;
+ have_xmp = true;
+ }
+ }
+ return true;
+}
+
+Status ParseChunkedMarker(const jpeg::JPEGData& src, uint8_t marker_type,
+ const ByteSpan& tag, PaddedBytes* output,
+ bool allow_permutations = false) {
+ output->clear();
+
+ std::vector<ByteSpan> chunks;
+ std::vector<bool> presence;
+ size_t expected_number_of_parts = 0;
+ bool is_first_chunk = true;
+ size_t ordinal = 0;
+ for (const auto& marker : src.app_data) {
+ if (marker.empty() || marker[0] != marker_type) {
+ continue;
+ }
+ ByteSpan payload;
+ if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
+ // Something is wrong with this marker; does not care.
+ continue;
+ }
+ if ((payload.size() < tag.size()) ||
+ memcmp(payload.data(), tag.data(), tag.size()) != 0) {
+ continue;
+ }
+ payload.remove_prefix(tag.size());
+ if (payload.size() < 2) {
+ return JXL_FAILURE("Chunk is too small.");
+ }
+ uint8_t index = payload[0];
+ uint8_t total = payload[1];
+ ordinal++;
+ if (!allow_permutations) {
+ if (index != ordinal) return JXL_FAILURE("Invalid chunk order.");
+ }
+
+ payload.remove_prefix(2);
+
+ JXL_RETURN_IF_ERROR(total != 0);
+ if (is_first_chunk) {
+ is_first_chunk = false;
+ expected_number_of_parts = total;
+ // 1-based indices; 0-th element is added for convenience.
+ chunks.resize(total + 1);
+ presence.resize(total + 1);
+ } else {
+ JXL_RETURN_IF_ERROR(expected_number_of_parts == total);
+ }
+
+ if (index == 0 || index > total) {
+ return JXL_FAILURE("Invalid chunk index.");
+ }
+
+ if (presence[index]) {
+ return JXL_FAILURE("Duplicate chunk.");
+ }
+ presence[index] = true;
+ chunks[index] = payload;
+ }
+
+ for (size_t i = 0; i < expected_number_of_parts; ++i) {
+ // 0-th element is not used.
+ size_t index = i + 1;
+ if (!presence[index]) {
+ return JXL_FAILURE("Missing chunk.");
+ }
+ output->append(chunks[index]);
+ }
+
+ return true;
+}
+
+Status SetBlobsFromJpegData(const jpeg::JPEGData& jpeg_data, Blobs* blobs) {
+ for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
+ auto& marker = jpeg_data.app_data[i];
+ if (marker.empty() || marker[0] != kApp1) {
+ continue;
+ }
+ ByteSpan payload;
+ if (!GetMarkerPayload(marker.data(), marker.size(), &payload)) {
+ // Something is wrong with this marker; does not care.
+ continue;
+ }
+ if (payload.size() >= sizeof kExifTag &&
+ !memcmp(payload.data(), kExifTag, sizeof kExifTag)) {
+ if (blobs->exif.empty()) {
+ blobs->exif.resize(payload.size() - sizeof kExifTag);
+ memcpy(blobs->exif.data(), payload.data() + sizeof kExifTag,
+ payload.size() - sizeof kExifTag);
+ } else {
+ JXL_WARNING(
+ "ReJPEG: multiple Exif blobs, storing only first one in the JPEG "
+ "XL container\n");
+ }
+ }
+ if (payload.size() >= sizeof kXMPTag &&
+ !memcmp(payload.data(), kXMPTag, sizeof kXMPTag)) {
+ if (blobs->xmp.empty()) {
+ blobs->xmp.resize(payload.size() - sizeof kXMPTag);
+ memcpy(blobs->xmp.data(), payload.data() + sizeof kXMPTag,
+ payload.size() - sizeof kXMPTag);
+ } else {
+ JXL_WARNING(
+ "ReJPEG: multiple XMP blobs, storing only first one in the JPEG "
+ "XL container\n");
+ }
+ }
+ }
+ return true;
+}
+
+static inline bool IsJPG(const Span<const uint8_t> bytes) {
+ return bytes.size() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xD8;
+}
+
+} // namespace
+
+Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
+ ColorEncoding* color_encoding) {
+ PaddedBytes icc_profile;
+ if (!ParseChunkedMarker(jpg, kApp2, ByteSpan(kIccProfileTag), &icc_profile)) {
+ JXL_WARNING("ReJPEG: corrupted ICC profile\n");
+ icc_profile.clear();
+ }
+
+ if (icc_profile.empty()) {
+ bool is_gray = (jpg.components.size() == 1);
+ *color_encoding = ColorEncoding::SRGB(is_gray);
+ return true;
+ }
+
+ return color_encoding->SetICC(std::move(icc_profile));
+}
+
+Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
+ const CompressParams& cparams) {
+ jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(),
+ AppMarkerType::kUnknown);
+ JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data));
+ JXL_RETURN_IF_ERROR(DetectBlobs(jpeg_data));
+ BitWriter writer;
+ JXL_RETURN_IF_ERROR(Bundle::Write(jpeg_data, &writer, 0, nullptr));
+ writer.ZeroPadToByte();
+ *bytes = std::move(writer).TakeBytes();
+ BrotliEncoderState* brotli_enc =
+ BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
+ int effort = cparams.brotli_effort;
+ if (effort < 0) effort = 11 - static_cast<int>(cparams.speed_tier);
+ BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort);
+ size_t total_data = 0;
+ for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
+ if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) {
+ continue;
+ }
+ total_data += jpeg_data.app_data[i].size();
+ }
+ for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
+ total_data += jpeg_data.com_data[i].size();
+ }
+ for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
+ total_data += jpeg_data.inter_marker_data[i].size();
+ }
+ total_data += jpeg_data.tail_data.size();
+ size_t initial_size = bytes->size();
+ size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data);
+ BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_SIZE_HINT, total_data);
+ bytes->resize(bytes->size() + brotli_capacity);
+ size_t enc_size = 0;
+ auto br_append = [&](const std::vector<uint8_t>& data, bool last) {
+ size_t available_in = data.size();
+ const uint8_t* in = data.data();
+ uint8_t* out = &(*bytes)[initial_size + enc_size];
+ do {
+ uint8_t* out_before = out;
+ msan::MemoryIsInitialized(in, available_in);
+ JXL_CHECK(BrotliEncoderCompressStream(
+ brotli_enc, last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
+ &available_in, &in, &brotli_capacity, &out, &enc_size));
+ msan::UnpoisonMemory(out_before, out - out_before);
+ } while (BrotliEncoderHasMoreOutput(brotli_enc) || available_in > 0);
+ };
+
+ for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
+ if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) {
+ continue;
+ }
+ br_append(jpeg_data.app_data[i], /*last=*/false);
+ }
+ for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
+ br_append(jpeg_data.com_data[i], /*last=*/false);
+ }
+ for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
+ br_append(jpeg_data.inter_marker_data[i], /*last=*/false);
+ }
+ br_append(jpeg_data.tail_data, /*last=*/true);
+ BrotliEncoderDestroyInstance(brotli_enc);
+ bytes->resize(initial_size + enc_size);
+ return true;
+}
+
+Status DecodeImageJPG(const Span<const uint8_t> bytes, CodecInOut* io) {
+ if (!IsJPG(bytes)) return false;
+ io->frames.clear();
+ io->frames.reserve(1);
+ io->frames.emplace_back(&io->metadata.m);
+ io->Main().jpeg_data = make_unique<jpeg::JPEGData>();
+ jpeg::JPEGData* jpeg_data = io->Main().jpeg_data.get();
+ if (!jpeg::ReadJpeg(bytes.data(), bytes.size(), jpeg::JpegReadMode::kReadAll,
+ jpeg_data)) {
+ return JXL_FAILURE("Error reading JPEG");
+ }
+ JXL_RETURN_IF_ERROR(
+ SetColorEncodingFromJpegData(*jpeg_data, &io->metadata.m.color_encoding));
+ JXL_RETURN_IF_ERROR(SetBlobsFromJpegData(*jpeg_data, &io->blobs));
+ size_t nbcomp = jpeg_data->components.size();
+ if (nbcomp != 1 && nbcomp != 3) {
+ return JXL_FAILURE("Cannot recompress JPEGs with neither 1 nor 3 channels");
+ }
+ YCbCrChromaSubsampling cs;
+ if (nbcomp == 3) {
+ uint8_t hsample[3], vsample[3];
+ for (size_t i = 0; i < nbcomp; i++) {
+ hsample[i] = jpeg_data->components[i].h_samp_factor;
+ vsample[i] = jpeg_data->components[i].v_samp_factor;
+ }
+ JXL_RETURN_IF_ERROR(cs.Set(hsample, vsample));
+ } else if (nbcomp == 1) {
+ uint8_t hsample[3], vsample[3];
+ for (size_t i = 0; i < 3; i++) {
+ hsample[i] = jpeg_data->components[0].h_samp_factor;
+ vsample[i] = jpeg_data->components[0].v_samp_factor;
+ }
+ JXL_RETURN_IF_ERROR(cs.Set(hsample, vsample));
+ }
+ bool is_rgb = false;
+ {
+ const auto& markers = jpeg_data->marker_order;
+ // If there is a JFIF marker, this is YCbCr. Otherwise...
+ if (std::find(markers.begin(), markers.end(), 0xE0) == markers.end()) {
+ // Try to find an 'Adobe' marker.
+ size_t app_markers = 0;
+ size_t i = 0;
+ for (; i < markers.size(); i++) {
+ // This is an APP marker.
+ if ((markers[i] & 0xF0) == 0xE0) {
+ JXL_CHECK(app_markers < jpeg_data->app_data.size());
+ // APP14 marker
+ if (markers[i] == 0xEE) {
+ const auto& data = jpeg_data->app_data[app_markers];
+ if (data.size() == 15 && data[3] == 'A' && data[4] == 'd' &&
+ data[5] == 'o' && data[6] == 'b' && data[7] == 'e') {
+ // 'Adobe' marker.
+ is_rgb = data[14] == 0;
+ break;
+ }
+ }
+ app_markers++;
+ }
+ }
+
+ if (i == markers.size()) {
+ // No 'Adobe' marker, guess from component IDs.
+ is_rgb = nbcomp == 3 && jpeg_data->components[0].id == 'R' &&
+ jpeg_data->components[1].id == 'G' &&
+ jpeg_data->components[2].id == 'B';
+ }
+ }
+ }
+
+ io->Main().chroma_subsampling = cs;
+ io->Main().color_transform =
+ (!is_rgb || nbcomp == 1) ? ColorTransform::kYCbCr : ColorTransform::kNone;
+
+ io->metadata.m.SetIntensityTarget(kDefaultIntensityTarget);
+ io->metadata.m.SetUintSamples(BITS_IN_JSAMPLE);
+ io->SetFromImage(Image3F(jpeg_data->width, jpeg_data->height),
+ io->metadata.m.color_encoding);
+ SetIntensityTarget(&io->metadata.m);
+ return true;
+}
+
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h
new file mode 100644
index 0000000000..806128c465
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef LIB_JXL_JPEG_ENC_JPEG_DATA_H_
+#define LIB_JXL_JPEG_ENC_JPEG_DATA_H_
+
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/codec_in_out.h"
+#include "lib/jxl/enc_params.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
+ const CompressParams& cparams);
+
+Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
+ ColorEncoding* color_encoding);
+
+/**
+ * Decodes bytes containing JPEG codestream into a CodecInOut as coefficients
+ * only, for lossless JPEG transcoding.
+ */
+Status DecodeImageJPG(Span<const uint8_t> bytes, CodecInOut* io);
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_ENC_JPEG_DATA_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc
new file mode 100644
index 0000000000..f569b73363
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc
@@ -0,0 +1,1053 @@
+// 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/jxl/jpeg/enc_jpeg_data_reader.h"
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/common.h"
+#include "lib/jxl/jpeg/enc_jpeg_huffman_decode.h"
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+namespace {
+static const int kBrunsliMaxSampling = 15;
+
+// Macros for commonly used error conditions.
+
+#define JXL_JPEG_VERIFY_LEN(n) \
+ if (*pos + (n) > len) { \
+ return JXL_FAILURE("Unexpected end of input: pos=%" PRIuS \
+ " need=%d len=%" PRIuS, \
+ *pos, static_cast<int>(n), len); \
+ }
+
+#define JXL_JPEG_VERIFY_INPUT(var, low, high, code) \
+ if ((var) < (low) || (var) > (high)) { \
+ return JXL_FAILURE("Invalid " #var ": %d", static_cast<int>(var)); \
+ }
+
+#define JXL_JPEG_VERIFY_MARKER_END() \
+ if (start_pos + marker_len != *pos) { \
+ return JXL_FAILURE("Invalid marker length: declared=%" PRIuS \
+ " actual=%" PRIuS, \
+ marker_len, (*pos - start_pos)); \
+ }
+
+#define JXL_JPEG_EXPECT_MARKER() \
+ if (pos + 2 > len || data[pos] != 0xff) { \
+ return JXL_FAILURE( \
+ "Marker byte (0xff) expected, found: 0x%.2x pos=%" PRIuS \
+ " len=%" PRIuS, \
+ (pos < len ? data[pos] : 0), pos, len); \
+ }
+
+inline int ReadUint8(const uint8_t* data, size_t* pos) {
+ return data[(*pos)++];
+}
+
+inline int ReadUint16(const uint8_t* data, size_t* pos) {
+ int v = (data[*pos] << 8) + data[*pos + 1];
+ *pos += 2;
+ return v;
+}
+
+// Reads the Start of Frame (SOF) marker segment and fills in *jpg with the
+// parsed data.
+bool ProcessSOF(const uint8_t* data, const size_t len, JpegReadMode mode,
+ size_t* pos, JPEGData* jpg) {
+ if (jpg->width != 0) {
+ return JXL_FAILURE("Duplicate SOF marker.");
+ }
+ const size_t start_pos = *pos;
+ JXL_JPEG_VERIFY_LEN(8);
+ size_t marker_len = ReadUint16(data, pos);
+ int precision = ReadUint8(data, pos);
+ int height = ReadUint16(data, pos);
+ int width = ReadUint16(data, pos);
+ int num_components = ReadUint8(data, pos);
+ // 'jbrd' is hardcoded for 8bits:
+ JXL_JPEG_VERIFY_INPUT(precision, 8, 8, PRECISION);
+ JXL_JPEG_VERIFY_INPUT(height, 1, kMaxDimPixels, HEIGHT);
+ JXL_JPEG_VERIFY_INPUT(width, 1, kMaxDimPixels, WIDTH);
+ JXL_JPEG_VERIFY_INPUT(num_components, 1, kMaxComponents, NUMCOMP);
+ JXL_JPEG_VERIFY_LEN(3 * num_components);
+ jpg->height = height;
+ jpg->width = width;
+ jpg->components.resize(num_components);
+
+ // Read sampling factors and quant table index for each component.
+ std::vector<bool> ids_seen(256, false);
+ int max_h_samp_factor = 1;
+ int max_v_samp_factor = 1;
+ for (size_t i = 0; i < jpg->components.size(); ++i) {
+ const int id = ReadUint8(data, pos);
+ if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci)
+ return JXL_FAILURE("Duplicate ID %d in SOF.", id);
+ }
+ ids_seen[id] = true;
+ jpg->components[i].id = id;
+ int factor = ReadUint8(data, pos);
+ int h_samp_factor = factor >> 4;
+ int v_samp_factor = factor & 0xf;
+ JXL_JPEG_VERIFY_INPUT(h_samp_factor, 1, kBrunsliMaxSampling, SAMP_FACTOR);
+ JXL_JPEG_VERIFY_INPUT(v_samp_factor, 1, kBrunsliMaxSampling, SAMP_FACTOR);
+ jpg->components[i].h_samp_factor = h_samp_factor;
+ jpg->components[i].v_samp_factor = v_samp_factor;
+ jpg->components[i].quant_idx = ReadUint8(data, pos);
+ max_h_samp_factor = std::max(max_h_samp_factor, h_samp_factor);
+ max_v_samp_factor = std::max(max_v_samp_factor, v_samp_factor);
+ }
+
+ // We have checked above that none of the sampling factors are 0, so the max
+ // sampling factors can not be 0.
+ int MCU_rows = DivCeil(jpg->height, max_v_samp_factor * 8);
+ int MCU_cols = DivCeil(jpg->width, max_h_samp_factor * 8);
+ // Compute the block dimensions for each component.
+ for (size_t i = 0; i < jpg->components.size(); ++i) {
+ JPEGComponent* c = &jpg->components[i];
+ if (max_h_samp_factor % c->h_samp_factor != 0 ||
+ max_v_samp_factor % c->v_samp_factor != 0) {
+ return JXL_FAILURE("Non-integral subsampling ratios.");
+ }
+ c->width_in_blocks = MCU_cols * c->h_samp_factor;
+ c->height_in_blocks = MCU_rows * c->v_samp_factor;
+ const uint64_t num_blocks =
+ static_cast<uint64_t>(c->width_in_blocks) * c->height_in_blocks;
+ if (mode == JpegReadMode::kReadAll) {
+ c->coeffs.resize(num_blocks * kDCTBlockSize);
+ }
+ }
+ JXL_JPEG_VERIFY_MARKER_END();
+ return true;
+}
+
+// Reads the Start of Scan (SOS) marker segment and fills in *scan_info with the
+// parsed data.
+bool ProcessSOS(const uint8_t* data, const size_t len, size_t* pos,
+ JPEGData* jpg) {
+ const size_t start_pos = *pos;
+ JXL_JPEG_VERIFY_LEN(3);
+ size_t marker_len = ReadUint16(data, pos);
+ size_t comps_in_scan = ReadUint8(data, pos);
+ JXL_JPEG_VERIFY_INPUT(comps_in_scan, 1, jpg->components.size(),
+ COMPS_IN_SCAN);
+
+ JPEGScanInfo scan_info;
+ scan_info.num_components = comps_in_scan;
+ JXL_JPEG_VERIFY_LEN(2 * comps_in_scan);
+ std::vector<bool> ids_seen(256, false);
+ for (size_t i = 0; i < comps_in_scan; ++i) {
+ uint32_t id = ReadUint8(data, pos);
+ if (ids_seen[id]) { // (cf. section B.2.3, regarding CSj)
+ return JXL_FAILURE("Duplicate ID %d in SOS.", id);
+ }
+ ids_seen[id] = true;
+ bool found_index = false;
+ for (size_t j = 0; j < jpg->components.size(); ++j) {
+ if (jpg->components[j].id == id) {
+ scan_info.components[i].comp_idx = j;
+ found_index = true;
+ }
+ }
+ if (!found_index) {
+ return JXL_FAILURE("SOS marker: Could not find component with id %d", id);
+ }
+ int c = ReadUint8(data, pos);
+ int dc_tbl_idx = c >> 4;
+ int ac_tbl_idx = c & 0xf;
+ JXL_JPEG_VERIFY_INPUT(dc_tbl_idx, 0, 3, HUFFMAN_INDEX);
+ JXL_JPEG_VERIFY_INPUT(ac_tbl_idx, 0, 3, HUFFMAN_INDEX);
+ scan_info.components[i].dc_tbl_idx = dc_tbl_idx;
+ scan_info.components[i].ac_tbl_idx = ac_tbl_idx;
+ }
+ JXL_JPEG_VERIFY_LEN(3);
+ scan_info.Ss = ReadUint8(data, pos);
+ scan_info.Se = ReadUint8(data, pos);
+ JXL_JPEG_VERIFY_INPUT(static_cast<int>(scan_info.Ss), 0, 63, START_OF_SCAN);
+ JXL_JPEG_VERIFY_INPUT(scan_info.Se, scan_info.Ss, 63, END_OF_SCAN);
+ int c = ReadUint8(data, pos);
+ scan_info.Ah = c >> 4;
+ scan_info.Al = c & 0xf;
+ if (scan_info.Ah != 0 && scan_info.Al != scan_info.Ah - 1) {
+ // section G.1.1.1.2 : Successive approximation control only improves
+ // by one bit at a time. But it's not always respected, so we just issue
+ // a warning.
+ JXL_WARNING("Invalid progressive parameters: Al=%d Ah=%d", scan_info.Al,
+ scan_info.Ah);
+ }
+ // Check that all the Huffman tables needed for this scan are defined.
+ for (size_t i = 0; i < comps_in_scan; ++i) {
+ bool found_dc_table = false;
+ bool found_ac_table = false;
+ for (size_t j = 0; j < jpg->huffman_code.size(); ++j) {
+ uint32_t slot_id = jpg->huffman_code[j].slot_id;
+ if (slot_id == scan_info.components[i].dc_tbl_idx) {
+ found_dc_table = true;
+ } else if (slot_id == scan_info.components[i].ac_tbl_idx + 16) {
+ found_ac_table = true;
+ }
+ }
+ if (scan_info.Ss == 0 && !found_dc_table) {
+ return JXL_FAILURE(
+ "SOS marker: Could not find DC Huffman table with index %d",
+ scan_info.components[i].dc_tbl_idx);
+ }
+ if (scan_info.Se > 0 && !found_ac_table) {
+ return JXL_FAILURE(
+ "SOS marker: Could not find AC Huffman table with index %d",
+ scan_info.components[i].ac_tbl_idx);
+ }
+ }
+ jpg->scan_info.push_back(scan_info);
+ JXL_JPEG_VERIFY_MARKER_END();
+ return true;
+}
+
+// Reads the Define Huffman Table (DHT) marker segment and fills in *jpg with
+// the parsed data. Builds the Huffman decoding table in either dc_huff_lut or
+// ac_huff_lut, depending on the type and solt_id of Huffman code being read.
+bool ProcessDHT(const uint8_t* data, const size_t len, JpegReadMode mode,
+ std::vector<HuffmanTableEntry>* dc_huff_lut,
+ std::vector<HuffmanTableEntry>* ac_huff_lut, size_t* pos,
+ JPEGData* jpg) {
+ const size_t start_pos = *pos;
+ JXL_JPEG_VERIFY_LEN(2);
+ size_t marker_len = ReadUint16(data, pos);
+ if (marker_len == 2) {
+ return JXL_FAILURE("DHT marker: no Huffman table found");
+ }
+ while (*pos < start_pos + marker_len) {
+ JXL_JPEG_VERIFY_LEN(1 + kJpegHuffmanMaxBitLength);
+ JPEGHuffmanCode huff;
+ huff.slot_id = ReadUint8(data, pos);
+ int huffman_index = huff.slot_id;
+ int is_ac_table = (huff.slot_id & 0x10) != 0;
+ HuffmanTableEntry* huff_lut;
+ if (is_ac_table) {
+ huffman_index -= 0x10;
+ JXL_JPEG_VERIFY_INPUT(huffman_index, 0, 3, HUFFMAN_INDEX);
+ huff_lut = &(*ac_huff_lut)[huffman_index * kJpegHuffmanLutSize];
+ } else {
+ JXL_JPEG_VERIFY_INPUT(huffman_index, 0, 3, HUFFMAN_INDEX);
+ huff_lut = &(*dc_huff_lut)[huffman_index * kJpegHuffmanLutSize];
+ }
+ huff.counts[0] = 0;
+ int total_count = 0;
+ int space = 1 << kJpegHuffmanMaxBitLength;
+ int max_depth = 1;
+ for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
+ int count = ReadUint8(data, pos);
+ if (count != 0) {
+ max_depth = i;
+ }
+ huff.counts[i] = count;
+ total_count += count;
+ space -= count * (1 << (kJpegHuffmanMaxBitLength - i));
+ }
+ if (is_ac_table) {
+ JXL_JPEG_VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize,
+ HUFFMAN_CODE);
+ } else {
+ JXL_JPEG_VERIFY_INPUT(total_count, 0, kJpegDCAlphabetSize, HUFFMAN_CODE);
+ }
+ JXL_JPEG_VERIFY_LEN(total_count);
+ std::vector<bool> values_seen(256, false);
+ for (int i = 0; i < total_count; ++i) {
+ int value = ReadUint8(data, pos);
+ if (!is_ac_table) {
+ JXL_JPEG_VERIFY_INPUT(value, 0, kJpegDCAlphabetSize - 1, HUFFMAN_CODE);
+ }
+ if (values_seen[value]) {
+ return JXL_FAILURE("Duplicate Huffman code value %d", value);
+ }
+ values_seen[value] = true;
+ huff.values[i] = value;
+ }
+ // Add an invalid symbol that will have the all 1 code.
+ ++huff.counts[max_depth];
+ huff.values[total_count] = kJpegHuffmanAlphabetSize;
+ space -= (1 << (kJpegHuffmanMaxBitLength - max_depth));
+ if (space < 0) {
+ return JXL_FAILURE("Invalid Huffman code lengths.");
+ } else if (space > 0 && huff_lut[0].value != 0xffff) {
+ // Re-initialize the values to an invalid symbol so that we can recognize
+ // it when reading the bit stream using a Huffman code with space > 0.
+ for (int i = 0; i < kJpegHuffmanLutSize; ++i) {
+ huff_lut[i].bits = 0;
+ huff_lut[i].value = 0xffff;
+ }
+ }
+ huff.is_last = (*pos == start_pos + marker_len);
+ if (mode == JpegReadMode::kReadAll) {
+ BuildJpegHuffmanTable(&huff.counts[0], &huff.values[0], huff_lut);
+ }
+ jpg->huffman_code.push_back(huff);
+ }
+ JXL_JPEG_VERIFY_MARKER_END();
+ return true;
+}
+
+// Reads the Define Quantization Table (DQT) marker segment and fills in *jpg
+// with the parsed data.
+bool ProcessDQT(const uint8_t* data, const size_t len, size_t* pos,
+ JPEGData* jpg) {
+ const size_t start_pos = *pos;
+ JXL_JPEG_VERIFY_LEN(2);
+ size_t marker_len = ReadUint16(data, pos);
+ if (marker_len == 2) {
+ return JXL_FAILURE("DQT marker: no quantization table found");
+ }
+ while (*pos < start_pos + marker_len && jpg->quant.size() < kMaxQuantTables) {
+ JXL_JPEG_VERIFY_LEN(1);
+ int quant_table_index = ReadUint8(data, pos);
+ int quant_table_precision = quant_table_index >> 4;
+ JXL_JPEG_VERIFY_INPUT(quant_table_precision, 0, 1, QUANT_TBL_PRECISION);
+ quant_table_index &= 0xf;
+ JXL_JPEG_VERIFY_INPUT(quant_table_index, 0, 3, QUANT_TBL_INDEX);
+ JXL_JPEG_VERIFY_LEN((quant_table_precision + 1) * kDCTBlockSize);
+ JPEGQuantTable table;
+ table.index = quant_table_index;
+ table.precision = quant_table_precision;
+ for (size_t i = 0; i < kDCTBlockSize; ++i) {
+ int quant_val =
+ quant_table_precision ? ReadUint16(data, pos) : ReadUint8(data, pos);
+ JXL_JPEG_VERIFY_INPUT(quant_val, 1, 65535, QUANT_VAL);
+ table.values[kJPEGNaturalOrder[i]] = quant_val;
+ }
+ table.is_last = (*pos == start_pos + marker_len);
+ jpg->quant.push_back(table);
+ }
+ JXL_JPEG_VERIFY_MARKER_END();
+ return true;
+}
+
+// Reads the DRI marker and saves the restart interval into *jpg.
+bool ProcessDRI(const uint8_t* data, const size_t len, size_t* pos,
+ bool* found_dri, JPEGData* jpg) {
+ if (*found_dri) {
+ return JXL_FAILURE("Duplicate DRI marker.");
+ }
+ *found_dri = true;
+ const size_t start_pos = *pos;
+ JXL_JPEG_VERIFY_LEN(4);
+ size_t marker_len = ReadUint16(data, pos);
+ int restart_interval = ReadUint16(data, pos);
+ jpg->restart_interval = restart_interval;
+ JXL_JPEG_VERIFY_MARKER_END();
+ return true;
+}
+
+// Saves the APP marker segment as a string to *jpg.
+bool ProcessAPP(const uint8_t* data, const size_t len, size_t* pos,
+ JPEGData* jpg) {
+ JXL_JPEG_VERIFY_LEN(2);
+ size_t marker_len = ReadUint16(data, pos);
+ JXL_JPEG_VERIFY_INPUT(marker_len, 2, 65535, MARKER_LEN);
+ JXL_JPEG_VERIFY_LEN(marker_len - 2);
+ JXL_DASSERT(*pos >= 3);
+ // Save the marker type together with the app data.
+ const uint8_t* app_str_start = data + *pos - 3;
+ std::vector<uint8_t> app_str(app_str_start, app_str_start + marker_len + 1);
+ *pos += marker_len - 2;
+ jpg->app_data.push_back(app_str);
+ return true;
+}
+
+// Saves the COM marker segment as a string to *jpg.
+bool ProcessCOM(const uint8_t* data, const size_t len, size_t* pos,
+ JPEGData* jpg) {
+ JXL_JPEG_VERIFY_LEN(2);
+ size_t marker_len = ReadUint16(data, pos);
+ JXL_JPEG_VERIFY_INPUT(marker_len, 2, 65535, MARKER_LEN);
+ JXL_JPEG_VERIFY_LEN(marker_len - 2);
+ const uint8_t* com_str_start = data + *pos - 3;
+ std::vector<uint8_t> com_str(com_str_start, com_str_start + marker_len + 1);
+ *pos += marker_len - 2;
+ jpg->com_data.push_back(com_str);
+ return true;
+}
+
+// Helper structure to read bits from the entropy coded data segment.
+struct BitReaderState {
+ BitReaderState(const uint8_t* data, const size_t len, size_t pos)
+ : data_(data), len_(len) {
+ Reset(pos);
+ }
+
+ void Reset(size_t pos) {
+ pos_ = pos;
+ val_ = 0;
+ bits_left_ = 0;
+ next_marker_pos_ = len_ - 2;
+ FillBitWindow();
+ }
+
+ // Returns the next byte and skips the 0xff/0x00 escape sequences.
+ uint8_t GetNextByte() {
+ if (pos_ >= next_marker_pos_) {
+ ++pos_;
+ return 0;
+ }
+ uint8_t c = data_[pos_++];
+ if (c == 0xff) {
+ uint8_t escape = data_[pos_];
+ if (escape == 0) {
+ ++pos_;
+ } else {
+ // 0xff was followed by a non-zero byte, which means that we found the
+ // start of the next marker segment.
+ next_marker_pos_ = pos_ - 1;
+ }
+ }
+ return c;
+ }
+
+ void FillBitWindow() {
+ if (bits_left_ <= 16) {
+ while (bits_left_ <= 56) {
+ val_ <<= 8;
+ val_ |= (uint64_t)GetNextByte();
+ bits_left_ += 8;
+ }
+ }
+ }
+
+ int ReadBits(int nbits) {
+ FillBitWindow();
+ uint64_t val = (val_ >> (bits_left_ - nbits)) & ((1ULL << nbits) - 1);
+ bits_left_ -= nbits;
+ return val;
+ }
+
+ // Sets *pos to the next stream position where parsing should continue.
+ // Enqueue the padding bits seen (0 or 1).
+ // Returns false if there is inconsistent or invalid padding or the stream
+ // ended too early.
+ bool FinishStream(JPEGData* jpg, size_t* pos) {
+ int npadbits = bits_left_ & 7;
+ if (npadbits > 0) {
+ uint64_t padmask = (1ULL << npadbits) - 1;
+ uint64_t padbits = (val_ >> (bits_left_ - npadbits)) & padmask;
+ if (padbits != padmask) {
+ jpg->has_zero_padding_bit = true;
+ }
+ for (int i = npadbits - 1; i >= 0; --i) {
+ jpg->padding_bits.push_back((padbits >> i) & 1);
+ }
+ }
+ // Give back some bytes that we did not use.
+ int unused_bytes_left = bits_left_ >> 3;
+ while (unused_bytes_left-- > 0) {
+ --pos_;
+ // If we give back a 0 byte, we need to check if it was a 0xff/0x00 escape
+ // sequence, and if yes, we need to give back one more byte.
+ if (pos_ < next_marker_pos_ && data_[pos_] == 0 &&
+ data_[pos_ - 1] == 0xff) {
+ --pos_;
+ }
+ }
+ if (pos_ > next_marker_pos_) {
+ // Data ran out before the scan was complete.
+ return JXL_FAILURE("Unexpected end of scan.");
+ }
+ *pos = pos_;
+ return true;
+ }
+
+ const uint8_t* data_;
+ const size_t len_;
+ size_t pos_;
+ uint64_t val_;
+ int bits_left_;
+ size_t next_marker_pos_;
+};
+
+// Returns the next Huffman-coded symbol.
+int ReadSymbol(const HuffmanTableEntry* table, BitReaderState* br) {
+ int nbits;
+ br->FillBitWindow();
+ int val = (br->val_ >> (br->bits_left_ - 8)) & 0xff;
+ table += val;
+ nbits = table->bits - 8;
+ if (nbits > 0) {
+ br->bits_left_ -= 8;
+ table += table->value;
+ val = (br->val_ >> (br->bits_left_ - nbits)) & ((1 << nbits) - 1);
+ table += val;
+ }
+ br->bits_left_ -= table->bits;
+ return table->value;
+}
+
+/**
+ * Returns the DC diff or AC value for extra bits value x and prefix code s.
+ *
+ * CCITT Rec. T.81 (1992 E)
+ * Table F.1 – Difference magnitude categories for DC coding
+ * SSSS | DIFF values
+ * ------+--------------------------
+ * 0 | 0
+ * 1 | –1, 1
+ * 2 | –3, –2, 2, 3
+ * 3 | –7..–4, 4..7
+ * ......|..........................
+ * 11 | –2047..–1024, 1024..2047
+ *
+ * CCITT Rec. T.81 (1992 E)
+ * Table F.2 – Categories assigned to coefficient values
+ * [ Same as Table F.1, but does not include SSSS equal to 0 and 11]
+ *
+ *
+ * CCITT Rec. T.81 (1992 E)
+ * F.1.2.1.1 Structure of DC code table
+ * For each category,... additional bits... appended... to uniquely identify
+ * which difference... occurred... When DIFF is positive... SSSS... bits of DIFF
+ * are appended. When DIFF is negative... SSSS... bits of (DIFF – 1) are
+ * appended... Most significant bit... is 0 for negative differences and 1 for
+ * positive differences.
+ *
+ * In other words the upper half of extra bits range represents DIFF as is.
+ * The lower half represents the negative DIFFs with an offset.
+ */
+int HuffExtend(int x, int s) {
+ JXL_DASSERT(s >= 1);
+ int half = 1 << (s - 1);
+ if (x >= half) {
+ JXL_DASSERT(x < (1 << s));
+ return x;
+ } else {
+ return x - (1 << s) + 1;
+ }
+}
+
+// Decodes one 8x8 block of DCT coefficients from the bit stream.
+bool DecodeDCTBlock(const HuffmanTableEntry* dc_huff,
+ const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al,
+ int* eobrun, bool* reset_state, int* num_zero_runs,
+ BitReaderState* br, JPEGData* jpg, coeff_t* last_dc_coeff,
+ coeff_t* coeffs) {
+ // Nowadays multiplication is even faster than variable shift.
+ int Am = 1 << Al;
+ bool eobrun_allowed = Ss > 0;
+ if (Ss == 0) {
+ int s = ReadSymbol(dc_huff, br);
+ if (s >= kJpegDCAlphabetSize) {
+ return JXL_FAILURE("Invalid Huffman symbol %d for DC coefficient.", s);
+ }
+ int diff = 0;
+ if (s > 0) {
+ int bits = br->ReadBits(s);
+ diff = HuffExtend(bits, s);
+ }
+ int coeff = diff + *last_dc_coeff;
+ const int dc_coeff = coeff * Am;
+ coeffs[0] = dc_coeff;
+ // TODO(eustas): is there a more elegant / explicit way to check this?
+ if (dc_coeff != coeffs[0]) {
+ return JXL_FAILURE("Invalid DC coefficient %d", dc_coeff);
+ }
+ *last_dc_coeff = coeff;
+ ++Ss;
+ }
+ if (Ss > Se) {
+ return true;
+ }
+ if (*eobrun > 0) {
+ --(*eobrun);
+ return true;
+ }
+ *num_zero_runs = 0;
+ for (int k = Ss; k <= Se; k++) {
+ int sr = ReadSymbol(ac_huff, br);
+ if (sr >= kJpegHuffmanAlphabetSize) {
+ return JXL_FAILURE("Invalid Huffman symbol %d for AC coefficient %d", sr,
+ k);
+ }
+ int r = sr >> 4;
+ int s = sr & 15;
+ if (s > 0) {
+ k += r;
+ if (k > Se) {
+ return JXL_FAILURE("Out-of-band coefficient %d band was %d-%d", k, Ss,
+ Se);
+ }
+ if (s + Al >= kJpegDCAlphabetSize) {
+ return JXL_FAILURE(
+ "Out of range AC coefficient value: s = %d Al = %d k = %d", s, Al,
+ k);
+ }
+ int bits = br->ReadBits(s);
+ int coeff = HuffExtend(bits, s);
+ coeffs[kJPEGNaturalOrder[k]] = coeff * Am;
+ *num_zero_runs = 0;
+ } else if (r == 15) {
+ k += 15;
+ ++(*num_zero_runs);
+ } else {
+ if (eobrun_allowed && k == Ss && *eobrun == 0) {
+ // We have two end-of-block runs right after each other, so we signal
+ // the jpeg encoder to force a state reset at this point.
+ *reset_state = true;
+ }
+ *eobrun = 1 << r;
+ if (r > 0) {
+ if (!eobrun_allowed) {
+ return JXL_FAILURE("End-of-block run crossing DC coeff.");
+ }
+ *eobrun += br->ReadBits(r);
+ }
+ break;
+ }
+ }
+ --(*eobrun);
+ return true;
+}
+
+bool RefineDCTBlock(const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al,
+ int* eobrun, bool* reset_state, BitReaderState* br,
+ JPEGData* jpg, coeff_t* coeffs) {
+ // Nowadays multiplication is even faster than variable shift.
+ int Am = 1 << Al;
+ bool eobrun_allowed = Ss > 0;
+ if (Ss == 0) {
+ int s = br->ReadBits(1);
+ coeff_t dc_coeff = coeffs[0];
+ dc_coeff |= s * Am;
+ coeffs[0] = dc_coeff;
+ ++Ss;
+ }
+ if (Ss > Se) {
+ return true;
+ }
+ int p1 = Am;
+ int m1 = -Am;
+ int k = Ss;
+ int r;
+ int s;
+ bool in_zero_run = false;
+ if (*eobrun <= 0) {
+ for (; k <= Se; k++) {
+ s = ReadSymbol(ac_huff, br);
+ if (s >= kJpegHuffmanAlphabetSize) {
+ return JXL_FAILURE("Invalid Huffman symbol %d for AC coefficient %d", s,
+ k);
+ }
+ r = s >> 4;
+ s &= 15;
+ if (s) {
+ if (s != 1) {
+ return JXL_FAILURE("Invalid Huffman symbol %d for AC coefficient %d",
+ s, k);
+ }
+ s = br->ReadBits(1) ? p1 : m1;
+ in_zero_run = false;
+ } else {
+ if (r != 15) {
+ if (eobrun_allowed && k == Ss && *eobrun == 0) {
+ // We have two end-of-block runs right after each other, so we
+ // signal the jpeg encoder to force a state reset at this point.
+ *reset_state = true;
+ }
+ *eobrun = 1 << r;
+ if (r > 0) {
+ if (!eobrun_allowed) {
+ return JXL_FAILURE("End-of-block run crossing DC coeff.");
+ }
+ *eobrun += br->ReadBits(r);
+ }
+ break;
+ }
+ in_zero_run = true;
+ }
+ do {
+ coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]];
+ if (thiscoef != 0) {
+ if (br->ReadBits(1)) {
+ if ((thiscoef & p1) == 0) {
+ if (thiscoef >= 0) {
+ thiscoef += p1;
+ } else {
+ thiscoef += m1;
+ }
+ }
+ }
+ coeffs[kJPEGNaturalOrder[k]] = thiscoef;
+ } else {
+ if (--r < 0) {
+ break;
+ }
+ }
+ k++;
+ } while (k <= Se);
+ if (s) {
+ if (k > Se) {
+ return JXL_FAILURE("Out-of-band coefficient %d band was %d-%d", k, Ss,
+ Se);
+ }
+ coeffs[kJPEGNaturalOrder[k]] = s;
+ }
+ }
+ }
+ if (in_zero_run) {
+ return JXL_FAILURE("Extra zero run before end-of-block.");
+ }
+ if (*eobrun > 0) {
+ for (; k <= Se; k++) {
+ coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]];
+ if (thiscoef != 0) {
+ if (br->ReadBits(1)) {
+ if ((thiscoef & p1) == 0) {
+ if (thiscoef >= 0) {
+ thiscoef += p1;
+ } else {
+ thiscoef += m1;
+ }
+ }
+ }
+ coeffs[kJPEGNaturalOrder[k]] = thiscoef;
+ }
+ }
+ }
+ --(*eobrun);
+ return true;
+}
+
+bool ProcessRestart(const uint8_t* data, const size_t len,
+ int* next_restart_marker, BitReaderState* br,
+ JPEGData* jpg) {
+ size_t pos = 0;
+ if (!br->FinishStream(jpg, &pos)) {
+ return JXL_FAILURE("Invalid scan");
+ }
+ int expected_marker = 0xd0 + *next_restart_marker;
+ JXL_JPEG_EXPECT_MARKER();
+ int marker = data[pos + 1];
+ if (marker != expected_marker) {
+ return JXL_FAILURE("Did not find expected restart marker %d actual %d",
+ expected_marker, marker);
+ }
+ br->Reset(pos + 2);
+ *next_restart_marker += 1;
+ *next_restart_marker &= 0x7;
+ return true;
+}
+
+bool ProcessScan(const uint8_t* data, const size_t len,
+ const std::vector<HuffmanTableEntry>& dc_huff_lut,
+ const std::vector<HuffmanTableEntry>& ac_huff_lut,
+ uint16_t scan_progression[kMaxComponents][kDCTBlockSize],
+ bool is_progressive, size_t* pos, JPEGData* jpg) {
+ if (!ProcessSOS(data, len, pos, jpg)) {
+ return false;
+ }
+ JPEGScanInfo* scan_info = &jpg->scan_info.back();
+ bool is_interleaved = (scan_info->num_components > 1);
+ int max_h_samp_factor = 1;
+ int max_v_samp_factor = 1;
+ for (size_t i = 0; i < jpg->components.size(); ++i) {
+ max_h_samp_factor =
+ std::max(max_h_samp_factor, jpg->components[i].h_samp_factor);
+ max_v_samp_factor =
+ std::max(max_v_samp_factor, jpg->components[i].v_samp_factor);
+ }
+
+ int MCU_rows = DivCeil(jpg->height, max_v_samp_factor * 8);
+ int MCUs_per_row = DivCeil(jpg->width, max_h_samp_factor * 8);
+ if (!is_interleaved) {
+ const JPEGComponent& c = jpg->components[scan_info->components[0].comp_idx];
+ MCUs_per_row = DivCeil(jpg->width * c.h_samp_factor, 8 * max_h_samp_factor);
+ MCU_rows = DivCeil(jpg->height * c.v_samp_factor, 8 * max_v_samp_factor);
+ }
+ coeff_t last_dc_coeff[kMaxComponents] = {0};
+ BitReaderState br(data, len, *pos);
+ int restarts_to_go = jpg->restart_interval;
+ int next_restart_marker = 0;
+ int eobrun = -1;
+ int block_scan_index = 0;
+ const int Al = is_progressive ? scan_info->Al : 0;
+ const int Ah = is_progressive ? scan_info->Ah : 0;
+ const int Ss = is_progressive ? scan_info->Ss : 0;
+ const int Se = is_progressive ? scan_info->Se : 63;
+ const uint16_t scan_bitmask = Ah == 0 ? (0xffff << Al) : (1u << Al);
+ const uint16_t refinement_bitmask = (1 << Al) - 1;
+ for (size_t i = 0; i < scan_info->num_components; ++i) {
+ int comp_idx = scan_info->components[i].comp_idx;
+ for (int k = Ss; k <= Se; ++k) {
+ if (scan_progression[comp_idx][k] & scan_bitmask) {
+ return JXL_FAILURE(
+ "Overlapping scans: component=%d k=%d prev_mask: %u cur_mask %u",
+ comp_idx, k, scan_progression[i][k], scan_bitmask);
+ }
+ if (scan_progression[comp_idx][k] & refinement_bitmask) {
+ return JXL_FAILURE(
+ "Invalid scan order, a more refined scan was already done: "
+ "component=%d k=%d prev_mask=%u cur_mask=%u",
+ comp_idx, k, scan_progression[i][k], scan_bitmask);
+ }
+ scan_progression[comp_idx][k] |= scan_bitmask;
+ }
+ }
+ if (Al > 10) {
+ return JXL_FAILURE("Scan parameter Al=%d is not supported.", Al);
+ }
+ for (int mcu_y = 0; mcu_y < MCU_rows; ++mcu_y) {
+ for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) {
+ // Handle the restart intervals.
+ if (jpg->restart_interval > 0) {
+ if (restarts_to_go == 0) {
+ if (ProcessRestart(data, len, &next_restart_marker, &br, jpg)) {
+ restarts_to_go = jpg->restart_interval;
+ memset(static_cast<void*>(last_dc_coeff), 0, sizeof(last_dc_coeff));
+ if (eobrun > 0) {
+ return JXL_FAILURE("End-of-block run too long.");
+ }
+ eobrun = -1; // fresh start
+ } else {
+ return JXL_FAILURE("Could not process restart.");
+ }
+ }
+ --restarts_to_go;
+ }
+ // Decode one MCU.
+ for (size_t i = 0; i < scan_info->num_components; ++i) {
+ JPEGComponentScanInfo* si = &scan_info->components[i];
+ JPEGComponent* c = &jpg->components[si->comp_idx];
+ const HuffmanTableEntry* dc_lut =
+ &dc_huff_lut[si->dc_tbl_idx * kJpegHuffmanLutSize];
+ const HuffmanTableEntry* ac_lut =
+ &ac_huff_lut[si->ac_tbl_idx * kJpegHuffmanLutSize];
+ int nblocks_y = is_interleaved ? c->v_samp_factor : 1;
+ int nblocks_x = is_interleaved ? c->h_samp_factor : 1;
+ for (int iy = 0; iy < nblocks_y; ++iy) {
+ for (int ix = 0; ix < nblocks_x; ++ix) {
+ int block_y = mcu_y * nblocks_y + iy;
+ int block_x = mcu_x * nblocks_x + ix;
+ int block_idx = block_y * c->width_in_blocks + block_x;
+ bool reset_state = false;
+ int num_zero_runs = 0;
+ coeff_t* coeffs = &c->coeffs[block_idx * kDCTBlockSize];
+ if (Ah == 0) {
+ if (!DecodeDCTBlock(dc_lut, ac_lut, Ss, Se, Al, &eobrun,
+ &reset_state, &num_zero_runs, &br, jpg,
+ &last_dc_coeff[si->comp_idx], coeffs)) {
+ return false;
+ }
+ } else {
+ if (!RefineDCTBlock(ac_lut, Ss, Se, Al, &eobrun, &reset_state,
+ &br, jpg, coeffs)) {
+ return false;
+ }
+ }
+ if (reset_state) {
+ scan_info->reset_points.emplace_back(block_scan_index);
+ }
+ if (num_zero_runs > 0) {
+ JPEGScanInfo::ExtraZeroRunInfo info;
+ info.block_idx = block_scan_index;
+ info.num_extra_zero_runs = num_zero_runs;
+ scan_info->extra_zero_runs.push_back(info);
+ }
+ ++block_scan_index;
+ }
+ }
+ }
+ }
+ }
+ if (eobrun > 0) {
+ return JXL_FAILURE("End-of-block run too long.");
+ }
+ if (!br.FinishStream(jpg, pos)) {
+ return JXL_FAILURE("Invalid scan.");
+ }
+ if (*pos > len) {
+ return JXL_FAILURE("Unexpected end of file during scan. pos=%" PRIuS
+ " len=%" PRIuS,
+ *pos, len);
+ }
+ return true;
+}
+
+// Changes the quant_idx field of the components to refer to the index of the
+// quant table in the jpg->quant array.
+bool FixupIndexes(JPEGData* jpg) {
+ for (size_t i = 0; i < jpg->components.size(); ++i) {
+ JPEGComponent* c = &jpg->components[i];
+ bool found_index = false;
+ for (size_t j = 0; j < jpg->quant.size(); ++j) {
+ if (jpg->quant[j].index == c->quant_idx) {
+ c->quant_idx = j;
+ found_index = true;
+ break;
+ }
+ }
+ if (!found_index) {
+ return JXL_FAILURE("Quantization table with index %u not found",
+ c->quant_idx);
+ }
+ }
+ return true;
+}
+
+size_t FindNextMarker(const uint8_t* data, const size_t len, size_t pos) {
+ // kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker.
+ static const uint8_t kIsValidMarker[] = {
+ 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ };
+ size_t num_skipped = 0;
+ while (pos + 1 < len && (data[pos] != 0xff || data[pos + 1] < 0xc0 ||
+ !kIsValidMarker[data[pos + 1] - 0xc0])) {
+ ++pos;
+ ++num_skipped;
+ }
+ return num_skipped;
+}
+
+} // namespace
+
+bool ReadJpeg(const uint8_t* data, const size_t len, JpegReadMode mode,
+ JPEGData* jpg) {
+ size_t pos = 0;
+ // Check SOI marker.
+ JXL_JPEG_EXPECT_MARKER();
+ int marker = data[pos + 1];
+ pos += 2;
+ if (marker != 0xd8) {
+ return JXL_FAILURE("Did not find expected SOI marker, actual=%d", marker);
+ }
+ int lut_size = kMaxHuffmanTables * kJpegHuffmanLutSize;
+ std::vector<HuffmanTableEntry> dc_huff_lut(lut_size);
+ std::vector<HuffmanTableEntry> ac_huff_lut(lut_size);
+ bool found_sof = false;
+ bool found_dri = false;
+ uint16_t scan_progression[kMaxComponents][kDCTBlockSize] = {{0}};
+
+ jpg->padding_bits.resize(0);
+ bool is_progressive = false; // default
+ do {
+ // Read next marker.
+ size_t num_skipped = FindNextMarker(data, len, pos);
+ if (num_skipped > 0) {
+ // Add a fake marker to indicate arbitrary in-between-markers data.
+ jpg->marker_order.push_back(0xff);
+ jpg->inter_marker_data.emplace_back(data + pos, data + pos + num_skipped);
+ pos += num_skipped;
+ }
+ JXL_JPEG_EXPECT_MARKER();
+ marker = data[pos + 1];
+ pos += 2;
+ bool ok = true;
+ switch (marker) {
+ case 0xc0:
+ case 0xc1:
+ case 0xc2:
+ is_progressive = (marker == 0xc2);
+ ok = ProcessSOF(data, len, mode, &pos, jpg);
+ found_sof = true;
+ break;
+ case 0xc4:
+ ok = ProcessDHT(data, len, mode, &dc_huff_lut, &ac_huff_lut, &pos, jpg);
+ break;
+ case 0xd0:
+ case 0xd1:
+ case 0xd2:
+ case 0xd3:
+ case 0xd4:
+ case 0xd5:
+ case 0xd6:
+ case 0xd7:
+ // RST markers do not have any data.
+ break;
+ case 0xd9:
+ // Found end marker.
+ break;
+ case 0xda:
+ if (mode == JpegReadMode::kReadAll) {
+ ok = ProcessScan(data, len, dc_huff_lut, ac_huff_lut,
+ scan_progression, is_progressive, &pos, jpg);
+ }
+ break;
+ case 0xdb:
+ ok = ProcessDQT(data, len, &pos, jpg);
+ break;
+ case 0xdd:
+ ok = ProcessDRI(data, len, &pos, &found_dri, jpg);
+ break;
+ case 0xe0:
+ case 0xe1:
+ case 0xe2:
+ case 0xe3:
+ case 0xe4:
+ case 0xe5:
+ case 0xe6:
+ case 0xe7:
+ case 0xe8:
+ case 0xe9:
+ case 0xea:
+ case 0xeb:
+ case 0xec:
+ case 0xed:
+ case 0xee:
+ case 0xef:
+ if (mode != JpegReadMode::kReadTables) {
+ ok = ProcessAPP(data, len, &pos, jpg);
+ }
+ break;
+ case 0xfe:
+ if (mode != JpegReadMode::kReadTables) {
+ ok = ProcessCOM(data, len, &pos, jpg);
+ }
+ break;
+ default:
+ return JXL_FAILURE("Unsupported marker: %d pos=%" PRIuS " len=%" PRIuS,
+ marker, pos, len);
+ }
+ if (!ok) {
+ return false;
+ }
+ jpg->marker_order.push_back(marker);
+ if (mode == JpegReadMode::kReadHeader && found_sof) {
+ break;
+ }
+ } while (marker != 0xd9);
+
+ if (!found_sof) {
+ return JXL_FAILURE("Missing SOF marker.");
+ }
+
+ // Supplemental checks.
+ if (mode == JpegReadMode::kReadAll) {
+ if (pos < len) {
+ jpg->tail_data = std::vector<uint8_t>(data + pos, data + len);
+ }
+ if (!FixupIndexes(jpg)) {
+ return false;
+ }
+ if (jpg->huffman_code.empty()) {
+ // Section B.2.4.2: "If a table has never been defined for a particular
+ // destination, then when this destination is specified in a scan header,
+ // the results are unpredictable."
+ return JXL_FAILURE("Need at least one Huffman code table.");
+ }
+ if (jpg->huffman_code.size() >= kMaxDHTMarkers) {
+ return JXL_FAILURE("Too many Huffman tables.");
+ }
+ }
+ return true;
+}
+
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.h b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.h
new file mode 100644
index 0000000000..3fad820e9d
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.h
@@ -0,0 +1,36 @@
+// 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.
+
+// Functions for reading a jpeg byte stream into a JPEGData object.
+
+#ifndef LIB_JXL_JPEG_ENC_JPEG_DATA_READER_H_
+#define LIB_JXL_JPEG_ENC_JPEG_DATA_READER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+enum class JpegReadMode {
+ kReadHeader, // only basic headers
+ kReadTables, // headers and tables (quant, Huffman, ...)
+ kReadAll, // everything
+};
+
+// Parses the JPEG stream contained in data[*pos ... len) and fills in *jpg with
+// the parsed information.
+// If mode is kReadHeader, it fills in only the image dimensions in *jpg.
+// Returns false if the data is not valid JPEG, or if it contains an unsupported
+// JPEG feature.
+bool ReadJpeg(const uint8_t* data, const size_t len, JpegReadMode mode,
+ JPEGData* jpg);
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_ENC_JPEG_DATA_READER_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.cc
new file mode 100644
index 0000000000..38282e640a
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.cc
@@ -0,0 +1,103 @@
+// 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/jxl/jpeg/enc_jpeg_huffman_decode.h"
+
+#include "lib/jxl/jpeg/jpeg_data.h"
+
+namespace jxl {
+namespace jpeg {
+
+// Returns the table width of the next 2nd level table, count is the histogram
+// of bit lengths for the remaining symbols, len is the code length of the next
+// processed symbol.
+static inline int NextTableBitSize(const int* count, int len) {
+ int left = 1 << (len - kJpegHuffmanRootTableBits);
+ while (len < static_cast<int>(kJpegHuffmanMaxBitLength)) {
+ left -= count[len];
+ if (left <= 0) break;
+ ++len;
+ left <<= 1;
+ }
+ return len - kJpegHuffmanRootTableBits;
+}
+
+void BuildJpegHuffmanTable(const uint32_t* count, const uint32_t* symbols,
+ HuffmanTableEntry* lut) {
+ HuffmanTableEntry code; // current table entry
+ HuffmanTableEntry* table; // next available space in table
+ int len; // current code length
+ int idx; // symbol index
+ int key; // prefix code
+ int reps; // number of replicate key values in current table
+ int low; // low bits for current root entry
+ int table_bits; // key length of current table
+ int table_size; // size of current table
+
+ // Make a local copy of the input bit length histogram.
+ int tmp_count[kJpegHuffmanMaxBitLength + 1] = {0};
+ int total_count = 0;
+ for (len = 1; len <= static_cast<int>(kJpegHuffmanMaxBitLength); ++len) {
+ tmp_count[len] = count[len];
+ total_count += tmp_count[len];
+ }
+
+ table = lut;
+ table_bits = kJpegHuffmanRootTableBits;
+ table_size = 1 << table_bits;
+
+ // Special case code with only one value.
+ if (total_count == 1) {
+ code.bits = 0;
+ code.value = symbols[0];
+ for (key = 0; key < table_size; ++key) {
+ table[key] = code;
+ }
+ return;
+ }
+
+ // Fill in root table.
+ key = 0;
+ idx = 0;
+ for (len = 1; len <= kJpegHuffmanRootTableBits; ++len) {
+ for (; tmp_count[len] > 0; --tmp_count[len]) {
+ code.bits = len;
+ code.value = symbols[idx++];
+ reps = 1 << (kJpegHuffmanRootTableBits - len);
+ while (reps--) {
+ table[key++] = code;
+ }
+ }
+ }
+
+ // Fill in 2nd level tables and add pointers to root table.
+ table += table_size;
+ table_size = 0;
+ low = 0;
+ for (len = kJpegHuffmanRootTableBits + 1;
+ len <= static_cast<int>(kJpegHuffmanMaxBitLength); ++len) {
+ for (; tmp_count[len] > 0; --tmp_count[len]) {
+ // Start a new sub-table if the previous one is full.
+ if (low >= table_size) {
+ table += table_size;
+ table_bits = NextTableBitSize(tmp_count, len);
+ table_size = 1 << table_bits;
+ low = 0;
+ lut[key].bits = table_bits + kJpegHuffmanRootTableBits;
+ lut[key].value = (table - lut) - key;
+ ++key;
+ }
+ code.bits = len - kJpegHuffmanRootTableBits;
+ code.value = symbols[idx++];
+ reps = 1 << (table_bits - code.bits);
+ while (reps--) {
+ table[low++] = code;
+ }
+ }
+ }
+}
+
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.h b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.h
new file mode 100644
index 0000000000..b8a60e4107
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_huffman_decode.h
@@ -0,0 +1,41 @@
+// 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.
+
+// Utility function for building a Huffman lookup table for the jpeg decoder.
+
+#ifndef LIB_JXL_JPEG_ENC_JPEG_HUFFMAN_DECODE_H_
+#define LIB_JXL_JPEG_ENC_JPEG_HUFFMAN_DECODE_H_
+
+#include <stdint.h>
+
+namespace jxl {
+namespace jpeg {
+
+constexpr int kJpegHuffmanRootTableBits = 8;
+// Maximum huffman lookup table size.
+// According to zlib/examples/enough.c, 758 entries are always enough for
+// an alphabet of 257 symbols (256 + 1 special symbol for the all 1s code) and
+// max bit length 16 if the root table has 8 bits.
+constexpr int kJpegHuffmanLutSize = 758;
+
+struct HuffmanTableEntry {
+ // Initialize the value to an invalid symbol so that we can recognize it
+ // when reading the bit stream using a Huffman code with space > 0.
+ HuffmanTableEntry() : bits(0), value(0xffff) {}
+
+ uint8_t bits; // number of bits used for this symbol
+ uint16_t value; // symbol value or table offset
+};
+
+// Builds jpeg-style Huffman lookup table from the given symbols.
+// The symbols are in order of increasing bit lengths. The number of symbols
+// with bit length n is given in counts[n] for each n >= 1.
+void BuildJpegHuffmanTable(const uint32_t* counts, const uint32_t* symbols,
+ HuffmanTableEntry* lut);
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_ENC_JPEG_HUFFMAN_DECODE_H_
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc
new file mode 100644
index 0000000000..430707b9ed
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc
@@ -0,0 +1,451 @@
+// 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/jxl/jpeg/jpeg_data.h"
+
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/base/status.h"
+
+namespace jxl {
+namespace jpeg {
+
+#if JPEGXL_ENABLE_TRANSCODE_JPEG
+
+namespace {
+enum JPEGComponentType : uint32_t {
+ kGray = 0,
+ kYCbCr = 1,
+ kRGB = 2,
+ kCustom = 3,
+};
+
+struct JPEGInfo {
+ size_t num_app_markers = 0;
+ size_t num_com_markers = 0;
+ size_t num_scans = 0;
+ size_t num_intermarker = 0;
+ bool has_dri = false;
+};
+
+Status VisitMarker(uint8_t* marker, Visitor* visitor, JPEGInfo* info) {
+ uint32_t marker32 = *marker - 0xc0;
+ JXL_RETURN_IF_ERROR(visitor->Bits(6, 0x00, &marker32));
+ *marker = marker32 + 0xc0;
+ if ((*marker & 0xf0) == 0xe0) {
+ info->num_app_markers++;
+ }
+ if (*marker == 0xfe) {
+ info->num_com_markers++;
+ }
+ if (*marker == 0xda) {
+ info->num_scans++;
+ }
+ // We use a fake 0xff marker to signal intermarker data.
+ if (*marker == 0xff) {
+ info->num_intermarker++;
+ }
+ if (*marker == 0xdd) {
+ info->has_dri = true;
+ }
+ return true;
+}
+
+} // namespace
+
+Status JPEGData::VisitFields(Visitor* visitor) {
+ bool is_gray = components.size() == 1;
+ JXL_RETURN_IF_ERROR(visitor->Bool(false, &is_gray));
+ if (visitor->IsReading()) {
+ components.resize(is_gray ? 1 : 3);
+ }
+ JPEGInfo info;
+ if (visitor->IsReading()) {
+ uint8_t marker = 0xc0;
+ do {
+ JXL_RETURN_IF_ERROR(VisitMarker(&marker, visitor, &info));
+ marker_order.push_back(marker);
+ if (marker_order.size() > 16384) {
+ return JXL_FAILURE("Too many markers: %" PRIuS "\n",
+ marker_order.size());
+ }
+ } while (marker != 0xd9);
+ } else {
+ if (marker_order.size() > 16384) {
+ return JXL_FAILURE("Too many markers: %" PRIuS "\n", marker_order.size());
+ }
+ for (size_t i = 0; i < marker_order.size(); i++) {
+ JXL_RETURN_IF_ERROR(VisitMarker(&marker_order[i], visitor, &info));
+ }
+ if (!marker_order.empty()) {
+ // Last marker should always be EOI marker.
+ JXL_CHECK(marker_order.back() == 0xd9);
+ }
+ }
+
+ // Size of the APP and COM markers.
+ if (visitor->IsReading()) {
+ app_data.resize(info.num_app_markers);
+ app_marker_type.resize(info.num_app_markers);
+ com_data.resize(info.num_com_markers);
+ scan_info.resize(info.num_scans);
+ }
+ JXL_ASSERT(app_data.size() == info.num_app_markers);
+ JXL_ASSERT(app_marker_type.size() == info.num_app_markers);
+ JXL_ASSERT(com_data.size() == info.num_com_markers);
+ JXL_ASSERT(scan_info.size() == info.num_scans);
+ for (size_t i = 0; i < app_data.size(); i++) {
+ auto& app = app_data[i];
+ // Encodes up to 8 different values.
+ JXL_RETURN_IF_ERROR(
+ visitor->U32(Val(0), Val(1), BitsOffset(1, 2), BitsOffset(2, 4), 0,
+ reinterpret_cast<uint32_t*>(&app_marker_type[i])));
+ if (app_marker_type[i] != AppMarkerType::kUnknown &&
+ app_marker_type[i] != AppMarkerType::kICC &&
+ app_marker_type[i] != AppMarkerType::kExif &&
+ app_marker_type[i] != AppMarkerType::kXMP) {
+ return JXL_FAILURE("Unknown app marker type %u",
+ static_cast<uint32_t>(app_marker_type[i]));
+ }
+ uint32_t len = app.size() - 1;
+ JXL_RETURN_IF_ERROR(visitor->Bits(16, 0, &len));
+ if (visitor->IsReading()) app.resize(len + 1);
+ if (app.size() < 3) {
+ return JXL_FAILURE("Invalid marker size: %" PRIuS "\n", app.size());
+ }
+ }
+ for (auto& com : com_data) {
+ uint32_t len = com.size() - 1;
+ JXL_RETURN_IF_ERROR(visitor->Bits(16, 0, &len));
+ if (visitor->IsReading()) com.resize(len + 1);
+ if (com.size() < 3) {
+ return JXL_FAILURE("Invalid marker size: %" PRIuS "\n", com.size());
+ }
+ }
+
+ uint32_t num_quant_tables = quant.size();
+ JXL_RETURN_IF_ERROR(
+ visitor->U32(Val(1), Val(2), Val(3), Val(4), 2, &num_quant_tables));
+ if (num_quant_tables == 4) {
+ return JXL_FAILURE("Invalid number of quant tables");
+ }
+ if (visitor->IsReading()) {
+ quant.resize(num_quant_tables);
+ }
+ for (size_t i = 0; i < num_quant_tables; i++) {
+ if (quant[i].precision > 1) {
+ return JXL_FAILURE(
+ "Quant tables with more than 16 bits are not supported");
+ }
+ JXL_RETURN_IF_ERROR(visitor->Bits(1, 0, &quant[i].precision));
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, i, &quant[i].index));
+ JXL_RETURN_IF_ERROR(visitor->Bool(true, &quant[i].is_last));
+ }
+
+ JPEGComponentType component_type =
+ components.size() == 1 && components[0].id == 1 ? JPEGComponentType::kGray
+ : components.size() == 3 && components[0].id == 1 &&
+ components[1].id == 2 && components[2].id == 3
+ ? JPEGComponentType::kYCbCr
+ : components.size() == 3 && components[0].id == 'R' &&
+ components[1].id == 'G' && components[2].id == 'B'
+ ? JPEGComponentType::kRGB
+ : JPEGComponentType::kCustom;
+ JXL_RETURN_IF_ERROR(
+ visitor->Bits(2, JPEGComponentType::kYCbCr,
+ reinterpret_cast<uint32_t*>(&component_type)));
+ uint32_t num_components;
+ if (component_type == JPEGComponentType::kGray) {
+ num_components = 1;
+ } else if (component_type != JPEGComponentType::kCustom) {
+ num_components = 3;
+ } else {
+ num_components = components.size();
+ JXL_RETURN_IF_ERROR(
+ visitor->U32(Val(1), Val(2), Val(3), Val(4), 3, &num_components));
+ if (num_components != 1 && num_components != 3) {
+ return JXL_FAILURE("Invalid number of components: %u", num_components);
+ }
+ }
+ if (visitor->IsReading()) {
+ components.resize(num_components);
+ }
+ if (component_type == JPEGComponentType::kCustom) {
+ for (size_t i = 0; i < components.size(); i++) {
+ JXL_RETURN_IF_ERROR(visitor->Bits(8, 0, &components[i].id));
+ }
+ } else if (component_type == JPEGComponentType::kGray) {
+ components[0].id = 1;
+ } else if (component_type == JPEGComponentType::kRGB) {
+ components[0].id = 'R';
+ components[1].id = 'G';
+ components[2].id = 'B';
+ } else {
+ components[0].id = 1;
+ components[1].id = 2;
+ components[2].id = 3;
+ }
+ size_t used_tables = 0;
+ for (size_t i = 0; i < components.size(); i++) {
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, 0, &components[i].quant_idx));
+ if (components[i].quant_idx >= quant.size()) {
+ return JXL_FAILURE("Invalid quant table for component %" PRIuS ": %u\n",
+ i, components[i].quant_idx);
+ }
+ used_tables |= 1U << components[i].quant_idx;
+ }
+ for (size_t i = 0; i < quant.size(); i++) {
+ if (used_tables & (1 << i)) continue;
+ if (i == 0) return JXL_FAILURE("First quant table unused.");
+ // Unused quant table has to be set to copy of previous quant table
+ for (size_t j = 0; j < 64; j++) {
+ if (quant[i].values[j] != quant[i - 1].values[j]) {
+ return JXL_FAILURE("Non-trivial unused quant table");
+ }
+ }
+ }
+
+ uint32_t num_huff = huffman_code.size();
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(4), BitsOffset(3, 2), BitsOffset(4, 10),
+ BitsOffset(6, 26), 4, &num_huff));
+ if (visitor->IsReading()) {
+ huffman_code.resize(num_huff);
+ }
+ for (JPEGHuffmanCode& hc : huffman_code) {
+ bool is_ac = hc.slot_id >> 4;
+ uint32_t id = hc.slot_id & 0xF;
+ JXL_RETURN_IF_ERROR(visitor->Bool(false, &is_ac));
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, 0, &id));
+ hc.slot_id = (static_cast<uint32_t>(is_ac) << 4) | id;
+ JXL_RETURN_IF_ERROR(visitor->Bool(true, &hc.is_last));
+ size_t num_symbols = 0;
+ for (size_t i = 0; i <= 16; i++) {
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), Val(1), BitsOffset(3, 2),
+ Bits(8), 0, &hc.counts[i]));
+ num_symbols += hc.counts[i];
+ }
+ if (num_symbols < 1) {
+ // Actually, at least 2 symbols are required, since one of them is EOI.
+ return JXL_FAILURE("Empty Huffman table");
+ }
+ if (num_symbols > hc.values.size()) {
+ return JXL_FAILURE("Huffman code too large (%" PRIuS ")", num_symbols);
+ }
+ // Presence flags for 4 * 64 + 1 values.
+ uint64_t value_slots[5] = {};
+ for (size_t i = 0; i < num_symbols; i++) {
+ // Goes up to 256, included. Might have the same symbol appear twice...
+ JXL_RETURN_IF_ERROR(visitor->U32(Bits(2), BitsOffset(2, 4),
+ BitsOffset(4, 8), BitsOffset(8, 1), 0,
+ &hc.values[i]));
+ value_slots[hc.values[i] >> 6] |= (uint64_t)1 << (hc.values[i] & 0x3F);
+ }
+ if (hc.values[num_symbols - 1] != kJpegHuffmanAlphabetSize) {
+ return JXL_FAILURE("Missing EOI symbol");
+ }
+ // Last element, denoting EOI, have to be 1 after the loop.
+ JXL_ASSERT(value_slots[4] == 1);
+ size_t num_values = 1;
+ for (size_t i = 0; i < 4; ++i) num_values += hwy::PopCount(value_slots[i]);
+ if (num_values != num_symbols) {
+ return JXL_FAILURE("Duplicate Huffman symbols");
+ }
+ if (!is_ac) {
+ bool only_dc = ((value_slots[0] >> kJpegDCAlphabetSize) | value_slots[1] |
+ value_slots[2] | value_slots[3]) == 0;
+ if (!only_dc) return JXL_FAILURE("Huffman symbols out of DC range");
+ }
+ }
+
+ for (auto& scan : scan_info) {
+ JXL_RETURN_IF_ERROR(
+ visitor->U32(Val(1), Val(2), Val(3), Val(4), 1, &scan.num_components));
+ if (scan.num_components >= 4) {
+ return JXL_FAILURE("Invalid number of components in SOS marker");
+ }
+ JXL_RETURN_IF_ERROR(visitor->Bits(6, 0, &scan.Ss));
+ JXL_RETURN_IF_ERROR(visitor->Bits(6, 63, &scan.Se));
+ JXL_RETURN_IF_ERROR(visitor->Bits(4, 0, &scan.Al));
+ JXL_RETURN_IF_ERROR(visitor->Bits(4, 0, &scan.Ah));
+ for (size_t i = 0; i < scan.num_components; i++) {
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, 0, &scan.components[i].comp_idx));
+ if (scan.components[i].comp_idx >= components.size()) {
+ return JXL_FAILURE("Invalid component idx in SOS marker");
+ }
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, 0, &scan.components[i].ac_tbl_idx));
+ JXL_RETURN_IF_ERROR(visitor->Bits(2, 0, &scan.components[i].dc_tbl_idx));
+ }
+ // TODO(veluca): actually set and use this value.
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), Val(1), Val(2), BitsOffset(3, 3),
+ kMaxNumPasses - 1,
+ &scan.last_needed_pass));
+ }
+
+ // From here on, this is data that is not strictly necessary to get a valid
+ // JPEG, but necessary for bit-exact JPEG reconstruction.
+ if (info.has_dri) {
+ JXL_RETURN_IF_ERROR(visitor->Bits(16, 0, &restart_interval));
+ }
+
+ for (auto& scan : scan_info) {
+ uint32_t num_reset_points = scan.reset_points.size();
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(2, 1), BitsOffset(4, 4),
+ BitsOffset(16, 20), 0, &num_reset_points));
+ if (visitor->IsReading()) {
+ scan.reset_points.resize(num_reset_points);
+ }
+ int last_block_idx = -1;
+ for (auto& block_idx : scan.reset_points) {
+ block_idx -= last_block_idx + 1;
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(3, 1),
+ BitsOffset(5, 9), BitsOffset(28, 41), 0,
+ &block_idx));
+ block_idx += last_block_idx + 1;
+ if (static_cast<int>(block_idx) < last_block_idx + 1) {
+ return JXL_FAILURE("Invalid block ID: %u, last block was %d", block_idx,
+ last_block_idx);
+ }
+ // TODO(eustas): better upper boundary could be given at this point; also
+ // it could be applied during reset_points reading.
+ if (block_idx > (1u << 30)) {
+ // At most 8K x 8K x num_channels blocks are expected. That is,
+ // typically, 1.5 * 2^27. 2^30 should be sufficient for any sane
+ // image.
+ return JXL_FAILURE("Invalid block ID: %u", block_idx);
+ }
+ last_block_idx = block_idx;
+ }
+
+ uint32_t num_extra_zero_runs = scan.extra_zero_runs.size();
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(2, 1), BitsOffset(4, 4),
+ BitsOffset(16, 20), 0,
+ &num_extra_zero_runs));
+ if (visitor->IsReading()) {
+ scan.extra_zero_runs.resize(num_extra_zero_runs);
+ }
+ last_block_idx = -1;
+ for (size_t i = 0; i < scan.extra_zero_runs.size(); ++i) {
+ uint32_t& block_idx = scan.extra_zero_runs[i].block_idx;
+ JXL_RETURN_IF_ERROR(visitor->U32(
+ Val(1), BitsOffset(2, 2), BitsOffset(4, 5), BitsOffset(8, 20), 1,
+ &scan.extra_zero_runs[i].num_extra_zero_runs));
+ block_idx -= last_block_idx + 1;
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(3, 1),
+ BitsOffset(5, 9), BitsOffset(28, 41), 0,
+ &block_idx));
+ block_idx += last_block_idx + 1;
+ if (static_cast<int>(block_idx) < last_block_idx + 1) {
+ return JXL_FAILURE("Invalid block ID: %u, last block was %d", block_idx,
+ last_block_idx);
+ }
+ if (block_idx > (1u << 30)) {
+ // At most 8K x 8K x num_channels blocks are expected. That is,
+ // typically, 1.5 * 2^27. 2^30 should be sufficient for any sane
+ // image.
+ return JXL_FAILURE("Invalid block ID: %u", block_idx);
+ }
+ last_block_idx = block_idx;
+ }
+ }
+ std::vector<uint32_t> inter_marker_data_sizes;
+ inter_marker_data_sizes.reserve(info.num_intermarker);
+ for (size_t i = 0; i < info.num_intermarker; ++i) {
+ uint32_t len = visitor->IsReading() ? 0 : inter_marker_data[i].size();
+ JXL_RETURN_IF_ERROR(visitor->Bits(16, 0, &len));
+ if (visitor->IsReading()) inter_marker_data_sizes.emplace_back(len);
+ }
+ uint32_t tail_data_len = tail_data.size();
+ if (!visitor->IsReading() && tail_data_len > 4260096) {
+ return JXL_FAILURE("Tail data too large (max size = 4260096, size = %u).",
+ tail_data_len);
+ }
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(8, 1),
+ BitsOffset(16, 257), BitsOffset(22, 65793),
+ 0, &tail_data_len));
+
+ JXL_RETURN_IF_ERROR(visitor->Bool(false, &has_zero_padding_bit));
+ if (has_zero_padding_bit) {
+ uint32_t nbit = padding_bits.size();
+ JXL_RETURN_IF_ERROR(visitor->Bits(24, 0, &nbit));
+ if (visitor->IsReading()) {
+ padding_bits.reserve(std::min<uint32_t>(1024u, nbit));
+ for (uint32_t i = 0; i < nbit; i++) {
+ bool bbit = false;
+ JXL_RETURN_IF_ERROR(visitor->Bool(false, &bbit));
+ padding_bits.push_back(bbit);
+ }
+ } else {
+ for (uint8_t& bit : padding_bits) {
+ bool bbit = bit;
+ JXL_RETURN_IF_ERROR(visitor->Bool(false, &bbit));
+ bit = bbit;
+ }
+ }
+ }
+
+ // Apply postponed actions.
+ if (visitor->IsReading()) {
+ tail_data.resize(tail_data_len);
+ JXL_ASSERT(inter_marker_data_sizes.size() == info.num_intermarker);
+ inter_marker_data.reserve(info.num_intermarker);
+ for (size_t i = 0; i < info.num_intermarker; ++i) {
+ inter_marker_data.emplace_back(inter_marker_data_sizes[i]);
+ }
+ }
+
+ return true;
+}
+
+#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
+
+void JPEGData::CalculateMcuSize(const JPEGScanInfo& scan, int* MCUs_per_row,
+ int* MCU_rows) const {
+ const bool is_interleaved = (scan.num_components > 1);
+ const JPEGComponent& base_component = components[scan.components[0].comp_idx];
+ // h_group / v_group act as numerators for converting number of blocks to
+ // number of MCU. In interleaved mode it is 1, so MCU is represented with
+ // max_*_samp_factor blocks. In non-interleaved mode we choose numerator to
+ // be the samping factor, consequently MCU is always represented with single
+ // block.
+ const int h_group = is_interleaved ? 1 : base_component.h_samp_factor;
+ const int v_group = is_interleaved ? 1 : base_component.v_samp_factor;
+ int max_h_samp_factor = 1;
+ int max_v_samp_factor = 1;
+ for (const auto& c : components) {
+ max_h_samp_factor = std::max(c.h_samp_factor, max_h_samp_factor);
+ max_v_samp_factor = std::max(c.v_samp_factor, max_v_samp_factor);
+ }
+ *MCUs_per_row = DivCeil(width * h_group, 8 * max_h_samp_factor);
+ *MCU_rows = DivCeil(height * v_group, 8 * max_v_samp_factor);
+}
+
+#if JPEGXL_ENABLE_TRANSCODE_JPEG
+
+Status SetJPEGDataFromICC(const PaddedBytes& icc, jpeg::JPEGData* jpeg_data) {
+ size_t icc_pos = 0;
+ for (size_t i = 0; i < jpeg_data->app_data.size(); i++) {
+ if (jpeg_data->app_marker_type[i] != jpeg::AppMarkerType::kICC) {
+ continue;
+ }
+ size_t len = jpeg_data->app_data[i].size() - 17;
+ if (icc_pos + len > icc.size()) {
+ return JXL_FAILURE(
+ "ICC length is less than APP markers: requested %" PRIuS
+ " more bytes, "
+ "%" PRIuS " available",
+ len, icc.size() - icc_pos);
+ }
+ memcpy(&jpeg_data->app_data[i][17], icc.data() + icc_pos, len);
+ icc_pos += len;
+ }
+ if (icc_pos != icc.size() && icc_pos != 0) {
+ return JXL_FAILURE("ICC length is more than APP markers");
+ }
+ return true;
+}
+
+#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
+
+} // namespace jpeg
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.h b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.h
new file mode 100644
index 0000000000..70ff4f8e05
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.h
@@ -0,0 +1,216 @@
+// 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.
+
+// Data structures that represent the non-pixel contents of a jpeg file.
+
+#ifndef LIB_JXL_JPEG_JPEG_DATA_H_
+#define LIB_JXL_JPEG_JPEG_DATA_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+#include <vector>
+
+#include "lib/jxl/common.h" // JPEGXL_ENABLE_TRANSCODE_JPEG
+#include "lib/jxl/fields.h"
+
+namespace jxl {
+namespace jpeg {
+
+constexpr int kMaxComponents = 4;
+constexpr int kMaxQuantTables = 4;
+constexpr int kMaxHuffmanTables = 4;
+constexpr size_t kJpegHuffmanMaxBitLength = 16;
+constexpr int kJpegHuffmanAlphabetSize = 256;
+constexpr int kJpegDCAlphabetSize = 12;
+constexpr int kMaxDHTMarkers = 512;
+constexpr int kMaxDimPixels = 65535;
+constexpr uint8_t kApp1 = 0xE1;
+constexpr uint8_t kApp2 = 0xE2;
+const uint8_t kIccProfileTag[12] = "ICC_PROFILE";
+const uint8_t kExifTag[6] = "Exif\0";
+const uint8_t kXMPTag[29] = "http://ns.adobe.com/xap/1.0/";
+
+/* clang-format off */
+constexpr uint32_t kJPEGNaturalOrder[80] = {
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ // extra entries for safety in decoder
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63
+};
+
+constexpr uint32_t kJPEGZigZagOrder[64] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+/* clang-format on */
+
+// Quantization values for an 8x8 pixel block.
+struct JPEGQuantTable {
+ std::array<int32_t, kDCTBlockSize> values;
+ uint32_t precision = 0;
+ // The index of this quantization table as it was parsed from the input JPEG.
+ // Each DQT marker segment contains an 'index' field, and we save this index
+ // here. Valid values are 0 to 3.
+ uint32_t index = 0;
+ // Set to true if this table is the last one within its marker segment.
+ bool is_last = true;
+};
+
+// Huffman code and decoding lookup table used for DC and AC coefficients.
+struct JPEGHuffmanCode {
+ // Bit length histogram.
+ std::array<uint32_t, kJpegHuffmanMaxBitLength + 1> counts = {};
+ // Symbol values sorted by increasing bit lengths.
+ std::array<uint32_t, kJpegHuffmanAlphabetSize + 1> values = {};
+ // The index of the Huffman code in the current set of Huffman codes. For AC
+ // component Huffman codes, 0x10 is added to the index.
+ int slot_id = 0;
+ // Set to true if this Huffman code is the last one within its marker segment.
+ bool is_last = true;
+};
+
+// Huffman table indexes used for one component of one scan.
+struct JPEGComponentScanInfo {
+ uint32_t comp_idx;
+ uint32_t dc_tbl_idx;
+ uint32_t ac_tbl_idx;
+};
+
+// Contains information that is used in one scan.
+struct JPEGScanInfo {
+ // Parameters used for progressive scans (named the same way as in the spec):
+ // Ss : Start of spectral band in zig-zag sequence.
+ // Se : End of spectral band in zig-zag sequence.
+ // Ah : Successive approximation bit position, high.
+ // Al : Successive approximation bit position, low.
+ uint32_t Ss;
+ uint32_t Se;
+ uint32_t Ah;
+ uint32_t Al;
+ uint32_t num_components = 0;
+ std::array<JPEGComponentScanInfo, 4> components;
+ // Last codestream pass that is needed to write this scan.
+ uint32_t last_needed_pass = 0;
+
+ // Extra information required for bit-precise JPEG file reconstruction.
+
+ // Set of block indexes where the JPEG encoder has to flush the end-of-block
+ // runs and refinement bits.
+ std::vector<uint32_t> reset_points;
+ // The number of extra zero runs (Huffman symbol 0xf0) before the end of
+ // block (if nonzero), indexed by block index.
+ // All of these symbols can be omitted without changing the pixel values, but
+ // some jpeg encoders put these at the end of blocks.
+ typedef struct {
+ uint32_t block_idx;
+ uint32_t num_extra_zero_runs;
+ } ExtraZeroRunInfo;
+ std::vector<ExtraZeroRunInfo> extra_zero_runs;
+};
+
+typedef int16_t coeff_t;
+
+// Represents one component of a jpeg file.
+struct JPEGComponent {
+ JPEGComponent()
+ : id(0),
+ h_samp_factor(1),
+ v_samp_factor(1),
+ quant_idx(0),
+ width_in_blocks(0),
+ height_in_blocks(0) {}
+
+ // One-byte id of the component.
+ uint32_t id;
+ // Horizontal and vertical sampling factors.
+ // In interleaved mode, each minimal coded unit (MCU) has
+ // h_samp_factor x v_samp_factor DCT blocks from this component.
+ int h_samp_factor;
+ int v_samp_factor;
+ // The index of the quantization table used for this component.
+ uint32_t quant_idx;
+ // The dimensions of the component measured in 8x8 blocks.
+ uint32_t width_in_blocks;
+ uint32_t height_in_blocks;
+ // The DCT coefficients of this component, laid out block-by-block, divided
+ // through the quantization matrix values.
+ std::vector<coeff_t> coeffs;
+};
+
+enum class AppMarkerType : uint32_t {
+ kUnknown = 0,
+ kICC = 1,
+ kExif = 2,
+ kXMP = 3,
+};
+
+// Represents a parsed jpeg file.
+struct JPEGData : public Fields {
+ JPEGData()
+ : width(0), height(0), restart_interval(0), has_zero_padding_bit(false) {}
+
+ JXL_FIELDS_NAME(JPEGData)
+#if JPEGXL_ENABLE_TRANSCODE_JPEG
+ // Doesn't serialize everything - skips brotli-encoded data and what is
+ // already encoded in the codestream.
+ Status VisitFields(Visitor* visitor) override;
+#else
+ Status VisitFields(Visitor* /* visitor */) override {
+ JXL_ABORT("JPEG transcoding support not enabled");
+ }
+#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
+
+ void CalculateMcuSize(const JPEGScanInfo& scan, int* MCUs_per_row,
+ int* MCU_rows) const;
+
+ int width;
+ int height;
+ uint32_t restart_interval;
+ std::vector<std::vector<uint8_t>> app_data;
+ std::vector<AppMarkerType> app_marker_type;
+ std::vector<std::vector<uint8_t>> com_data;
+ std::vector<JPEGQuantTable> quant;
+ std::vector<JPEGHuffmanCode> huffman_code;
+ std::vector<JPEGComponent> components;
+ std::vector<JPEGScanInfo> scan_info;
+ std::vector<uint8_t> marker_order;
+ std::vector<std::vector<uint8_t>> inter_marker_data;
+ std::vector<uint8_t> tail_data;
+
+ // Extra information required for bit-precise JPEG file reconstruction.
+
+ bool has_zero_padding_bit;
+ std::vector<uint8_t> padding_bits;
+};
+
+#if JPEGXL_ENABLE_TRANSCODE_JPEG
+// Set ICC profile in jpeg_data.
+Status SetJPEGDataFromICC(const PaddedBytes& icc, jpeg::JPEGData* jpeg_data);
+#else
+static JXL_INLINE Status SetJPEGDataFromICC(const PaddedBytes& /* icc */,
+ jpeg::JPEGData* /* jpeg_data */) {
+ JXL_ABORT("JPEG transcoding support not enabled");
+}
+#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
+
+} // namespace jpeg
+} // namespace jxl
+
+#endif // LIB_JXL_JPEG_JPEG_DATA_H_