Skip to content

Commit

Permalink
[winpr,image] use fuzzy compare
Browse files Browse the repository at this point in the history
* Add winpr_image_equal_ex to allow comparison of lozzy compressed
  formats, ignoring color depth and alpha
* Adjust tests to utilize winpr_image_equal_ex
  • Loading branch information
akallabeth committed Feb 14, 2024
1 parent 28a2bf0 commit 770726b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 16 deletions.
9 changes: 9 additions & 0 deletions winpr/include/winpr/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ typedef struct
UINT32 bytesPerPixel;
} wImage;

typedef enum
{
WINPR_IMAGE_CMP_NO_FLAGS = 0,
WINPR_IMAGE_CMP_IGNORE_DEPTH = 1,
WINPR_IMAGE_CMP_IGNORE_ALPHA = 2,
WINPR_IMAGE_CMP_FUZZY = 4
} wImageFlags;

#ifdef __cplusplus
extern "C"
{
Expand Down Expand Up @@ -105,6 +113,7 @@ extern "C"
WINPR_API const char* winpr_image_format_extension(UINT32 format);
WINPR_API const char* winpr_image_format_mime(UINT32 format);
WINPR_API BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB);
WINPR_API BOOL winpr_image_equal_ex(const wImage* imageA, const wImage* imageB, UINT32 flags);

#ifdef __cplusplus
}
Expand Down
129 changes: 114 additions & 15 deletions winpr/libwinpr/utils/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -926,36 +926,135 @@ BOOL winpr_image_format_is_supported(UINT32 format)
}

BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB)
{
return winpr_image_equal_ex(imageA, imageB, 0);
}

static BYTE* convert(const wImage* image, size_t* pstride, UINT32 flags)
{
WINPR_ASSERT(image);
WINPR_ASSERT(pstride);

*pstride = 0;
if (image->bitsPerPixel < 24)
return NULL;

const size_t stride = image->width * 4ull;
BYTE* data = calloc(stride, image->height);
if (data)
{
for (size_t y = 0; y < image->height; y++)
{
const BYTE* srcLine = &image->data[image->scanline * y];
BYTE* dstLine = &data[stride * y];
if (image->bitsPerPixel == 32)
memcpy(dstLine, srcLine, stride);
else
{
for (size_t x = 0; x < image->width; x++)
{
const BYTE* src = &srcLine[image->bytesPerPixel * x];
BYTE* dst = &dstLine[4ull * x];
BYTE b = *src++;
BYTE g = *src++;
BYTE r = *src++;

*dst++ = b;
*dst++ = g;
*dst++ = r;
*dst++ = 0xff;
}
}
}
*pstride = stride;
}
return data;
}

static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags)
{
if (a != b)
{
if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0)
{
const int diff = abs((int)a) - abs((int)b);
/* filter out quantization errors */
if (diff > 6)
return FALSE;
}
else
{
return FALSE;
}
}
return TRUE;
}

static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags)
{
WINPR_ASSERT(pa);
WINPR_ASSERT(pb);

if (!compare_byte_relaxed(*pa++, *pb++, flags))
return FALSE;
if (!compare_byte_relaxed(*pa++, *pb++, flags))
return FALSE;
if (!compare_byte_relaxed(*pa++, *pb++, flags))
return FALSE;
if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0)
{
if (!compare_byte_relaxed(*pa++, *pb++, flags))
return FALSE;
}
return TRUE;
}

BOOL winpr_image_equal_ex(const wImage* imageA, const wImage* imageB, UINT32 flags)
{
if (imageA == imageB)
return TRUE;
if (!imageA || !imageB)
return FALSE;

if (imageA->bitsPerPixel != imageB->bitsPerPixel)
return FALSE;
if (imageA->bytesPerPixel != imageB->bytesPerPixel)
return FALSE;
if (imageA->height != imageB->height)
return FALSE;
if (imageA->width != imageB->width)
return FALSE;
if (imageA->scanline != imageB->scanline)
return FALSE;

const size_t sizeA = 1ull * imageA->scanline * imageA->height;
for (size_t x = 0; x < sizeA; x++)
if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0)
{
const BYTE a = imageA->data[x];
const BYTE b = imageB->data[x];
if (a != b)
if (imageA->bitsPerPixel != imageB->bitsPerPixel)
return FALSE;
if (imageA->bytesPerPixel != imageB->bytesPerPixel)
return FALSE;
}

BOOL rc = FALSE;
size_t astride = 0;
size_t bstride = 0;
BYTE* dataA = convert(imageA, &astride, flags);
BYTE* dataB = convert(imageA, &bstride, flags);
if (dataA && dataB && (astride == bstride))
{
rc = TRUE;
for (size_t y = 0; y < imageA->height; y++)
{
/* filter out quantization errors */
if (abs((int)a - (int)b) > 6)
return FALSE;
const BYTE* lineA = &dataA[astride * y];
const BYTE* lineB = &dataB[bstride * y];

for (size_t x = 0; x < imageA->width; x++)
{
const BYTE* pa = &lineA[x * 4ull];
const BYTE* pb = &lineB[x * 4ull];

if (!compare_pixel(pa, pb, flags))
rc = FALSE;
}
}
}
return TRUE;
free(dataA);
free(dataB);
return rc;
}

const char* winpr_image_format_mime(UINT32 format)
Expand Down
10 changes: 9 additions & 1 deletion winpr/libwinpr/utils/test/TestImage.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
#include <stdio.h>
#include <winpr/string.h>
#include <winpr/assert.h>
#include <winpr/file.h>
#include <winpr/path.h>
#include <winpr/image.h>

static const char test_src_filename[] = TEST_SOURCE_PATH "/rgb";
static const char test_bin_filename[] = TEST_BINARY_PATH "/rgb";

static BOOL test_image_equal(const wImage* imageA, const wImage* imageB)
{
return winpr_image_equal_ex(imageA, imageB,
WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA |
WINPR_IMAGE_CMP_FUZZY);
}

static BOOL test_equal_to(const wImage* bmp, const char* name, UINT32 format)
{
BOOL rc = FALSE;
Expand All @@ -23,7 +31,7 @@ static BOOL test_equal_to(const wImage* bmp, const char* name, UINT32 format)
goto fail;
}

rc = winpr_image_equal(bmp, cmp);
rc = test_image_equal(bmp, cmp);
if (!rc)
fprintf(stderr, "[%s] winpr_image_eqal failed", __func__);

Expand Down
Binary file added winpr/libwinpr/utils/test/rgb.24.bmp
Binary file not shown.
Binary file modified winpr/libwinpr/utils/test/rgb.bmp
Binary file not shown.

0 comments on commit 770726b

Please sign in to comment.