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
|
#include "cgen_color.h"
#include <cairo.h>
#include <iostream>
#include <filesystem>
#include <numbers>
#include <optional>
using namespace std::literals;
// Compare two pngs, save the diff
// Return
// 3: file error
// 2: diff size (we also create diff file named '*_diff*')
// 1: diff (we also create diff file named '*_diff*')
// 0: same
int main(int argc, char** argv) {
if (argc < 3 || argc > 4) {
std::cerr << "Usage: " << std::filesystem::path(argv[0]).filename() << " new.png canonical.png [nofont-new.png]" << '\n';
return 3;
}
// std::cerr << "doing " << argv[1] << '\n';
auto s1 = cairo_image_surface_create_from_png(argv[1]),
s2 = cairo_image_surface_create_from_png(argv[2]),
s3 = argc >= 4 ? cairo_image_surface_create_from_png(argv[3]) : nullptr;
if (cairo_surface_status(s1) + cairo_surface_status(s2) + (s3 ? cairo_surface_status(s3) : 0))
return 3;
// std::cerr << "succ reading\n";
const auto w1 = cairo_image_surface_get_width(s1), h1 = cairo_image_surface_get_height(s1),
w2 = cairo_image_surface_get_width(s2), h2 = cairo_image_surface_get_height(s2);
auto f1 [[maybe_unused]] = cairo_image_surface_get_format(s1);
_ASSERT(CAIRO_FORMAT_RGB24 == f1 || CAIRO_FORMAT_ARGB32 == f1); //we ignore the 'a' channel anyway
if (s3) {
auto cr = cairo_create(s3);
cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
cairo_set_source_surface(cr, s1, 0, 0);
cairo_paint(cr);
const bool save_temps = getenv("PNGDIFF_SAVE_TEMPS") && getenv("PNGDIFF_SAVE_TEMPS")[0] && getenv("PNGDIFF_SAVE_TEMPS") != "0"sv;
if (save_temps)
cairo_surface_write_to_png(s3, (argv[3] + "_maskdiff.png"s).data());
const auto w3 = cairo_image_surface_get_width(s3), h3 = cairo_image_surface_get_height(s3);
cairo_surface_flush(s3);
auto p3 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s3));
// Need an alpha channel for the mask.
// NB: cairo_fill operates on surfaces with the same stride, hence A8 is not enough
auto mask = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w3, h3);
cr = cairo_create(mask);
cairo_set_source_rgba(cr, 1, 1, 1, 1);
const double r = [] {
try { return std::stod(getenv("PNGDIFF_NOFONT_MASK_RADIUS")); }
catch (...) { return 2.; }
}();
for (auto u = 0; u < h3; ++u)
for (auto v = 0; v < w3; ++v)
if (p3[u*w3+v] & 0xffffff) {
cairo_arc(cr, v, u, r, 0, 2 * std::numbers::pi);
cairo_fill(cr);
}
if (save_temps)
cairo_surface_write_to_png(mask, (argv[3] + "_mask_inflated.png"s).data());
cr = cairo_create(s2);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cr, s1, 0, 0);
cairo_mask_surface(cr, mask, 0, 0);
cairo_fill(cr);
if (save_temps)
cairo_surface_write_to_png(s2, (argv[2] + "_masked.png"s).data());
}
cairo_surface_flush(s1);
cairo_surface_flush(s2);
auto p1 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s1));
auto p2 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s2));
auto luma_diff = [](ColorType c1, ColorType c2) -> std::optional<bool> { // if not equal then whether outside the allowed Luma difference
if (c1 == c2)
return {};
auto luma = [](ColorType const& c) { return .299 * c.r + .587 * c.g + .114 * c.b; };
static const auto allowed = [] {
try { return std::abs(std::stod(getenv("PNGDIFF_LUMA_DIFF"))); }
catch (...) { return .0; }
}();
return !allowed || std::abs(luma(c1) - luma(c2)) > allowed;
};
auto&& [changed, count] = [&] {
bool changed = false;
int count = 0;
for (auto u = 0; u < std::min(h1, h2); ++u)
for (auto v = 0; v < std::min(w1, w2); ++v)
if (auto diff = luma_diff(ColorType(p1[u*w1+v]), ColorType(p2[u*w2+v]))) {
changed = true;
if (*diff)
++count;
static const auto red = ColorType(255, 0, 0, 0).ConvertToUnsigned(), light = ColorType(255, 0, 0, 0).Lighter(.5).ConvertToUnsigned();
p1[u*w1+v] = *diff ? red : light;
} else
p1[u*w1+v] = ColorType(p1[u*w1+v]).Lighter(0.85).ConvertToUnsigned(); //quite transparent
return std::make_tuple(changed, count);
}();
if (changed) {
cairo_surface_mark_dirty(s1);
// I'm lazy for a regex
std::string df = argv[1];
auto dot = df.rfind('.');
if (dot == df.npos)
dot = df.size();
// df = df.substr(0, dot) + "_diff" + df.substr(dot);
df.insert(dot, "_diff");
// std::cerr << "df " << df << '\n';
cairo_surface_write_to_png(s1, df.c_str());
}
cairo_surface_destroy(s1);
cairo_surface_destroy(s2);
if (w1 != w2 || h1 != h2)
return 2;
return count > 0;
}
|