File: combine_command.cc

package info (click to toggle)
libavif 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 21,488 kB
  • sloc: ansic: 30,721; cpp: 14,588; xml: 1,507; sh: 1,258; java: 307; makefile: 51
file content (200 lines) | stat: -rw-r--r-- 8,281 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include "combine_command.h"

#include <cmath>

#include "avif/avif_cxx.h"
#include "imageio.h"

namespace avif {

CombineCommand::CombineCommand()
    : ProgramCommand("combine",
                     "Create an AVIF image with a gain map from a base image "
                     "and an alternate image") {
  argparse_.add_argument(arg_base_filename_, "base_image")
      .help(
          "The base image, that will be shown by viewers that don't support "
          "gain maps");
  argparse_.add_argument(arg_alternate_filename_, "alternate_image")
      .help("The alternate image, the result of fully applying the gain map");
  argparse_.add_argument(arg_output_filename_, "output_image.avif");
  argparse_.add_argument(arg_downscaling_, "--downscaling")
      .help("Downscaling factor for the gain map")
      .default_value("1");
  argparse_.add_argument(arg_gain_map_quality_, "--qgain-map")
      .help("Quality for the gain map (0-100, where 100 is lossless)")
      .default_value("60");
  argparse_.add_argument(arg_gain_map_depth_, "--depth-gain-map")
      .choices({"8", "10", "12"})
      .help("Output depth for the gain map")
      .default_value("8");
  argparse_
      .add_argument<int, PixelFormatConverter>(arg_gain_map_pixel_format_,
                                               "--yuv-gain-map")
      .choices({"444", "422", "420", "400"})
      .help("Output format for the gain map")
      .default_value("444");
  argparse_.add_argument(arg_max_headroom_, "--max-headroom")
      .help(
          "Maximum value for the base image HDR headroom and alternate "
          "image HDR headroom. Overrides the default headroom values computed "
          "from the image's content if they are larger than this maximum. Use "
          "0 for no maximum. "
          "E.g. assuming one of the two images is SDR and the "
          "other is HDR, the full HDR image (i.e. without tone mapping to SDR "
          "using the gain map) will be shown for displays with at least this "
          "amount of HDR headroom.")
      .default_value("4.0");
  argparse_
      .add_argument<CicpValues, CicpConverter>(arg_base_cicp_, "--cicp-base")
      .help(
          "Set or override the CICP values for the base image, expressed as "
          "P/T/M where P = color primaries, T = transfer characteristics, "
          "M = matrix coefficients.");
  argparse_
      .add_argument<CicpValues, CicpConverter>(arg_alternate_cicp_,
                                               "--cicp-alternate")
      .help(
          "Set or override the CICP values for the alternate image, expressed "
          "as P/T/M  where P = color primaries, T = transfer characteristics, "
          "M = matrix coefficients.");
  argparse_
      .add_argument<avifContentLightLevelInformationBox, ClliConverter>(
          arg_base_clli_, "--clli-base")
      .help(
          "Override content light level information of the base image, "
          "expressed as:  MaxCLL,MaxPALL.");
  argparse_
      .add_argument<avifContentLightLevelInformationBox, ClliConverter>(
          arg_alternate_clli_, "--clli-alternate")
      .help(
          "Override content light level information of the alternate image, "
          "expressed as:  MaxCLL,MaxPALL.");
  arg_image_encode_.Init(argparse_, /*can_have_alpha=*/true);
  arg_image_read_.Init(argparse_);
}

avifResult CombineCommand::Run() {
  const avifPixelFormat pixel_format =
      static_cast<avifPixelFormat>(arg_image_read_.pixel_format.value());
  const avifPixelFormat gain_map_pixel_format =
      static_cast<avifPixelFormat>(arg_gain_map_pixel_format_.value());

  ImagePtr base_image(avifImageCreateEmpty());
  ImagePtr alternate_image(avifImageCreateEmpty());
  if (base_image == nullptr || alternate_image == nullptr) {
    return AVIF_RESULT_OUT_OF_MEMORY;
  }
  avifResult result =
      ReadImage(base_image.get(), arg_base_filename_, pixel_format,
                arg_image_read_.depth, arg_image_read_.ignore_profile);
  if (result != AVIF_RESULT_OK) {
    std::cout << "Failed to read base image: " << avifResultToString(result)
              << "\n";
    return result;
  }
  if (arg_base_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
    base_image->colorPrimaries = arg_base_cicp_.value().color_primaries;
    base_image->transferCharacteristics =
        arg_base_cicp_.value().transfer_characteristics;
    base_image->matrixCoefficients = arg_base_cicp_.value().matrix_coefficients;
  }

  result =
      ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
                arg_image_read_.depth, arg_image_read_.ignore_profile);
  if (result != AVIF_RESULT_OK) {
    std::cout << "Failed to read alternate image: "
              << avifResultToString(result) << "\n";
    return result;
  }
  if (arg_alternate_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
    alternate_image->colorPrimaries =
        arg_alternate_cicp_.value().color_primaries;
    alternate_image->transferCharacteristics =
        arg_alternate_cicp_.value().transfer_characteristics;
    alternate_image->matrixCoefficients =
        arg_alternate_cicp_.value().matrix_coefficients;
  }

