diff --git a/winpr/include/winpr/image.h b/winpr/include/winpr/image.h index 27c90f15d..cf33c5f5d 100644 --- a/winpr/include/winpr/image.h +++ b/winpr/include/winpr/image.h @@ -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" { @@ -104,7 +112,7 @@ extern "C" WINPR_API BOOL winpr_image_format_is_supported(UINT32 format); 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(const wImage* imageA, const wImage* imageB, UINT32 flags); #ifdef __cplusplus } diff --git a/winpr/libwinpr/utils/image.c b/winpr/libwinpr/utils/image.c index e99cf9e03..bb9584e8b 100644 --- a/winpr/libwinpr/utils/image.c +++ b/winpr/libwinpr/utils/image.c @@ -925,37 +925,131 @@ BOOL winpr_image_format_is_supported(UINT32 format) } } -BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB) +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(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) diff --git a/winpr/libwinpr/utils/test/TestImage.c b/winpr/libwinpr/utils/test/TestImage.c index 0f06d3e69..38519d561 100644 --- a/winpr/libwinpr/utils/test/TestImage.c +++ b/winpr/libwinpr/utils/test/TestImage.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,6 +8,13 @@ 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(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; @@ -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__); @@ -134,13 +142,17 @@ static BOOL test_read_write_compare(const char* tname, const char* tdst, UINT32 goto fail; } - if (!winpr_image_equal(bmp1, bmp2)) + if (!winpr_image_equal(bmp1, bmp2, + WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA | + WINPR_IMAGE_CMP_FUZZY)) { fprintf(stderr, "[%s] winpr_image_eqal failed bmp1 bmp2", __func__); goto fail; } - rc = winpr_image_equal(bmp3, bmp2); + rc = winpr_image_equal(bmp3, bmp2, + WINPR_IMAGE_CMP_IGNORE_DEPTH | WINPR_IMAGE_CMP_IGNORE_ALPHA | + WINPR_IMAGE_CMP_FUZZY); if (!rc) fprintf(stderr, "[%s] winpr_image_eqal failed bmp3 bmp2", __func__); fail: