gtk/testsuite/reftests/reftest-compare.c
Benjamin Otte e268041a42 reftest-compare: Respect color states when diffing
Always pick the color state from texture1 and download the data and
generate the diff in that color state.

That now means the order of the 2 arguments matters.
2024-08-29 20:34:23 +02:00

317 lines
11 KiB
C

/*
* Copyright (C) 2011,2021 Red Hat Inc.
*
* Author:
* Benjamin Otte <otte@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "reftest-compare.h"
static gboolean
memory_format_is_high_depth (GdkMemoryFormat format)
{
switch (format)
{
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_A8B8G8R8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_B8G8R8X8:
case GDK_MEMORY_X8R8G8B8:
case GDK_MEMORY_R8G8B8X8:
case GDK_MEMORY_X8B8G8R8:
case GDK_MEMORY_G8:
case GDK_MEMORY_G8A8:
case GDK_MEMORY_G8A8_PREMULTIPLIED:
case GDK_MEMORY_A8:
return FALSE;
case GDK_MEMORY_R16G16B16:
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16:
case GDK_MEMORY_G16:
case GDK_MEMORY_G16A16:
case GDK_MEMORY_G16A16_PREMULTIPLIED:
case GDK_MEMORY_A16:
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16_FLOAT:
case GDK_MEMORY_A16_FLOAT:
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32A32_FLOAT:
case GDK_MEMORY_A32_FLOAT:
return TRUE;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
return FALSE;
}
}
/* Compares two GDK_MEMORY_DEFAULT buffers, returning NULL if the
* buffers are equal or a surface containing a diff between the two
* surfaces.
*
* This function is originally from cairo:test/buffer-diff.c.
* Copyright © 2004 Richard D. Worth
*/
static GdkTexture *
buffer_diff_u8 (GdkColorState *color_state,
const guchar *buf_a,
int stride_a,
const guchar *buf_b,
int stride_b,
int width,
int height)
{
int x, y;
guchar *buf_diff = NULL;
int stride_diff = 0;
GdkTexture *diff = NULL;
for (y = 0; y < height; y++)
{
const guint32 *row_a = (const guint32 *) (buf_a + y * stride_a);
const guint32 *row_b = (const guint32 *) (buf_b + y * stride_b);
guint32 *row = (guint32 *) (buf_diff + y * stride_diff);
for (x = 0; x < width; x++)
{
int channel;
guint32 diff_pixel = 0;
/* check if the pixels are the same */
if (row_a[x] == row_b[x])
continue;
/* even if they're not literally the same, fully-transparent
* pixels are effectively the same regardless of colour */
if ((row_a[x] & 0xff000000) == 0 && (row_b[x] & 0xff000000) == 0)
continue;
if (diff == NULL)
{
GdkMemoryTextureBuilder *builder;
GBytes *bytes;
stride_diff = 4 * width;
buf_diff = g_malloc0_n (stride_diff, height);
bytes = g_bytes_new_take (buf_diff, stride_diff * height);
builder = gdk_memory_texture_builder_new ();
gdk_memory_texture_builder_set_width (builder, width);
gdk_memory_texture_builder_set_height (builder, height);
gdk_memory_texture_builder_set_format (builder, GDK_MEMORY_DEFAULT);
gdk_memory_texture_builder_set_color_state (builder, color_state);
gdk_memory_texture_builder_set_bytes (builder, bytes);
gdk_memory_texture_builder_set_stride (builder, stride_diff);
diff = gdk_memory_texture_builder_build (builder);
g_object_unref (builder);
row = (guint32 *) (buf_diff + y * stride_diff);
}
/* calculate a difference value for all 4 channels */
for (channel = 0; channel < 4; channel++)
{
int value_a = (row_a[x] >> (channel*8)) & 0xff;
int value_b = (row_b[x] >> (channel*8)) & 0xff;
guint channel_diff;
channel_diff = ABS (value_a - value_b);
channel_diff *= 4; /* emphasize */
if (channel_diff)
channel_diff += 128; /* make sure it's visible */
if (channel_diff > 255)
channel_diff = 255;
diff_pixel |= channel_diff << (channel * 8);
}
if ((diff_pixel & 0x00ffffff) == 0)
{
/* alpha only difference, convert to luminance */
guint8 alpha = diff_pixel >> 24;
diff_pixel = alpha * 0x010101;
}
/* make the pixel fully opaque */
diff_pixel |= 0xff000000;
row[x] = diff_pixel;
}
}
return diff;
}
/* Compares two FLOAT buffers, returning NULL if the
* buffers are equal or a surface containing a diff between the two
* surfaces.
*/
static GdkTexture *
buffer_diff_float (GdkColorState *color_state,
const guchar *buf_a,
int stride_a,
const guchar *buf_b,
int stride_b,
int width,
int height)
{
int x, y;
guchar *buf_diff = NULL;
int stride_diff = 0;
GdkTexture *diff = NULL;
for (y = 0; y < height; y++)
{
const float *row_a = (const float *) (buf_a + y * stride_a);
const float *row_b = (const float *) (buf_b + y * stride_b);
float *row = (float *) (buf_diff + y * stride_diff);
for (x = 0; x < width; x++)
{
int channel;
/* check if the pixels are the same */
if (G_APPROX_VALUE (row_a[4 * x + 0], row_b[4 * x + 0], 1. / 255) &&
G_APPROX_VALUE (row_a[4 * x + 1], row_b[4 * x + 1], 1. / 255) &&
G_APPROX_VALUE (row_a[4 * x + 2], row_b[4 * x + 2], 1. / 255) &&
G_APPROX_VALUE (row_a[4 * x + 3], row_b[4 * x + 3], 1. / 255))
continue;
/* even if they're not literally the same, fully-transparent
* pixels are effectively the same regardless of colour */
if (G_APPROX_VALUE (row_a[4 * x + 3], 0.0, 1. / 255) &&
G_APPROX_VALUE (row_b[4 * x + 3], 0.0, 1. / 255))
continue;
if (diff == NULL)
{
GdkMemoryTextureBuilder *builder;
GBytes *bytes;
stride_diff = 4 * width * sizeof (float);
buf_diff = g_malloc0_n (stride_diff, height);
bytes = g_bytes_new_take (buf_diff, stride_diff * height);
builder = gdk_memory_texture_builder_new ();
gdk_memory_texture_builder_set_width (builder, width);
gdk_memory_texture_builder_set_height (builder, height);
gdk_memory_texture_builder_set_format (builder, GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED);
gdk_memory_texture_builder_set_color_state (builder, color_state);
gdk_memory_texture_builder_set_bytes (builder, bytes);
gdk_memory_texture_builder_set_stride (builder, stride_diff);
diff = gdk_memory_texture_builder_build (builder);
g_object_unref (builder);
row = (float *) (buf_diff + y * stride_diff);
}
/* calculate a difference value for all 4 channels */
for (channel = 0; channel < 4; channel++)
{
float value_a = row_a[4 * x + channel];
float value_b = row_b[4 * x + channel];\
float channel_diff;
channel_diff = ABS (value_a - value_b);
channel_diff *= 4; /* emphasize */
if (channel_diff)
channel_diff += 0.5; /* make sure it's visible */
if (channel_diff > 1.0)
channel_diff = 1.0;
row[4 * x + channel] = channel_diff;
}
if (row[4 * x + 0] < 0.5 &&
row[4 * x + 1] < 0.5 &&
row[4 * x + 2] < 0.5)
{
/* alpha only difference, convert to luminance */
row[4 * x + 0] = row[4 * x + 3];
row[4 * x + 1] = row[4 * x + 3];
row[4 * x + 2] = row[4 * x + 3];
}
/* make the pixel fully opaque */
row[4 * x + 3] = 1.0;
}
}
return diff;
}
GdkTexture *
reftest_compare_textures (GdkTexture *texture1,
GdkTexture *texture2)
{
int w, h, stride;
guchar *data1, *data2;
GdkTextureDownloader *downloader;
GdkColorState *color_state;
GdkTexture *diff;
gboolean high_depth;
w = MAX (gdk_texture_get_width (texture1), gdk_texture_get_width (texture2));
h = MAX (gdk_texture_get_height (texture1), gdk_texture_get_height (texture2));
color_state = gdk_texture_get_color_state (texture1);
downloader = gdk_texture_downloader_new (texture1);
gdk_texture_downloader_set_color_state (downloader, color_state);
high_depth = memory_format_is_high_depth (gdk_texture_get_format (texture1)) ||
memory_format_is_high_depth (gdk_texture_get_format (texture1));
if (high_depth)
{
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED);
stride = w * 4 * sizeof (float);
}
else
{
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_DEFAULT);
stride = w * 4 * sizeof (float);
}
data1 = g_malloc_n (stride, h);
gdk_texture_downloader_download_into (downloader, data1, stride);
data2 = g_malloc_n (stride, h);
gdk_texture_downloader_set_texture (downloader, texture2);
gdk_texture_downloader_download_into (downloader, data2, stride);
if (high_depth)
{
diff = buffer_diff_float (color_state,
data1, stride,
data2, stride,
w, h);
}
else
{
diff = buffer_diff_u8 (color_state,
data1, stride,
data2, stride,
w, h);
}
g_free (data1);
g_free (data2);
return diff;
}