  const uint32_t downscaling = std::max<int>(1, arg_downscaling_);
  const uint32_t rounding = downscaling / 2;
  const uint32_t gain_map_width =
      std::max((base_image->width + rounding) / downscaling, 1u);
  const uint32_t gain_map_height =
      std::max((base_image->height + rounding) / downscaling, 1u);
  std::cout << "Creating a gain map of size " << gain_map_width << " x "
            << gain_map_height << "\n";

  base_image->gainMap = avifGainMapCreate();
  base_image->gainMap->image =
      avifImageCreate(gain_map_width, gain_map_height, arg_gain_map_depth_,
                      gain_map_pixel_format);
  if (base_image->gainMap->image == nullptr) {
    return AVIF_RESULT_OUT_OF_MEMORY;
  }
  avifDiagnostics diag;
  result = avifImageComputeGainMap(base_image.get(), alternate_image.get(),
                                   base_image->gainMap, &diag);
  if (result != AVIF_RESULT_OK) {
    std::cout << "Failed to compute gain map: " << avifResultToString(result)
              << " (" << diag.error << ")\n";
    return result;
  }

  if (arg_max_headroom_.value() > 0) {
    if (arg_max_headroom_.value() * base_image->gainMap->baseHdrHeadroom.d <
        base_image->gainMap->baseHdrHeadroom.n) {
      if (!avifDoubleToUnsignedFraction(
              arg_max_headroom_.value(),
              &base_image->gainMap->baseHdrHeadroom)) {
        std::cout << "Unable to express " << arg_max_headroom_.value()
                  << " as a fraction";
        return AVIF_RESULT_INVALID_ARGUMENT;
      }
    }
    if (arg_max_headroom_.value() *
            base_image->gainMap->alternateHdrHeadroom.d <
        base_image->gainMap->alternateHdrHeadroom.n) {
      if (!avifDoubleToUnsignedFraction(
              arg_max_headroom_.value(),
              &base_image->gainMap->alternateHdrHeadroom)) {
        std::cout << "Unable to express " << arg_max_headroom_.value()
                  << " as a fraction";
        return AVIF_RESULT_INVALID_ARGUMENT;
      }
    }
  }

  if (arg_base_clli_.provenance() == argparse::Provenance::SPECIFIED) {
    base_image->clli = arg_base_clli_.value();
  }
  if (arg_alternate_clli_.provenance() == argparse::Provenance::SPECIFIED) {
    base_image->gainMap->altCLLI = arg_alternate_clli_.value();
  }

  EncoderPtr encoder(avifEncoderCreate());
  if (encoder == nullptr) {
    return AVIF_RESULT_OUT_OF_MEMORY;
  }
  encoder->quality = arg_image_encode_.quality;
  encoder->qualityAlpha = arg_image_encode_.quality_alpha;
  encoder->qualityGainMap = arg_gain_map_quality_;
  encoder->speed = arg_image_encode_.speed;
  result =
      WriteAvifGrid(base_image.get(), arg_image_encode_.grid.value().grid_cols,
                    arg_image_encode_.grid.value().grid_rows, encoder.get(),
                    arg_output_filename_);
  if (result != AVIF_RESULT_OK) {
    std::cout << "Failed to encode image: " << avifResultToString(result)
              << " (" << encoder->diag.error << ")\n";
    return result;
  }

  return AVIF_RESULT_OK;
}

}  // namespace avif