Repository: SerenityOS/serenity
File: Userland/Utilities/icc.cpp (C++)

Code before:
    }
    outln("\rlargest difference between neighboring colors: {}, between {} and {}", largest, largest_color1, largest_color2);
    outln("smallest difference between neighboring colors: {}, between {} and {}", smallest, smallest_color1, smallest_color2);
    return {};
}

static ErrorOr<void> convert_color(StringView color, Gfx::ICC::Profile const& profile)
{
    auto split = color.split_view(',');
    if (split.size() != 3 && split.size() != 4)
        return Error::from_string_literal("expected 3 or 4 channels for color convertion");

    if (number_of_components_in_color_space(profile.data_color_space()) != split.size())
        return Error::from_string_literal("unexpected number of color in color string");

    Vector<u8, 4> channels;
    channels.resize(split.size());
    for (auto [i, channel] : enumerate(split)) {
        auto maybe_number = channel.to_number<u8>();
        if (!maybe_number.has_value())
            return Error::from_string_literal("unable to parse color string");
        channels[i] = *maybe_number;
    }

    auto srgb_profile = TRY(Gfx::ICC::sRGB());
    auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { 1, 1 }));
    if (channels.size() == 3) {
        bitmap->set_pixel(0, 0, Color { channels[0], channels[1], channels[2] });
        TRY(srgb_profile->convert_image(bitmap, profile));
        auto out_color = bitmap->get_pixel(0, 0);
        outln("({}, {}, {}) in sRGB is {}", channels[0], channels[1], channels[2], out_color);
    } else {
        auto cmyk_bitmap = TRY(Gfx::CMYKBitmap::create_with_size({ 1, 1 }));
        *cmyk_bitmap->scanline(0) = Gfx::CMYK { channels[0], channels[1], channels[2], channels[3] };
        TRY(srgb_profile->convert_cmyk_image(bitmap, cmyk_bitmap, profile));
        auto out_color = bitmap->get_pixel(0, 0);
        outln("({}, {}, {}, {}) in sRGB is {}", channels[0], channels[1], channels[2], channels[3], out_color);
    }
    return {};
}

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    Core::ArgsParser args_parser;

    StringView path;
    args_parser.add_positional_argument(path, "Path to ICC profile or to image containing ICC profile", "FILE", Core::ArgsParser::Required::No);

    StringView name;
    args_parser.add_option(name, "Name of a built-in profile, such as 'sRGB'", "name", 'n', "NAME");


Review comment (refactor):
There's no need to go through a bitmap:

```
    auto pcs = TRY(profile.to_pcs(channels));
    Vector<u8, 3> rgb;
    TRY(srgb_profile->from_pcs(profile, pcs, rgb));
```

(This works for both rgb and cmyk input.)

"PCS" is for "profile connection space", and it's an abstract space that all profiles can convert to and can convert from.

(Somewhat annoyingly, there are two of them, PCSXYZ and PCSLAB, and which one a profile converts to or from is a property of the profile.)

Code after:
    }
    outln("\rlargest difference between neighboring colors: {}, between {} and {}", largest, largest_color1, largest_color2);
    outln("smallest difference between neighboring colors: {}, between {} and {}", smallest, smallest_color1, smallest_color2);
    return {};
}

static ErrorOr<void> print_color_as_sRGB(StringView color, Gfx::ICC::Profile const& profile)
{
    auto split = color.split_view(',');

    if (number_of_components_in_color_space(profile.data_color_space()) != split.size())
        return Error::from_string_literal("unexpected number of color in color string");

    Vector<u8, 4> channels;
    for (auto [i, channel] : enumerate(split)) {
        auto maybe_number = channel.to_number<u8>();
        if (!maybe_number.has_value())
            return Error::from_string_literal("unable to parse color string");
        channels.append(*maybe_number);
    }

    auto srgb_profile = TRY(Gfx::ICC::sRGB());
    auto pcs = TRY(profile.to_pcs(channels));
    Array<u8, 3> out_color_buffer {};
    TRY(srgb_profile->from_pcs(profile, pcs, out_color_buffer));
    auto out_color = Color { out_color_buffer[0], out_color_buffer[1], out_color_buffer[2] };
    outln("{} in sRGB is {}", channels, out_color);

    return {};
}

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    Core::ArgsParser args_parser;

    StringView path;
    args_parser.add_positional_argument(path, "Path to ICC profile or to image containing ICC profile", "FILE", Core::ArgsParser::Required::No);

    StringView name;
    args_parser.add_option(name, "Name of a built-in profile, such as 'sRGB'", "name", 'n', "NAME");

    StringView dump_out_path;
    args_parser.add_option(dump_out_path, "Dump unmodified ICC profile bytes to this path", "dump-to", 0, "FILE");

    StringView reencode_out_path;
    args_parser.add_option(reencode_out_path, "Reencode ICC profile to this path", "reencode-to", 0, "FILE");

    bool debug_roundtrip = false;
    args_parser.add_option(debug_roundtrip, "Check how many u8 colors roundtrip losslessly through the profile. For debugging.", "debug-roundtrip");

    bool measure = false;