blob: ca839396e51a2603268d27473c92f6d061510201 [file] [log] [blame]
// Copyright 2020 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 "./container.h"
#include <algorithm>
#include <cassert>
#include <sstream>
#include <string>
#include "./bit_packing.h"
namespace container {
namespace {
static_assert(kMaxChunkSize - 1u <= kMaxVarInt,
"The maximum chunk size cannot be represented with var ints.");
//------------------------------------------------------------------------------
// Writes the bytes of a 'chunk', after writing its size as a variable int.
void WriteChunk(const uint8_t chunk[], size_t chunk_size, Data& data) {
EncodeVarInt(chunk_size, 1, kMaxChunkSize, data);
data.insert(data.end(), chunk, chunk + chunk_size);
}
// Reads the bytes of a 'chunk', after reading its size as a variable int.
// Advances 'data_pos' accordingly.
bool ReadChunk(const uint8_t data[], size_t data_size, uint32_t& data_pos,
Data& chunk) {
uint32_t chunk_size;
if (!DecodeVarInt(1, kMaxChunkSize, data, data_size, data_pos, chunk_size) ||
data_pos + chunk_size > data_size) {
return false;
}
assert(chunk.empty());
chunk.reserve(chunk_size);
chunk.insert(chunk.end(), data + data_pos, data + data_pos + chunk_size);
data_pos += chunk_size;
return true;
}
// Same as ReadChunk() but without copying bytes.
bool SkipChunk(const uint8_t data[], size_t data_size, uint32_t& data_pos) {
uint32_t chunk_size;
if (!DecodeVarInt(1, kMaxChunkSize, data, data_size, data_pos, chunk_size) ||
data_pos + chunk_size > data_size) {
return false;
}
data_pos += chunk_size;
return true;
}
} // namespace
//------------------------------------------------------------------------------
// The content of these functions can be replaced by actual image compression
// algorithms. Currently it is just raw 8b pixel packing, with or without alpha.
void EncodeRawPixels(const Image& image, Data& data) {
// TODO(yguyon): Implement if (image.is_animation)
if (image.format == Format::kARGB8) {
data.insert(data.end(), image.pixels.begin(), image.pixels.end());
} else {
abort(); // TODO(yguyon): Implement
}
}
bool DecodeRawPixels(const uint8_t data[], size_t data_size, Image& image) {
// TODO(yguyon): Implement if (image.is_animation)
if (image.format == Format::kARGB8) {
if (data_size != image.width * image.height * (image.has_alpha ? 4 : 3)) {
return false;
}
assert(image.pixels.empty());
image.pixels.reserve(data_size);
image.pixels.insert(image.pixels.end(), data, data + data_size);
} else {
abort(); // TODO(yguyon): Implement
}
return true;
}
//------------------------------------------------------------------------------
void WrapContainer(const Image& image, const EncoderConfig& config,
uint32_t codec, const uint8_t codec_bytes[],
size_t num_codec_bytes, Data& data) {
data.clear();
BitPacker header;
header.EncodeUInt(kContainerTag, 0, (1 << kContainerTagNumBits) - 1);
header.EncodeUInt(codec, 0, (1 << kContainerCodecNumBits) - 1);
header.EncodeUInt(image.width, 1, 1 << kImageDimNumBits);
header.EncodeUInt(image.height, 1, 1 << kImageDimNumBits);
header.EncodeUInt(static_cast<uint32_t>(image.orientation), 0, 7);
header.EncodeBool(image.has_alpha);
header.EncodeBool(image.is_animation);
header.EncodeBool(image.is_animation ? image.loop_forever : false);
header.EncodeUInt(image.preview_color, 0, (1 << 12) - 1);
header.EncodeBool(config.create_preview);
header.EncodeBool(!image.icc.empty());
header.EncodeBool(!image.xmp.empty());
header.EncodeBool(!image.exif.empty());
header.EncodeUInt((image.format == Format::kARGB8) ? 0
: (image.format == Format::kAYUV10) ? 1
: (image.format == Format::kAYUV12) ? 2
: 3,
0, 3);
header.EncodeUInt(kContainerHeaderPaddingValue, 0, 15);
assert(header.GetNumBits() == kContainerHeaderNumBits);
data.reserve(header.GetNumBytes());
data.insert(data.end(), header.GetBytes(),
header.GetBytes() + header.GetNumBytes());
if (!image.icc.empty()) {
WriteChunk(image.icc.data(), image.icc.size(), data);
}
if (config.create_preview) {
abort(); // TODO(yguyon): Implement
}
WriteChunk(codec_bytes, num_codec_bytes, data);
if (!image.xmp.empty()) {
WriteChunk(image.xmp.data(), image.xmp.size(), data);
}
if (!image.exif.empty()) {
WriteChunk(image.exif.data(), image.exif.size(), data);
}
}
//------------------------------------------------------------------------------
namespace {
// Extracts 'num_read_bytes' from 'data' into 'image' features.
bool UnwrapContainerHeader(const uint8_t data[], size_t data_size, Image& image,
uint32_t& codec, bool& has_preview, bool& has_icc,
bool& has_xmp, bool& has_exif,
uint32_t& num_read_bytes) {
BitUnpacker header(data, data_size);
const uint32_t tag = header.DecodeUInt(kContainerTagNumBits);
codec = header.DecodeUInt(kContainerCodecNumBits);
if (tag != kContainerTag) return false;
image.width = header.DecodeUInt(1, 1 << kImageDimNumBits);
image.height = header.DecodeUInt(1, 1 << kImageDimNumBits);
image.orientation = static_cast<Orientation>(header.DecodeUInt(0, 7));
image.has_alpha = header.DecodeBool();
image.is_animation = header.DecodeBool();
image.loop_forever = header.DecodeBool() && image.is_animation;
image.preview_color = header.DecodeUInt(/*num_bits=*/12);
has_preview = header.DecodeBool();
has_icc = header.DecodeBool();
has_xmp = header.DecodeBool();
has_exif = header.DecodeBool();
const uint32_t format = header.DecodeUInt(0, 3);
image.format = (format == 0) ? Format::kARGB8
: (format == 1) ? Format::kAYUV10
: (format == 2) ? Format::kAYUV12
: Format::kUnknown;
if (header.DecodeUInt(0, 15) != kContainerHeaderPaddingValue) return false;
assert(header.GetNumReadBits() == kContainerHeaderNumBits);
num_read_bytes = header.GetNumReadBytes();
return header.Ok();
}
} // namespace
bool UnwrapContainer(const uint8_t data[], size_t data_size, Image& image,
uint32_t& codec, size_t& offset_till_codec_bytes,
size_t& num_codec_bytes) {
image = Image();
bool has_preview, has_icc, has_xmp, has_exif;
uint32_t data_pos;
if (!UnwrapContainerHeader(data, data_size, image, codec, has_preview,
has_icc, has_xmp, has_exif, data_pos)) {
return false;
}
if ((has_icc && !ReadChunk(data, data_size, data_pos, image.icc)) ||
(has_preview && !SkipChunk(data, data_size, data_pos))) {
return false;
}
uint32_t chunk_size;
if (!DecodeVarInt(1, kMaxChunkSize, data, data_size, data_pos, chunk_size) ||
data_pos + chunk_size > data_size) {
return false;
}
offset_till_codec_bytes = data_pos;
num_codec_bytes = chunk_size;
data_pos += chunk_size;
if ((has_xmp && !ReadChunk(data, data_size, data_pos, image.xmp)) ||
(has_exif && !ReadChunk(data, data_size, data_pos, image.exif))) {
return false;
}
return true;
}
//------------------------------------------------------------------------------
std::string ContainerHeaderToStr(const uint8_t data[], size_t data_size) {
static const char* kOrientationStr[] = {"Original",
"FlipLeftToRight",
"180",
"FlipTopToBottom",
"FlipBotLeftToTopRgt",
"90Clockwise",
"FlipTopLeftToBotRgt",
"270Clockwise"};
static const char* kFormatStr[] = {"ARGB8", "AYUV10", "AYUV12", "Unknown"};
static const char* kBoolStr[] = {"false", "true"};
std::stringstream ss;
Image image;
uint32_t codec;
bool has_preview, has_icc, has_xmp, has_exif;
uint32_t num_read_bytes;
if (!UnwrapContainerHeader(data, data_size, image, codec, has_preview,
has_icc, has_xmp, has_exif, num_read_bytes)) {
ss << "Bad container header" << std::endl;
return ss.str();
}
const int orientation = static_cast<int>(image.orientation);
const int format = static_cast<int>(image.format);
if (orientation < 0 || orientation >= array_size(kOrientationStr) ||
format < 0 || format >= array_size(kFormatStr)) {
ss << "Bad container header value" << std::endl;
return ss.str();
}
ss << "Container header (" << num_read_bytes << " bytes, codec " << codec
<< ")" << std::endl
<< " width " << image.width << std::endl
<< " height " << image.height << std::endl
<< " orientation " << kOrientationStr[orientation] << std::endl
<< " has_alpha " << kBoolStr[image.has_alpha] << std::endl
<< " is_animation " << kBoolStr[image.is_animation] << std::endl
<< " loop_forever " << kBoolStr[image.loop_forever] << std::endl
<< " preview_color " << image.preview_color << std::endl
<< " has_preview " << kBoolStr[has_preview] << std::endl
<< " has_icc " << kBoolStr[has_icc] << std::endl
<< " has_xmp " << kBoolStr[has_xmp] << std::endl
<< " has_exif " << kBoolStr[has_exif] << std::endl
<< " format " << kFormatStr[format] << std::endl;
return ss.str();
}
//------------------------------------------------------------------------------
} // namespace container