From b50f3132ef04953eb4e0701b3d04db0b35594899 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Thu, 9 Jan 2025 19:25:00 -0500 Subject: [PATCH] imgcmp: Add a --write-diff-image option This writes a bitmap that has pixels that are identical between the two input images blended with 0.5 alpha against white, and differing pixels are highlighted in red. Use like so: imgcmp --write-diff-image diff.webp image1.png image2.webp --- Userland/Utilities/imgcmp.cpp | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Userland/Utilities/imgcmp.cpp b/Userland/Utilities/imgcmp.cpp index cb1a9484328..c8b0d5e87b7 100644 --- a/Userland/Utilities/imgcmp.cpp +++ b/Userland/Utilities/imgcmp.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include static ErrorOr> load_image(StringView path) { @@ -20,10 +22,49 @@ static ErrorOr> load_image(StringView path) return TRY(decoder->frame(0)).image; } +static ErrorOr save_image(NonnullRefPtr bitmap, StringView out_path) +{ + if (!out_path.ends_with(".png"sv, CaseSensitivity::CaseInsensitive) && !out_path.ends_with(".webp"sv, CaseSensitivity::CaseInsensitive)) + return Error::from_string_view("can only save to .png and .webp files"sv); + + auto output_stream = TRY(Core::File::open(out_path, Core::File::OpenMode::Write)); + auto buffered_stream = TRY(Core::OutputBufferedFile::create(move(output_stream))); + + if (out_path.ends_with(".png"sv, CaseSensitivity::CaseInsensitive)) + return Gfx::PNGWriter::encode(*buffered_stream, *bitmap); + + VERIFY(out_path.ends_with(".webp"sv, CaseSensitivity::CaseInsensitive)); + return Gfx::WebPWriter::encode(*buffered_stream, *bitmap); +} + +static ErrorOr> make_diff_image(NonnullRefPtr first_image, NonnullRefPtr second_image) +{ + VERIFY(first_image->size() == second_image->size()); + + auto diff_image = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, first_image->size())); + + for (int y = 0; y < first_image->height(); ++y) { + for (int x = 0; x < first_image->width(); ++x) { + auto first_pixel = first_image->get_pixel(x, y); + auto second_pixel = second_image->get_pixel(x, y); + if (first_pixel == second_pixel) { + diff_image->set_pixel(x, y, first_pixel.interpolate(Gfx::Color::White, 0.5f)); + } else { + diff_image->set_pixel(x, y, Gfx::Color::Red); + } + } + } + + return diff_image; +} + ErrorOr serenity_main(Main::Arguments arguments) { Core::ArgsParser args_parser; + StringView write_diff_image_path; + args_parser.add_option(write_diff_image_path, "Write image that highlights differing pixels", "write-diff-image", {}, "FILE"); + StringView first_image_path; args_parser.add_positional_argument(first_image_path, "Path to first input image", "FILE1"); @@ -40,6 +81,11 @@ ErrorOr serenity_main(Main::Arguments arguments) return 1; } + if (!write_diff_image_path.is_empty()) { + auto diff_image = TRY(make_diff_image(*first_image, *second_image)); + TRY(save_image(diff_image, write_diff_image_path)); + } + for (int y = 0; y < first_image->physical_height(); ++y) { for (int x = 0; x < first_image->physical_width(); ++x) { auto first_pixel = first_image->get_pixel(x, y);