blob: 37889ff154cf09e6ab0f9ab4dc71b1d6e2a7349b [file] [edit]
// 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/gif/reader.h"
#include <cstdint>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "formats/gif/constants.h"
#include "riegeli/bytes/reader.h"
namespace credentio {
namespace {
struct ColorTable {
bool has_table;
uint8_t size;
static ColorTable FromPacked(uint8_t packed) {
return {
.has_table = (packed & 0x80) != 0,
.size = static_cast<uint8_t>(packed & 0x07),
};
}
};
absl::Status SeekToEndOfData(riegeli::Reader& input) {
uint8_t size = 0x00;
do {
if (!input.ReadByte(size)) {
return input.StatusOrAnnotate(absl::DataLossError("Failed to read byte"));
}
if (!input.ok()) {
return input.status();
}
if (size > 0) {
if (!input.Skip(size)) {
return absl::InternalError("Failed to seek to offset");
}
if (!input.ok()) {
return input.status();
}
}
} while (size > 0);
return absl::OkStatus();
}
absl::StatusOr<GifBlock> ReadHeader(riegeli::Reader& input) {
GifBlock block = {.offset = input.pos()};
std::string data;
if (!input.Read(kGifHeaderBlock.size(), data)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read GIF header block"));
}
if (data != kGifHeaderBlock) {
return absl::InvalidArgumentError("Invalid GIF header block");
}
block.length = data.size();
block.type = data;
return std::move(block);
}
absl::Status PopulateDescriptor(riegeli::Reader& input,
int64_t packed_byte_offset, GifBlock& block) {
std::string data;
if (!input.Read(block.length, data)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read descriptor"));
}
ColorTable packed = ColorTable::FromPacked(data[packed_byte_offset]);
if (packed.has_table) {
int64_t ct_length = 3 * (1 << (packed.size + 1));
block.length += ct_length;
}
return absl::OkStatus();
}
absl::Status PopulateLogicalDescriptor(riegeli::Reader& input,
GifBlock& block) {
block.length = 7;
block.type = std::string(kGifLogicalScreenDescriptorLabel);
return PopulateDescriptor(input, 4, block);
}
absl::Status PopulateImageDescriptor(riegeli::Reader& input, GifBlock& block) {
// Backup one byte so we can read the introducer byte.
if (!input.Seek(input.pos() - 1)) {
return absl::InternalError("Failed to seek to offset");
}
block.length = 10;
block.type = std::string(kGifImageDescriptorLabel);
return PopulateDescriptor(input, 9, block);
}
absl::Status PopulateBlock(riegeli::Reader& input, GifBlock& block) {
ABSL_RETURN_IF_ERROR(SeekToEndOfData(input));
block.length = input.pos() - block.offset;
return absl::OkStatus();
}
absl::Status PopulateApplicationExtension(riegeli::Reader& input,
GifBlock& block) {
int64_t next_size_offset = input.pos();
uint8_t extension_info_size = 0x00;
if (!input.ReadByte(extension_info_size)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read extension info size"));
}
next_size_offset += 1 + extension_info_size;
if (extension_info_size == kGifC2paExtensionInfoSize) {
std::string data;
if (!input.Read(kGifC2paIdentifier.size(), data)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read c2pa identifier"));
}
if (data == kGifC2paIdentifier) {
block.type = kGifC2paLabel;
}
}
// Seek past the remaining Application Extension Information
if (!input.Seek(next_size_offset)) {
return absl::InternalError("Failed to seek to offset");
}
return PopulateBlock(input, block);
}
} // namespace
absl::Status IterateOverGifBlocks(riegeli::Reader& input,
GifBlockProcessor processor,
int64_t end_offset) {
if (!input.SupportsSize() || !input.Size().has_value()) {
return absl::InvalidArgumentError(
"Input does not support size or size is unknown");
}
uint64_t local_end_offset =
end_offset < 0 ? input.Size().value() : end_offset;
ABSL_ASSIGN_OR_RETURN(auto header, ReadHeader(input));
ABSL_ASSIGN_OR_RETURN(bool should_continue, processor(header));
ABSL_RETURN_IF_ERROR(input.status());
if (!should_continue) {
return absl::OkStatus();
}
if (input.pos() >= local_end_offset) {
return absl::OkStatus();
}
GifBlock logical_descriptor = {.offset = input.pos()};
ABSL_RETURN_IF_ERROR(PopulateLogicalDescriptor(input, logical_descriptor));
ABSL_ASSIGN_OR_RETURN(should_continue, processor(logical_descriptor));
ABSL_RETURN_IF_ERROR(input.status());
if (!should_continue) {
return absl::OkStatus();
}
if (!input.Seek(logical_descriptor.offset + logical_descriptor.length)) {
return input.StatusOrAnnotate(absl::InternalError(
"Failed to seek to offset after logical descriptor"));
}
while (input.pos() < local_end_offset) {
GifBlock block = {.offset = input.pos()};
uint8_t introducer = 0x00;
if (!input.ReadByte(introducer)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read introducer"));
}
switch (introducer) {
case kGifExtensionIntroducer: {
// is extension
uint8_t extension_type = 0x00;
if (!input.ReadByte(extension_type)) {
return input.StatusOrAnnotate(
absl::DataLossError("Failed to read extension type"));
}
switch (extension_type) {
case kGifExtensionPlainText:
block.type = std::string(kGifExtensionPlainTextLabel);
ABSL_RETURN_IF_ERROR(PopulateBlock(input, block));
break;
case kGifExtensionGraphicsControl:
block.type = std::string(kGifExtensionGraphicsControlLabel);
ABSL_RETURN_IF_ERROR(PopulateBlock(input, block));
break;
case kGifExtensionComment:
block.type = std::string(kGifExtensionCommentLabel);
ABSL_RETURN_IF_ERROR(PopulateBlock(input, block));
break;
case kGifExtensionApplication:
block.type = std::string(kGifExtensionApplicationLabel);
ABSL_RETURN_IF_ERROR(PopulateApplicationExtension(input, block));
break;
default:
return absl::UnimplementedError(absl::StrFormat(
"Extension Type not supported: %#04x", extension_type));
}
break;
}
case kGifImageDescriptorIntroducer: {
ABSL_RETURN_IF_ERROR(PopulateImageDescriptor(input, block));
break;
}
case kGifTrailerIntroducer: {
block.length = 1;
block.type = std::string(kGifTrailerLabel);
break;
}
default:
block.type = std::string(kGifImageDataLabel);
ABSL_RETURN_IF_ERROR(PopulateBlock(input, block));
}
ABSL_ASSIGN_OR_RETURN(should_continue, processor(block));
ABSL_RETURN_IF_ERROR(input.status());
if (!should_continue) {
return absl::OkStatus();
}
if (!input.Seek(block.offset + block.length)) {
return input.StatusOrAnnotate(
absl::InternalError("Failed to seek to offset"));
}
if (block.type == kGifTrailerLabel) {
break;
}
}
if (input.pos() < local_end_offset) {
GifBlock after_label = {.offset = input.pos(),
.length = local_end_offset - input.pos(),
.type = "c2pa.after"};
return processor(after_label).status();
}
return absl::OkStatus();
}
} // namespace credentio