blob: 2333733d2105bb61d10ba6e14e69c09b1dd46e97 [file]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include "imageio.h"
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <vector>
#include "avif/avif_cxx.h"
#include "avifjpeg.h"
#include "avifpng.h"
#include "avifutil.h"
#include "y4m.h"
#if defined(AVIF_LIBYUV_ENABLED)
#include <libyuv.h>
#endif
namespace avif {
namespace {
avifResult UpsampleYUV420To444(avifImage* image) {
if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV420) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (!image->imageOwnsYUVPlanes) {
// We need to replace the U and V planes with new, owned planes. Y has to
// be owned as well since there is a single ownership boolean for all
// three planes. If Y is not already owned, we could copy it, but this is
// not needed in practice for now so just reject it.
return AVIF_RESULT_NOT_IMPLEMENTED;
}
const uint32_t new_uv_width = image->width;
const uint32_t new_uv_height = image->height;
const uint32_t old_uv_width =
static_cast<uint32_t>((static_cast<uint64_t>(image->width) + 1) / 2);
const uint32_t old_uv_height =
static_cast<uint32_t>((static_cast<uint64_t>(image->height) + 1) / 2);
const uint32_t bytes_per_pixel = (image->depth > 8) ? 2 : 1;
if (new_uv_width > UINT32_MAX / bytes_per_pixel) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
const uint32_t new_stride_u = new_uv_width * bytes_per_pixel;
const uint32_t new_stride_v = new_stride_u;
if (new_stride_u != 0 && new_uv_height > SIZE_MAX / new_stride_u) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
const size_t uv_byte_size = static_cast<size_t>(new_stride_u) * new_uv_height;
uint8_t* new_u = static_cast<uint8_t*>(avifAlloc(uv_byte_size));
uint8_t* new_v = static_cast<uint8_t*>(avifAlloc(uv_byte_size));
if (!new_u || !new_v) {
avifFree(new_u);
avifFree(new_v);
return AVIF_RESULT_OUT_OF_MEMORY;
}
#if defined(AVIF_LIBYUV_ENABLED)
if (image->depth == 8) {
libyuv::ScalePlane(image->yuvPlanes[AVIF_CHAN_U],
image->yuvRowBytes[AVIF_CHAN_U], old_uv_width,
old_uv_height, new_u, new_stride_u, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
libyuv::ScalePlane(image->yuvPlanes[AVIF_CHAN_V],
image->yuvRowBytes[AVIF_CHAN_V], old_uv_width,
old_uv_height, new_v, new_stride_v, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
} else {
#if LIBYUV_VERSION >= 1774
libyuv::ScalePlane_12(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_U]),
image->yuvRowBytes[AVIF_CHAN_U] / 2, old_uv_width, old_uv_height,
reinterpret_cast<uint16_t*>(new_u), new_stride_u / 2, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
libyuv::ScalePlane_12(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_V]),
image->yuvRowBytes[AVIF_CHAN_V] / 2, old_uv_width, old_uv_height,
reinterpret_cast<uint16_t*>(new_v), new_stride_v / 2, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
#else
libyuv::ScalePlane_16(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_U]),
image->yuvRowBytes[AVIF_CHAN_U] / 2, old_uv_width, old_uv_height,
reinterpret_cast<uint16_t*>(new_u), new_stride_u / 2, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
libyuv::ScalePlane_16(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_V]),
image->yuvRowBytes[AVIF_CHAN_V] / 2, old_uv_width, old_uv_height,
reinterpret_cast<uint16_t*>(new_v), new_stride_v / 2, new_uv_width,
new_uv_height, libyuv::kFilterBilinear);
#endif
}
#else
(void)old_uv_width;
(void)old_uv_height;
// C Fallback: Nearest Neighbor for simplicity (while libyuv path uses a
// bilinear filter).
for (uint32_t y = 0; y < new_uv_height; ++y) {
for (uint32_t x = 0; x < new_uv_width; ++x) {
size_t src_y = static_cast<size_t>(y) / 2;
size_t src_x = static_cast<size_t>(x) / 2;
if (image->depth == 8) {
new_u[static_cast<size_t>(y) * new_stride_u + x] =
image->yuvPlanes[AVIF_CHAN_U]
[src_y * image->yuvRowBytes[AVIF_CHAN_U] + src_x];
new_v[static_cast<size_t>(y) * new_stride_v + x] =
image->yuvPlanes[AVIF_CHAN_V]
[src_y * image->yuvRowBytes[AVIF_CHAN_V] + src_x];
} else {
reinterpret_cast<uint16_t*>(
new_u)[static_cast<size_t>(y) * (new_stride_u / 2) + x] =
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_U])
[src_y * (image->yuvRowBytes[AVIF_CHAN_U] / 2) + src_x];
reinterpret_cast<uint16_t*>(
new_v)[static_cast<size_t>(y) * (new_stride_v / 2) + x] =
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_V])
[src_y * (image->yuvRowBytes[AVIF_CHAN_V] / 2) + src_x];
}
}
}
#endif
avifFree(image->yuvPlanes[AVIF_CHAN_U]);
avifFree(image->yuvPlanes[AVIF_CHAN_V]);
image->yuvPlanes[AVIF_CHAN_U] = new_u;
image->yuvPlanes[AVIF_CHAN_V] = new_v;
image->yuvRowBytes[AVIF_CHAN_U] = new_stride_u;
image->yuvRowBytes[AVIF_CHAN_V] = new_stride_v;
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
return AVIF_RESULT_OK;
}
avifResult DownsampleYUV444To420(avifImage* image) {
if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
return AVIF_RESULT_INVALID_ARGUMENT;
}
if (!image->imageOwnsYUVPlanes) {
// We need to replace the U and V planes with new, owned planes. Y has to
// be owned as well since there is a single ownership boolean for all
// three planes. If Y is not already owned, we could copy it, but this is
// not needed in practice for now so just reject it.
return AVIF_RESULT_NOT_IMPLEMENTED;
}
const uint32_t new_uv_width =
static_cast<uint32_t>((static_cast<uint64_t>(image->width) + 1) / 2);
const uint32_t new_uv_height =
static_cast<uint32_t>((static_cast<uint64_t>(image->height) + 1) / 2);
const uint32_t bytes_per_pixel = (image->depth > 8) ? 2 : 1;
const uint32_t new_stride_u = new_uv_width * bytes_per_pixel;
const uint32_t new_stride_v = new_stride_u;
const size_t uv_byte_size = static_cast<size_t>(new_stride_u) * new_uv_height;
uint8_t* new_u = static_cast<uint8_t*>(avifAlloc(uv_byte_size));
uint8_t* new_v = static_cast<uint8_t*>(avifAlloc(uv_byte_size));
if (!new_u || !new_v) {
avifFree(new_u);
avifFree(new_v);
return AVIF_RESULT_OUT_OF_MEMORY;
}
#if defined(AVIF_LIBYUV_ENABLED)
if (image->depth == 8) {
libyuv::ScalePlane(image->yuvPlanes[AVIF_CHAN_U],
image->yuvRowBytes[AVIF_CHAN_U], image->width,
image->height, new_u, new_stride_u, new_uv_width,
new_uv_height, libyuv::kFilterBox);
libyuv::ScalePlane(image->yuvPlanes[AVIF_CHAN_V],
image->yuvRowBytes[AVIF_CHAN_V], image->width,
image->height, new_v, new_stride_v, new_uv_width,
new_uv_height, libyuv::kFilterBox);
} else {
#if LIBYUV_VERSION >= 1774
libyuv::ScalePlane_12(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_U]),
image->yuvRowBytes[AVIF_CHAN_U] / 2, image->width, image->height,
reinterpret_cast<uint16_t*>(new_u), new_stride_u / 2, new_uv_width,
new_uv_height, libyuv::kFilterBox);
libyuv::ScalePlane_12(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_V]),
image->yuvRowBytes[AVIF_CHAN_V] / 2, image->width, image->height,
reinterpret_cast<uint16_t*>(new_v), new_stride_v / 2, new_uv_width,
new_uv_height, libyuv::kFilterBox);
#else
libyuv::ScalePlane_16(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_U]),
image->yuvRowBytes[AVIF_CHAN_U] / 2, image->width, image->height,
reinterpret_cast<uint16_t*>(new_u), new_stride_u / 2, new_uv_width,
new_uv_height, libyuv::kFilterBox);
libyuv::ScalePlane_16(
reinterpret_cast<const uint16_t*>(image->yuvPlanes[AVIF_CHAN_V]),
image->yuvRowBytes[AVIF_CHAN_V] / 2, image->width, image->height,
reinterpret_cast<uint16_t*>(new_v), new_stride_v / 2, new_uv_width,
new_uv_height, libyuv::kFilterBox);
#endif
}
#else
// C Fallback: 2x2 Box Filter
for (uint32_t y = 0; y < new_uv_height; ++y) {
for (uint32_t x = 0; x < new_uv_width; ++x) {
uint32_t count = 0;
uint32_t sum_u = 0;
uint32_t sum_v = 0;
for (uint32_t dy = 0; dy < 2; ++dy) {
for (uint32_t dx = 0; dx < 2; ++dx) {
size_t src_y = static_cast<size_t>(y) * 2 + dy;
size_t src_x = static_cast<size_t>(x) * 2 + dx;
if (src_y < image->height && src_x < image->width) {
if (image->depth == 8) {
sum_u +=
image->yuvPlanes[AVIF_CHAN_U]
[src_y * image->yuvRowBytes[AVIF_CHAN_U] +
src_x];
sum_v +=
image->yuvPlanes[AVIF_CHAN_V]
[src_y * image->yuvRowBytes[AVIF_CHAN_V] +
src_x];
} else {
sum_u += reinterpret_cast<const uint16_t*>(
image->yuvPlanes[AVIF_CHAN_U])
[src_y * (image->yuvRowBytes[AVIF_CHAN_U] / 2) + src_x];
sum_v += reinterpret_cast<const uint16_t*>(
image->yuvPlanes[AVIF_CHAN_V])
[src_y * (image->yuvRowBytes[AVIF_CHAN_V] / 2) + src_x];
}
count++;
}
}
}
if (image->depth == 8) {
new_u[static_cast<size_t>(y) * new_stride_u + x] =
(sum_u + count / 2) / count;
new_v[static_cast<size_t>(y) * new_stride_v + x] =
(sum_v + count / 2) / count;
} else {
reinterpret_cast<uint16_t*>(
new_u)[static_cast<size_t>(y) * (new_stride_u / 2) + x] =
(sum_u + count / 2) / count;
reinterpret_cast<uint16_t*>(
new_v)[static_cast<size_t>(y) * (new_stride_v / 2) + x] =
(sum_v + count / 2) / count;
}
}
}
#endif
avifFree(image->yuvPlanes[AVIF_CHAN_U]);
avifFree(image->yuvPlanes[AVIF_CHAN_V]);
image->yuvPlanes[AVIF_CHAN_U] = new_u;
image->yuvPlanes[AVIF_CHAN_V] = new_v;
image->yuvRowBytes[AVIF_CHAN_U] = new_stride_u;
image->yuvRowBytes[AVIF_CHAN_V] = new_stride_v;
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
return AVIF_RESULT_OK;
}
} // namespace
template <typename T>
inline T Clamp(T x, T low, T high) { // Only exists in C++17.
return (x < low) ? low : (high < x) ? high : x;
}
avifResult WriteImage(const avifImage* image, int grid_cols, int grid_rows,
const std::string& output_filename, int quality,
int speed, int jobs) {
quality = Clamp(quality, 0, 100);
speed = Clamp(speed, 0, 10);
const avifAppFileFormat output_format =
avifGuessFileFormat(output_filename.c_str());
if (output_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
std::cerr << "Cannot determine output file extension: " << output_filename
<< "\n";
return AVIF_RESULT_INVALID_ARGUMENT;
} else if (output_format == AVIF_APP_FILE_FORMAT_Y4M) {
if (!y4mWrite(output_filename.c_str(), image)) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
} else if (output_format == AVIF_APP_FILE_FORMAT_JPEG) {
if (!avifJPEGWrite(output_filename.c_str(), image, quality,
AVIF_CHROMA_UPSAMPLING_AUTOMATIC)) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
} else if (output_format == AVIF_APP_FILE_FORMAT_PNG) {
const int compression_level = Clamp(10 - speed, 0, 9);
if (!avifPNGWrite(output_filename.c_str(), image, /*requestedDepth=*/0,
AVIF_CHROMA_UPSAMPLING_AUTOMATIC, compression_level)) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
} else if (output_format == AVIF_APP_FILE_FORMAT_AVIF) {
EncoderPtr encoder(avifEncoderCreate());
if (encoder == nullptr) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
encoder->quality = quality;
encoder->speed = speed;
encoder->maxThreads = jobs;
return WriteAvifGrid(image, grid_cols, grid_rows, encoder.get(),
output_filename);
} else {
std::cerr << "Unsupported output file extension: " << output_filename
<< "\n";
return AVIF_RESULT_INVALID_ARGUMENT;
}
return AVIF_RESULT_OK;
}
namespace {
std::string QualityLevelString(int quality) {
if (quality == AVIF_QUALITY_LOSSLESS) {
return "Lossless";
}
if (quality >= 80) {
return "High";
}
if (quality >= 50) {
return "Medium";
}
if (quality == AVIF_QUALITY_WORST) {
return "Worst";
}
return "Low";
}
// Based on avifenc.c, changes here may be mirrored there if relevant.
void PrintEncodingSettings(const avifEncoder* encoder, bool has_gain_map) {
std::string gain_map_str;
if (has_gain_map) {
gain_map_str = ", gain map quality [" +
std::to_string(encoder->qualityGainMap) + " (" +
QualityLevelString(encoder->qualityGainMap) + ")]";
}
std::string tiling_str = "automatic tiling";
if (!encoder->autoTiling) {
tiling_str = "tileRowsLog2 [" + std::to_string(encoder->tileRowsLog2) +
"], tileColsLog2 [" + std::to_string(encoder->tileColsLog2) +
"]";
}
std::cout << "Encoding AVIF with settings: codec '"
<< avifCodecName(encoder->codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE)
<< "' speed ['" << encoder->speed << "'], color quality ['"
<< encoder->quality << "' (" << QualityLevelString(encoder->quality)
<< ")], alpha quality ['" << encoder->qualityAlpha << "' ("
<< QualityLevelString(encoder->qualityAlpha) << ")]" << gain_map_str
<< ", " << tiling_str << ", " << encoder->maxThreads
<< " worker thread(s), please wait...\n";
}
} // namespace
avifResult WriteAvif(const avifImage* image, avifEncoder* encoder,
const std::string& output_filename) {
avifRWData encoded = AVIF_DATA_EMPTY;
std::cout << "AVIF to be written:\n";
avifImageDump(image,
/*gridCols=*/1,
/*gridRows=*/1, AVIF_PROGRESSIVE_STATE_UNAVAILABLE);
PrintEncodingSettings(encoder, image->gainMap != nullptr);
avifResult result = avifEncoderWrite(encoder, image, &encoded);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to encode image: " << avifResultToString(result)
<< " (" << encoder->diag.error << ")\n";
return result;
}
std::ofstream f(output_filename, std::ios::binary);
f.write(reinterpret_cast<char*>(encoded.data), encoded.size);
avifRWDataFree(&encoded);
if (f.fail()) {
std::cerr << "Failed to write image " << output_filename << ": "
<< std::strerror(errno) << "\n";
return AVIF_RESULT_IO_ERROR;
}
std::cout << "Wrote AVIF: " << output_filename << "\n";
return AVIF_RESULT_OK;
}
avifResult WriteAvifGrid(const avifImage* image, int grid_cols, int grid_rows,
avifEncoder* encoder, const std::string& filename) {
if (grid_cols == 1 && grid_rows == 1) {
return WriteAvif(image, encoder, filename);
}
const uint32_t grid_cell_count = grid_cols * grid_rows;
std::cout << "Preparing to encode a " << grid_cols << "x" << grid_rows
<< " grid (" << grid_cell_count << " cells)...\n";
std::vector<avifImage*> grid_cells_ptrs(grid_cell_count);
if (!avifImageSplitGrid(image, grid_cols, grid_rows,
grid_cells_ptrs.data())) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
// Take ownership of the pointers returned by avifImageSplitGrid.
std::vector<ImagePtr> grid_cells(grid_cell_count);
for (uint32_t i = 0; i < grid_cell_count; i++) {
grid_cells[i].reset(grid_cells_ptrs[i]);
}
avifRWData encoded = AVIF_DATA_EMPTY;
std::cout << "AVIF to be written:\n";
avifImageDump(grid_cells_ptrs[0], grid_cols, grid_rows,
AVIF_PROGRESSIVE_STATE_UNAVAILABLE);
PrintEncodingSettings(encoder, image->gainMap != nullptr);
avifResult result = avifEncoderAddImageGrid(encoder, grid_cols, grid_rows,
grid_cells_ptrs.data(),
AVIF_ADD_IMAGE_FLAG_SINGLE);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to encode image grid: " << avifResultToString(result)
<< " (" << encoder->diag.error << ")\n";
return result;
}
result = avifEncoderFinish(encoder, &encoded);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to finish encoding image grid: "
<< avifResultToString(result) << " (" << encoder->diag.error
<< ")\n";
return result;
}
std::ofstream f(filename, std::ios::binary);
f.write(reinterpret_cast<char*>(encoded.data), encoded.size);
avifRWDataFree(&encoded);
if (f.fail()) {
std::cerr << "Failed to write image " << filename << ": "
<< std::strerror(errno) << "\n";
return AVIF_RESULT_IO_ERROR;
}
std::cout << "Wrote AVIF: " << filename << "\n";
return AVIF_RESULT_OK;
}
namespace {
std::string PixelFormatToString(avifPixelFormat format) {
switch (format) {
case AVIF_PIXEL_FORMAT_YUV444:
return "4:4:4";
case AVIF_PIXEL_FORMAT_YUV422:
return "4:2:2";
case AVIF_PIXEL_FORMAT_YUV420:
return "4:2:0";
case AVIF_PIXEL_FORMAT_YUV400:
return "4:0:0";
default:
return "unknown";
}
}
} // namespace
avifResult ReadImage(avifImage* image, const std::string& input_filename,
avifPixelFormat requested_format, uint32_t requested_depth,
bool ignore_profile, bool ignore_exif, bool ignore_xmp,
bool ignore_alpha, bool ignore_gain_map, int jobs) {
avifAppFileFormat input_format = avifGuessFileFormat(input_filename.c_str());
if (input_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
std::cerr << "Cannot determine input format: " << input_filename;
return AVIF_RESULT_INVALID_ARGUMENT;
} else if (input_format == AVIF_APP_FILE_FORMAT_AVIF) {
DecoderPtr decoder(avifDecoderCreate());
if (decoder == nullptr) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
decoder->maxThreads = jobs;
if (ignore_alpha) {
decoder->imageContentToDecode &= ~AVIF_IMAGE_CONTENT_ALPHA;
}
if (!ignore_gain_map) {
decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP;
}
decoder->ignoreICC = ignore_profile;
decoder->ignoreExif = ignore_exif;
decoder->ignoreXMP = ignore_xmp;
avifResult result = ReadAvif(decoder.get(), input_filename);
if (result != AVIF_RESULT_OK) {
return result;
}
const avifColorPrimaries in_primaries = image->colorPrimaries;
const avifTransferCharacteristics in_transfer =
image->transferCharacteristics;
const avifMatrixCoefficients in_matrix = image->matrixCoefficients;
result = avifImageCopy(image, decoder->image, AVIF_PLANES_ALL);
if (result != AVIF_RESULT_OK) {
return result;
}
if (in_primaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
in_transfer != AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED ||
in_matrix != AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
image->colorPrimaries = in_primaries;
image->transferCharacteristics = in_transfer;
image->matrixCoefficients = in_matrix;
}
// Attempt to honor the requested format if it was explicitly asked for and
// it differs from the source.
if (requested_format != AVIF_PIXEL_FORMAT_NONE &&
requested_format != image->yuvFormat) {
if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444 &&
requested_format == AVIF_PIXEL_FORMAT_YUV420) {
avifResult scale_res = DownsampleYUV444To420(image);
if (scale_res != AVIF_RESULT_OK) {
return scale_res;
}
} else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
requested_format == AVIF_PIXEL_FORMAT_YUV444) {
avifResult scale_res = UpsampleYUV420To444(image);
if (scale_res != AVIF_RESULT_OK) {
return scale_res;
}
} else {
std::cerr << "Warning: converting from "
<< PixelFormatToString(image->yuvFormat) << " to "
<< PixelFormatToString(requested_format)
<< " is not supported. Leaving image as "
<< PixelFormatToString(image->yuvFormat) << "\n";
}
}
} else {
const avifAppFileFormat file_format = avifReadImage(
input_filename.c_str(), AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
requested_format, static_cast<int>(requested_depth),
AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC, ignore_profile, ignore_exif,
ignore_xmp, ignore_alpha, ignore_gain_map,
AVIF_DEFAULT_IMAGE_SIZE_LIMIT, image,
/*outDepth=*/nullptr,
/*sourceTiming=*/nullptr, /*frameIter=*/nullptr);
if (file_format == AVIF_APP_FILE_FORMAT_UNKNOWN) {
std::cout << "Failed to decode image: " << input_filename;
return AVIF_RESULT_INVALID_ARGUMENT;
}
// Assume sRGB by default.
if (image->icc.size == 0 &&
image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED &&
image->transferCharacteristics == AVIF_COLOR_PRIMARIES_UNSPECIFIED) {
image->colorPrimaries = AVIF_COLOR_PRIMARIES_SRGB;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
}
if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
// Explicitly set the default matrix coefficient, see
// avifCalcYUVCoefficients().
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
}
}
return AVIF_RESULT_OK;
}
avifResult ReadAvif(avifDecoder* decoder, const std::string& input_filename) {
avifResult result = avifDecoderSetIOFile(decoder, input_filename.c_str());
if (result != AVIF_RESULT_OK) {
std::cerr << "Cannot open file for read: " << input_filename << "\n";
return result;
}
result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to parse image: " << avifResultToString(result) << " ("
<< decoder->diag.error << ")\n";
return result;
}
result = avifDecoderNextImage(decoder);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to decode image: " << avifResultToString(result)
<< " (" << decoder->diag.error << ")\n";
return result;
}
if (decoder->ignoreICC) {
assert(decoder->image->icc.size == 0);
if (decoder->image->gainMap) {
assert(decoder->image->gainMap->altICC.size == 0);
}
}
assert((decoder->imageContentToDecode & AVIF_IMAGE_CONTENT_ALPHA) != 0 ||
(decoder->image->alphaPlane == nullptr &&
decoder->image->alphaRowBytes == 0));
return AVIF_RESULT_OK;
}
} // namespace avif