| // Copyright 2023 Google LLC | |
| // SPDX-License-Identifier: BSD-2-Clause | |
| #include <iomanip> | |
| #include <string> | |
| #include <vector> | |
| #include "argparse.hpp" | |
| #include "avif/avif.h" | |
| #include "avifutil.h" | |
| #include "combine_command.h" | |
| #include "convert_command.h" | |
| #include "extractgainmap_command.h" | |
| #include "printmetadata_command.h" | |
| #include "program_command.h" | |
| #include "swapbase_command.h" | |
| #include "tonemap_command.h" | |
| namespace avif { | |
| namespace { | |
| class HelpCommand : public ProgramCommand { | |
| public: | |
| HelpCommand() : ProgramCommand("help", "Print a command's usage") {} | |
| avifResult Run() override { | |
| // Actual implementation is in the main function because it needs access | |
| // to the list of commands. | |
| return AVIF_RESULT_OK; | |
| } | |
| }; | |
| void PrintUsage(const std::vector<std::unique_ptr<ProgramCommand>>& commands) { | |
| std::cout << "\nTool to manipulate AVIF images with HDR gain maps.\n\n"; | |
| std::cout << "usage: avifgainmaputil <command> [options] [arguments...]\n\n"; | |
| std::cout << "Available commands:\n"; | |
| int longest_command_size = 0; | |
| for (const std::unique_ptr<ProgramCommand>& command : commands) { | |
| longest_command_size = std::max(longest_command_size, | |
| static_cast<int>(command->name().size())); | |
| } | |
| for (const std::unique_ptr<ProgramCommand>& command : commands) { | |
| std::cout << " " << std::left << std::setw(longest_command_size) | |
| << command->name() << " " << command->short_description() | |
| << "\n"; | |
| } | |
| std::cout << "\n"; | |
| std::cout << R"(General concepts: | |
| Gain maps allow creating HDR (High Dynamic Range) images that look good on any display, | |
| including SDR (Standard Dynamic Range) displays. Images with gain maps are also backward | |
| compatible with viewers that do not support gain maps. | |
| An image with a gain map consists of a "base image", and a "gain map image". The gain map | |
| image contains information used to "tone map" the base image, in order to adapt it to displays | |
| with different HDR capabilities. Fully applying the gain map results in a different image | |
| called the "alternate image". The gain map can also be applied partially, giving a result in | |
| between the base image and the alternate image. | |
| Typically, either the base image or the alternate image is SDR, and the other one is HDR. | |
| Both images have a target "HDR headroom" that they are meant to be displayed on. | |
| The HDR headroom is the ratio between the maximum brightness of white that the display can | |
| produce, and the standard SDR white brightness. This value is usually expressed in log2. | |
| An SDR display has an HDR headroom of 0. An HDR display with a headroom of 1 can produce white | |
| that is twice as bright as SDR white. | |
| Viewers that support gain maps will show the base image, or the alternate image, or something | |
| in between, depending on the display's current HDR headroom and the target headroom of the base | |
| image and alternate image. Viewers that do not support gain maps will always show the base image. | |
| )"; | |
| avifPrintVersions(); | |
| } | |
| } // namespace | |
| } // namespace avif | |
| int main(int argc, char** argv) { | |
| std::vector<std::unique_ptr<avif::ProgramCommand>> commands; | |
| commands.emplace_back(std::make_unique<avif::HelpCommand>()); | |
| commands.emplace_back(std::make_unique<avif::CombineCommand>()); | |
| commands.emplace_back(std::make_unique<avif::ConvertCommand>()); | |
| commands.emplace_back(std::make_unique<avif::TonemapCommand>()); | |
| commands.emplace_back(std::make_unique<avif::SwapBaseCommand>()); | |
| commands.emplace_back(std::make_unique<avif::ExtractGainMapCommand>()); | |
| commands.emplace_back(std::make_unique<avif::PrintMetadataCommand>()); | |
| if (argc < 2) { | |
| std::cerr << "Command name missing\n"; | |
| avif::PrintUsage(commands); | |
| return 1; | |
| } | |
| const std::string command_name(argv[1]); | |
| if (command_name == "help") { | |
| if (argc >= 3) { | |
| const std::string sub_command_name(argv[2]); | |
| for (const auto& command : commands) { | |
| if (command->name() == sub_command_name) { | |
| command->PrintUsage(); | |
| return 0; | |
| } | |
| } | |
| std::cerr << "Unknown command " << sub_command_name << "\n"; | |
| avif::PrintUsage(commands); | |
| return 1; | |
| } else { | |
| avif::PrintUsage(commands); | |
| return 0; | |
| } | |
| } | |
| for (const auto& command : commands) { | |
| if (command->name() == command_name) { | |
| try { | |
| avifResult result = command->ParseArgs(argc - 1, argv + 1); | |
| if (result == AVIF_RESULT_OK) { | |
| result = command->Run(); | |
| } | |
| if (result == AVIF_RESULT_INVALID_ARGUMENT) { | |
| command->PrintUsage(); | |
| } | |
| return (result == AVIF_RESULT_OK) ? 0 : 1; | |
| } catch (const std::invalid_argument& e) { | |
| std::cerr << e.what() << "\n\n"; | |
| command->PrintUsage(); | |
| return 1; | |
| } | |
| } | |
| } | |
| std::cerr << "Unknown command " << command_name << "\n"; | |
| avif::PrintUsage(commands); | |
| return 1; | |
| } |