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
This commit is contained in:
Nico Weber 2025-01-09 19:25:00 -05:00
parent bd5e5c1a0c
commit b50f3132ef

View file

@ -9,6 +9,8 @@
#include <LibCore/MappedFile.h>
#include <LibCore/MimeData.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
#include <LibGfx/ImageFormats/PNGWriter.h>
#include <LibGfx/ImageFormats/WebPWriter.h>
static ErrorOr<RefPtr<Gfx::Bitmap>> load_image(StringView path)
{
@ -20,10 +22,49 @@ static ErrorOr<RefPtr<Gfx::Bitmap>> load_image(StringView path)
return TRY(decoder->frame(0)).image;
}
static ErrorOr<void> save_image(NonnullRefPtr<Gfx::Bitmap> 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<NonnullRefPtr<Gfx::Bitmap>> make_diff_image(NonnullRefPtr<Gfx::Bitmap> first_image, NonnullRefPtr<Gfx::Bitmap> 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<Gfx::StorageFormat::BGRA8888>(x, y);
auto second_pixel = second_image->get_pixel<Gfx::StorageFormat::BGRA8888>(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<int> 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<int> 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);