| // 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 |