blob: 9e924dbd7a79145074a7c33b4cb353c901bab14e [file]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/jpeg/box_header.h"
#include <cstdint>
#include <ostream>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/endian/endian_reading.h"
namespace credentio {
namespace {
const uint32_t kJpegStartMarker = 0xffd8;
const uint32_t kJpegEndMarker = 0xffd9;
// Converts a JPEG marker to a human readable label.
// Sourced from Table B.1 of ISO/IEC 10918-1:1994
std::string ConvertMarkerToLabel(uint16_t marker) {
if (0xff00 != (marker & 0xff00) || marker == 0xffff) {
// Markers are always 0xFFXX
// 0xFFFF is not a valid JPEG marker
return absl::StrFormat("%04x", marker);
}
if (0x00c0 == (marker & 0x00f0)) {
switch (marker) {
case 0xffc4:
return "DHT"; // Define Huffman Table(s)
case 0xffc8:
return "JPG"; // Reserved for JPG extensions
case 0xffcc:
return "DAC"; // Define arithmetic coding conditioning(s)
default:
// Start Of Frame markers labeled numerically
return absl::StrFormat("SOF%d", (marker & 0x000f));
}
}
if (0x00d0 == (marker & 0x00f0)) {
switch (marker) {
case 0xffd8:
return "SOI"; // Start of Image
case 0xffd9:
return "EOI"; // End of Image
case 0xffda:
return "SOS"; // Start of Scan
case 0xffdb:
return "DQT"; // Define Quantization Table(s)
case 0xffdc:
return "DNL"; // Define Number of Lines
case 0xffdd:
return "DRI"; // Define Restart Interval
case 0xffde:
return "DHP"; // Define Hierarchical Progression
case 0xffdf:
return "EXP"; // Expand reference component(s)
default:
// Restart with modulo 8 markers labeled numerically
return absl::StrFormat("RST%d", (marker & 0x000f));
}
}
if (0x00e0 == (marker & 0x00f0)) {
// Reserved for APP markers labeled numerically
return absl::StrFormat("APP%d", (marker & 0x000f));
}
if (0x00f0 == (marker & 0x00f0)) {
switch (marker) {
case 0xfffe:
return "COM"; // Comment
default:
// Reserved for JPEG extensions labeled numerically
return absl::StrFormat("JPG%d", (marker & 0x000f));
}
}
if (marker == 0xff01) {
return "TEM"; // For temporary private use in arithmetic coding
}
return "RES"; // Reserved
}
} // namespace
std::string JpegBoxHeader::label() const {
return ConvertMarkerToLabel(this->type);
}
absl::StatusOr<JpegBoxHeader> ConsumeJpegBoxHeader(riegeli::Reader& input) {
JpegBoxHeader b = {.offset = input.pos()};
if (!riegeli::ReadBigEndian<uint16_t>(input, b.type)) {
return input.StatusOrAnnotate(absl::DataLossError("Failed to read type"));
}
if (0xff00 != (b.type & 0xff00) || b.type == 0xffff) {
// Invalid marker.
return absl::InvalidArgumentError(
absl::StrFormat("Invalid JPEG marker: %d", b.type));
}
if (b.type == kJpegStartMarker || b.type == kJpegEndMarker ||
b.type == 0xff01 || (b.type >= 0xffd0 && b.type <= 0xffd7)) {
// No payload.
b.size = sizeof(uint16_t);
return b;
}
uint16_t chunk_size;
if (!riegeli::ReadBigEndian<uint16_t>(input, chunk_size)) {
return input.StatusOrAnnotate(absl::DataLossError("Failed to read size"));
}
if (chunk_size < 2) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid JPEG segment size: %d", chunk_size));
}
b.size = chunk_size + sizeof(b.type);
return b;
}
void PrintTo(const JpegBoxHeader& x, ::std::ostream* os) {
*os << absl::StrCat("{type: ", x.type, ", offset: ", x.offset,
", size: ", x.size, ", label: \"", x.label(), "\"}");
}
} // namespace credentio