Merge branch 'master' of git://git.cairographics.org/git/cairo

This commit is contained in:
U-JONATHAN-X60S\jonathan 2006-12-15 02:31:13 +01:00
commit 15e8486ef1
68 changed files with 1203 additions and 1142 deletions

View file

@ -28,8 +28,7 @@ libpixman_la_SOURCES = \
fbtrap.c \
fbcompose.c \
renderedge.c \
renderedge.h \
slim_internal.h
renderedge.h
if USE_MMX
noinst_LTLIBRARIES += libpixman-mmx.la

View file

@ -2731,78 +2731,231 @@ static void fbFetch(PicturePtr pict, int x, int y, int width, CARD32 *buffer, CA
#define DIV(a,b) ((((a) < 0) == ((b) < 0)) ? (a) / (b) :\
((a) - (b) + 1 - (((b) < 0) << 1)) / (b))
static CARD32
xRenderColorMultToCard32 (pixman_color_t *c)
typedef struct
{
return
((((uint32_t) c->red * c->alpha) >> 24) << 16) |
((((uint32_t) c->green * c->alpha) >> 24) << 8) |
((((uint32_t) c->blue * c->alpha) >> 24) << 0) |
((((uint32_t) c->alpha ) >> 8) << 24);
CARD32 left_ag;
CARD32 left_rb;
CARD32 right_ag;
CARD32 right_rb;
int32_t left_x;
int32_t right_x;
int32_t width_x;
int32_t stepper;
pixman_gradient_stop_t *stops;
int num_stops;
unsigned int spread;
} GradientWalker;
static void
_gradient_walker_init (GradientWalker *walker,
SourcePictPtr pGradient,
unsigned int spread)
{
walker->num_stops = pGradient->gradient.nstops;
walker->stops = pGradient->gradient.stops;
walker->left_x = 0;
walker->right_x = 0x10000;
walker->width_x = 0; /* will force a reset */
walker->stepper = 0;
walker->left_ag = 0;
walker->left_rb = 0;
walker->right_ag = 0;
walker->right_rb = 0;
walker->spread = spread;
}
static CARD32 gradientPixel(const SourcePictPtr pGradient, xFixed_48_16 pos, unsigned int spread)
static void
_gradient_walker_reset (GradientWalker *walker,
xFixed_32_32 pos)
{
int ipos = (pos * pGradient->gradient.stopRange - 1) >> 16;
int32_t x, left_x, right_x;
pixman_color_t *left_c, *right_c;
int n, count = walker->num_stops;
pixman_gradient_stop_t *stops = walker->stops;
/* calculate the actual offset. */
if (ipos < 0 || ipos >= pGradient->gradient.stopRange)
static const pixman_color_t transparent_black = { 0, 0, 0, 0 };
switch (walker->spread)
{
if (pGradient->type == SourcePictTypeConical || spread == RepeatNormal)
{
ipos = ipos % pGradient->gradient.stopRange;
ipos = ipos < 0 ? pGradient->gradient.stopRange + ipos : ipos;
case RepeatNormal:
x = (int32_t)pos & 0xFFFF;
for (n = 0; n < count; n++)
if (x < stops[n].x)
break;
if (n == 0) {
left_x = stops[count-1].x - 0x10000;
left_c = &stops[count-1].color;
} else {
left_x = stops[n-1].x;
left_c = &stops[n-1].color;
}
else if (spread == RepeatReflect)
{
const int limit = pGradient->gradient.stopRange * 2 - 1;
ipos = ipos % limit;
ipos = ipos < 0 ? limit + ipos : ipos;
ipos = ipos >= pGradient->gradient.stopRange ? limit - ipos : ipos;
if (n == count) {
right_x = stops[0].x + 0x10000;
right_c = &stops[0].color;
} else {
right_x = stops[n].x;
right_c = &stops[n].color;
}
left_x += (pos - x);
right_x += (pos - x);
break;
case RepeatPad:
for (n = 0; n < count; n++)
if (pos < stops[n].x)
break;
if (n == 0) {
left_x = INT_MIN;
left_c = &stops[0].color;
} else {
left_x = stops[n-1].x;
left_c = &stops[n-1].color;
}
else if (spread == RepeatPad)
{
if (ipos < 0)
ipos = 0;
else
ipos = pGradient->gradient.stopRange - 1;
if (n == count) {
right_x = INT_MAX;
right_c = &stops[n-1].color;
} else {
right_x = stops[n].x;
right_c = &stops[n].color;
}
else /* RepeatNone */
break;
case RepeatReflect:
x = (int32_t)pos & 0xFFFF;
if ((int32_t)pos & 0x10000)
x = 0x10000 - x;
for (n = 0; n < count; n++)
if (x < stops[n].x)
break;
if (n == 0) {
left_x = -stops[0].x;
left_c = &stops[0].color;
} else {
left_x = stops[n-1].x;
left_c = &stops[n-1].color;
}
if (n == count) {
right_x = 0x20000 - stops[n-1].x;
right_c = &stops[n-1].color;
} else {
right_x = stops[n].x;
right_c = &stops[n].color;
}
if ((int32_t)pos & 0x10000) {
pixman_color_t *tmp_c;
int32_t tmp_x;
tmp_x = 0x20000 - right_x;
right_x = 0x20000 - left_x;
left_x = tmp_x;
tmp_c = right_c;
right_c = left_c;
left_c = tmp_c;
}
left_x += (pos - x);
right_x += (pos - x);
break;
default: /* RepeatNone */
for (n = 0; n < count; n++)
if (pos < stops[n].x)
break;
if (n == 0)
{
return 0;
left_x = INT_MIN;
right_x = stops[0].x;
left_c = right_c = (pixman_color_t*) &transparent_black;
}
else if (n == count)
{
left_x = stops[n-1].x;
right_x = INT_MAX;
left_c = right_c = (pixman_color_t*) &transparent_black;
}
else
{
left_x = stops[n-1].x;
right_x = stops[n].x;
left_c = &stops[n-1].color;
right_c = &stops[n].color;
}
}
if (pGradient->gradient.colorTableSize)
walker->left_x = left_x;
walker->right_x = right_x;
walker->width_x = right_x - left_x;
walker->left_ag = ((left_c->alpha >> 8) << 16) | (left_c->green >> 8);
walker->left_rb = ((left_c->red & 0xff00) << 8) | (left_c->blue >> 8);
walker->right_ag = ((right_c->alpha >> 8) << 16) | (right_c->green >> 8);
walker->right_rb = ((right_c->red & 0xff00) << 8) | (right_c->blue >> 8);
if ( walker->width_x == 0 ||
( walker->left_ag == walker->right_ag &&
walker->left_rb == walker->right_rb ) )
{
return pGradient->gradient.colorTable[ipos];
walker->width_x = 1;
walker->stepper = 0;
}
else
{
int i;
if (ipos <= pGradient->gradient.stops->x)
return xRenderColorMultToCard32 (&pGradient->gradient.stops->color);
for (i = 1; i < pGradient->gradient.nstops; i++)
{
if (pGradient->gradient.stops[i].x >= ipos)
return PictureGradientColor (&pGradient->gradient.stops[i - 1],
&pGradient->gradient.stops[i],
ipos);
}
return xRenderColorMultToCard32 (&pGradient->gradient.stops[--i].color);
walker->stepper = ((1 << 24) + walker->width_x/2)/walker->width_x;
}
}
#define GRADIENT_WALKER_NEED_RESET(w,x) \
( (x) < (w)->left_x || (x) - (w)->left_x >= (w)->width_x )
/* the following assumes that GRADIENT_WALKER_NEED_RESET(w,x) is FALSE */
static CARD32
_gradient_walker_pixel (GradientWalker *walker,
xFixed_32_32 x)
{
int dist, idist;
uint32_t t1, t2, a, color;
if (GRADIENT_WALKER_NEED_RESET (walker, x))
_gradient_walker_reset (walker, x);
dist = ((int)(x - walker->left_x)*walker->stepper) >> 16;
idist = 256 - dist;
/* combined INTERPOLATE and premultiply */
t1 = walker->left_rb*idist + walker->right_rb*dist;
t1 = (t1 >> 8) & 0xff00ff;
t2 = walker->left_ag*idist + walker->right_ag*dist;
t2 &= 0xff00ff00;
color = t2 & 0xff000000;
a = t2 >> 24;
t1 = t1*a + 0x800080;
t1 = (t1 + ((t1 >> 8) & 0xff00ff)) >> 8;
t2 = (t2 >> 8)*a + 0x800080;
t2 = (t2 + ((t2 >> 8) & 0xff00ff));
return (color | (t1 & 0xff00ff) | (t2 & 0xff00));
}
static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *buffer, CARD32 *mask, CARD32 maskBits)
{
SourcePictPtr pGradient = pict->pSourcePict;
CARD32 *end = buffer + width;
SourcePictPtr pGradient = pict->pSourcePict;
GradientWalker walker;
CARD32 *end = buffer + width;
_gradient_walker_init (&walker, pGradient, pict->repeat);
if (pGradient->type == SourcePictTypeSolidFill) {
register CARD32 color = pGradient->solidFill.color;
@ -2853,20 +3006,29 @@ static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *
{
register CARD32 color;
color = gradientPixel (pGradient, t, pict->repeat);
color = _gradient_walker_pixel( &walker, t );
while (buffer < end)
*buffer++ = color;
}
else
{
while (buffer < end) {
if (!mask || *mask++ & maskBits)
{
*buffer = gradientPixel (pGradient, t, pict->repeat);
}
++buffer;
t += inc;
}
if (!mask) {
while (buffer < end)
{
*buffer = _gradient_walker_pixel (&walker, t);
buffer += 1;
t += inc;
}
} else {
while (buffer < end) {
if (*mask++ & maskBits)
{
*buffer = _gradient_walker_pixel (&walker, t);
}
buffer += 1;
t += inc;
}
}
}
}
else /* projective transformation */
@ -2890,31 +3052,31 @@ static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *
t = ((a * x + b * y) >> 16) + off;
}
color = gradientPixel (pGradient, t, pict->repeat);
color = _gradient_walker_pixel( &walker, t );
while (buffer < end)
*buffer++ = color;
}
else
{
while (buffer < end)
{
if (!mask || *mask++ & maskBits)
{
if (v.vector[2] == 0) {
t = 0;
} else {
xFixed_48_16 x, y;
x = ((xFixed_48_16)v.vector[0] << 16) / v.vector[2];
y = ((xFixed_48_16)v.vector[1] << 16) / v.vector[2];
t = ((a*x + b*y) >> 16) + off;
}
*buffer = gradientPixel(pGradient, t, pict->repeat);
}
++buffer;
v.vector[0] += unit.vector[0];
v.vector[1] += unit.vector[1];
v.vector[2] += unit.vector[2];
}
while (buffer < end)
{
if (!mask || *mask++ & maskBits)
{
if (v.vector[2] == 0) {
t = 0;
} else {
xFixed_48_16 x, y;
x = ((xFixed_48_16)v.vector[0] << 16) / v.vector[2];
y = ((xFixed_48_16)v.vector[1] << 16) / v.vector[2];
t = ((a*x + b*y) >> 16) + off;
}
*buffer = _gradient_walker_pixel (&walker, t);
}
++buffer;
v.vector[0] += unit.vector[0];
v.vector[1] += unit.vector[1];
v.vector[2] += unit.vector[2];
}
}
}
} else {
@ -2951,19 +3113,22 @@ static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *
ry -= pGradient->radial.fy;
while (buffer < end) {
double b, c, det, s;
double b, c, det, s;
if (!mask || *mask++ & maskBits)
{
b = 2*(rx*pGradient->radial.dx + ry*pGradient->radial.dy);
c = -(rx*rx + ry*ry);
det = (b * b) - (4 * pGradient->radial.a * c);
s = (-b + sqrt(det))/(2. * pGradient->radial.a);
*buffer = gradientPixel(pGradient,
(xFixed_48_16)((s*pGradient->radial.m + pGradient->radial.b)*65536),
pict->repeat);
}
++buffer;
if (!mask || *mask++ & maskBits)
{
xFixed_48_16 t;
b = 2*(rx*pGradient->radial.dx + ry*pGradient->radial.dy);
c = -(rx*rx + ry*ry);
det = (b * b) - (4 * pGradient->radial.a * c);
s = (-b + sqrt(det))/(2. * pGradient->radial.a);
t = (xFixed_48_16)((s*pGradient->radial.m + pGradient->radial.b)*65536);
*buffer = _gradient_walker_pixel (&walker, t);
}
++buffer;
rx += cx;
ry += cy;
}
@ -2972,25 +3137,27 @@ static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *
double x, y;
double b, c, det, s;
if (!mask || *mask++ & maskBits)
{
if (rz != 0) {
x = rx/rz;
y = ry/rz;
} else {
x = y = 0.;
}
x -= pGradient->radial.fx;
y -= pGradient->radial.fy;
b = 2*(x*pGradient->radial.dx + y*pGradient->radial.dy);
c = -(x*x + y*y);
det = (b * b) - (4 * pGradient->radial.a * c);
s = (-b + sqrt(det))/(2. * pGradient->radial.a);
*buffer = gradientPixel(pGradient,
(xFixed_48_16)((s*pGradient->radial.m + pGradient->radial.b)*65536),
pict->repeat);
}
++buffer;
if (!mask || *mask++ & maskBits)
{
xFixed_48_16 t;
if (rz != 0) {
x = rx/rz;
y = ry/rz;
} else {
x = y = 0.;
}
x -= pGradient->radial.fx;
y -= pGradient->radial.fy;
b = 2*(x*pGradient->radial.dx + y*pGradient->radial.dy);
c = -(x*x + y*y);
det = (b * b) - (4 * pGradient->radial.a * c);
s = (-b + sqrt(det))/(2. * pGradient->radial.a);
t = (xFixed_48_16)((s*pGradient->radial.m + pGradient->radial.b)*65536);
*buffer = _gradient_walker_pixel (&walker, t);
}
++buffer;
rx += cx;
ry += cy;
rz += cz;
@ -3005,37 +3172,41 @@ static void fbFetchSourcePict(PicturePtr pict, int x, int y, int width, CARD32 *
while (buffer < end) {
double angle;
if (!mask || *mask++ & maskBits)
{
angle = atan2(ry, rx) + a;
if (!mask || *mask++ & maskBits)
{
xFixed_48_16 t;
*buffer = gradientPixel(pGradient, (xFixed_48_16) (angle * (65536. / (2*M_PI))),
pict->repeat);
}
angle = atan2(ry, rx) + a;
t = (xFixed_48_16) (angle * (65536. / (2*M_PI)));
*buffer = _gradient_walker_pixel (&walker, t);
}
++buffer;
rx += cx;
ry += cy;
}
} else {
while (buffer < end) {
double x, y;
double angle;
double angle;
if (!mask || *mask++ & maskBits)
{
if (rz != 0) {
x = rx/rz;
y = ry/rz;
} else {
x = y = 0.;
}
x -= pGradient->conical.center.x/65536.;
y -= pGradient->conical.center.y/65536.;
angle = atan2(y, x) + a;
*buffer = gradientPixel(pGradient, (xFixed_48_16) (angle * (65536. / (2*M_PI))),
pict->repeat);
}
if (!mask || *mask++ & maskBits)
{
xFixed_48_16 t;
if (rz != 0) {
x = rx/rz;
y = ry/rz;
} else {
x = y = 0.;
}
x -= pGradient->conical.center.x/65536.;
y -= pGradient->conical.center.y/65536.;
angle = atan2(y, x) + a;
t = (xFixed_48_16) (angle * (65536. / (2*M_PI)));
*buffer = _gradient_walker_pixel (&walker, t);
}
++buffer;
rx += cx;
ry += cy;

View file

@ -1,4 +1,4 @@
DIST_SUBDIRS=pdiff
SUBDIRS=pdiff .
# Here are all the tests that are run unconditionally
TESTS = \
@ -170,11 +170,9 @@ bitmap-font-ref.png \
bitmap-font-rgb24-ref.png \
bitmap-font-pdf-argb32-ref.png \
caps-joins-ref.png \
caps-joins-ps-argb32-ref.png \
caps-joins-alpha-ref.png \
caps-joins-alpha-svg-ref.png \
caps-sub-paths-ref.png \
caps-sub-paths-ps-argb32-ref.png \
clip-all-ref.png \
clip-fill-rule-ref.png \
clip-fill-rule-rgb24-ref.png \
@ -191,7 +189,6 @@ clip-twice-ref.png \
clip-twice-rgb24-ref.png \
clip-twice-ps-argb32-ref.png \
close-path-ref.png \
close-path-ps-argb32-ref.png \
composite-integer-translate-over-ref.png \
composite-integer-translate-over-repeat-ref.png \
composite-integer-translate-source-ref.png \
@ -203,7 +200,6 @@ dash-caps-joins-ref.png \
dash-caps-joins-ps-argb32-ref.png \
dash-no-dash-ref.png \
dash-offset-negative-ref.png \
dash-offset-negative-ps-argb32-ref.png \
dash-scale-ref.png \
dash-scale-ps-argb32-ref.png \
dash-zero-length-ref.png \
@ -220,7 +216,6 @@ fill-and-stroke-ref.png \
fill-and-stroke-rgb24-ref.png \
fill-and-stroke-ps-argb32-ref.png \
fill-and-stroke-alpha-ref.png \
fill-and-stroke-alpha-svg-ref.png \
fill-and-stroke-alpha-add-ref.png \
fill-degenerate-sort-order-ref.png \
fill-degenerate-sort-order-rgb24-ref.png \
@ -242,34 +237,26 @@ font-matrix-translation-ps-argb32-ref.png \
font-matrix-translation-svg-ref.png \
get-group-target-ref.png \
glyph-cache-pressure-ref.png \
glyph-cache-pressure-ps-argb32-ref.png \
glyph-cache-pressure-svg-ref.png \
gradient-alpha-ref.png \
gradient-alpha-rgb24-ref.png \
infinite-join-ref.png \
infinite-join-ps-argb32-ref.png \
leaky-dash-ref.png \
leaky-polygon-ref.png \
leaky-polygon-ps-argb32-ref.png \
linear-gradient-ref.png \
linear-gradient-svg-ref.png \
line-width-ref.png \
line-width-ps-argb32-ref.png \
line-width-scale-ref.png \
line-width-scale-ps-argb32-ref.png \
long-lines-ref.png \
mask-ref.png \
mask-rgb24-ref.png \
mask-svg-argb32-ref.png \
mask-svg-rgb24-ref.png \
mask-ctm-ref.png \
mask-ctm-rgb24-ref.png \
mask-ctm-svg-argb32-ref.png \
mask-ctm-svg-rgb24-ref.png \
mask-surface-ctm-ref.png \
mask-surface-ctm-rgb24-ref.png \
mask-surface-ctm-svg-argb32-ref.png \
mask-surface-ctm-svg-rgb24-ref.png \
move-to-show-surface-ref.png \
new-sub-path-ref.png \
new-sub-path-rgb24-ref.png \
@ -293,7 +280,6 @@ pixman-rotate-rgb24-ref.png \
push-group-ref.png \
push-group-rgb24-ref.png \
push-group-svg-argb32-ref.png \
push-group-svg-rgb24-ref.png \
random-intersections-ref.png \
random-intersections-rgb24-ref.png \
random-intersections-ps-argb32-ref.png \
@ -307,19 +293,14 @@ scale-source-surface-paint-pdf-argb32-ref.png \
scale-source-surface-paint-svg-argb32-ref.png \
scale-source-surface-paint-svg-rgb24-ref.png \
select-font-face-ref.png \
select-font-face-ps-argb32-ref.png \
select-font-face-svg-ref.png \
self-copy-ref.png \
self-intersecting-ref.png \
self-intersecting-rgb24-ref.png \
set-source-ref.png \
set-source-rgb24-ref.png \
set-source-svg-argb32-ref.png \
set-source-svg-rgb24-ref.png \
show-glyphs-many-ref.png \
show-text-current-point-ref.png \
show-text-current-point-ps-argb32-ref.png \
show-text-current-point-svg-ref.png \
source-clip-ref.png \
source-clip-scale-ref.png \
source-clip-scale-svg-ref.png \
@ -336,12 +317,9 @@ text-pattern-svg-rgb24-ref.png \
text-rotate-ref.png \
text-rotate-rgb24-ref.png \
transforms-ref.png \
transforms-ps-argb32-ref.png \
translate-show-surface-ref.png \
trap-clip-ref.png \
trap-clip-rgb24-ref.png \
trap-clip-svg-argb32-ref.png \
trap-clip-svg-rgb24-ref.png \
unantialiased-shapes-ref.png \
unbounded-operator-ref.png \
unbounded-operator-rgb24-ref.png \
@ -392,6 +370,7 @@ SUPPORT_PROGS =
INCLUDES = \
-D_GNU_SOURCE \
-I$(srcdir) \
-I$(srcdir)/pdiff \
-I$(top_srcdir)/boilerplate \
-I$(top_srcdir)/pixman/src \
-I$(top_srcdir)/src \
@ -407,6 +386,7 @@ libcairotest_la_SOURCES =\
cairo-test.h
LDADD = libcairotest.la \
$(top_builddir)/test/pdiff/libpdiff.la \
$(top_builddir)/boilerplate/libcairoboilerplate.la \
$(top_builddir)/src/libcairo.la

View file

@ -38,6 +38,7 @@
#include "cairo-test.h"
#include "pdiff.h"
#include "buffer-diff.h"
#include "xmalloc.h"
@ -117,17 +118,48 @@ buffer_diff_core (unsigned char *_buf_a,
}
void
buffer_diff (unsigned char *buf_a,
unsigned char *buf_b,
unsigned char *buf_diff,
int width,
int height,
int stride,
buffer_diff_result_t *result)
compare_surfaces (cairo_surface_t *surface_a,
cairo_surface_t *surface_b,
cairo_surface_t *surface_diff,
buffer_diff_result_t *result)
{
buffer_diff_core(buf_a, buf_b, buf_diff,
width, height, stride, 0xffffffff,
result);
/* These default values were taken straight from the
* perceptualdiff program. We'll probably want to tune these as
* necessary. */
double gamma = 2.2;
double luminance = 100.0;
double field_of_view = 45.0;
int discernible_pixels_changed;
/* First, we run cairo's old buffer_diff algorithm which looks for
* pixel-perfect images, (we do this first since the test suite
* runs about 3x slower if we run pdiff_compare first).
*/
buffer_diff_core (cairo_image_surface_get_data (surface_a),
cairo_image_surface_get_data (surface_b),
cairo_image_surface_get_data (surface_diff),
cairo_image_surface_get_width (surface_a),
cairo_image_surface_get_height (surface_a),
cairo_image_surface_get_stride (surface_a),
0xffffffff,
result);
if (result->pixels_changed == 0)
return;
cairo_test_log ("%d pixels differ (with maximum difference of %d) from reference image\n",
result->pixels_changed, result->max_diff);
/* Then, if there are any different pixels, we give the pdiff code
* a crack at the images. If it decides that there are no visually
* discernible differences in any pixels, then we accept this
* result as good enough. */
discernible_pixels_changed = pdiff_compare (surface_a, surface_b,
gamma, luminance, field_of_view);
if (discernible_pixels_changed == 0) {
result->pixels_changed = 0;
cairo_test_log ("But perceptual diff finds no visually discernible difference.\n"
"Accepting result.\n");
}
}
void
@ -304,11 +336,7 @@ image_diff_core (const char *filename_a,
surface_diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width_a, height_a);
buffer_diff (cairo_image_surface_get_data (surface_a),
cairo_image_surface_get_data (surface_b),
cairo_image_surface_get_data (surface_diff),
width_a, height_a, stride_a,
result);
compare_surfaces (surface_a, surface_b, surface_diff, result);
if (result->pixels_changed) {
FILE *png_file;

View file

@ -36,22 +36,19 @@ typedef struct _buffer_diff_result {
unsigned int max_diff;
} buffer_diff_result_t;
/* Compares two image buffers.
/* Compares two image surfaces
*
* Provides number of pixels changed and maximum single-channel
* difference in result.
*
* Also fills in a "diff" buffer intended to visually show where the
* Also fills in a "diff" surface intended to visually show where the
* images differ.
*/
void
buffer_diff (unsigned char *buf_a,
unsigned char *buf_b,
unsigned char *buf_diff,
int width,
int height,
int stride,
buffer_diff_result_t *result);
compare_surfaces (cairo_surface_t *surface_a,
cairo_surface_t *surface_b,
cairo_surface_t *surface_diff,
buffer_diff_result_t *result);
/* Compares two image buffers ignoring the alpha channel.
*

View file

@ -313,6 +313,8 @@ cairo_test_for_target (cairo_test_t *test,
goto UNWIND_CAIRO;
}
cairo_test_log ("Comparing result against reference image: %s\n", ref_name);
if (target->content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED) {
diff_status= image_diff_flattened (png_name, ref_name, diff_name,
dev_offset, dev_offset, 0, 0, &result);
@ -326,13 +328,9 @@ cairo_test_for_target (cairo_test_t *test,
ret = CAIRO_TEST_FAILURE;
goto UNWIND_CAIRO;
}
if (result.pixels_changed) {
cairo_test_log ("%d pixels differ (with maximum difference of %d) from reference image %s\n",
result.pixels_changed, result.max_diff, ref_name);
if (result.max_diff > target->error_tolerance) {
ret = CAIRO_TEST_FAILURE;
goto UNWIND_CAIRO;
}
if (result.pixels_changed && result.max_diff > target->error_tolerance) {
ret = CAIRO_TEST_FAILURE;
goto UNWIND_CAIRO;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -27,9 +27,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
const char png_filename[] = "romedalen.png";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

View file

@ -28,9 +28,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
const char png_filename[] = "romedalen.png";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 965 B

View file

@ -28,9 +28,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
const char png_filename[] = "romedalen.png";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -28,9 +28,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
const char png_filename[] = "romedalen.png";

1
test/pdiff/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
perceptualdiff

View file

@ -1,136 +0,0 @@
/*
Comapre Args
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "CompareArgs.h"
#include "RGBAImage.h"
#include <stdio.h>
static const char* copyright =
"PerceptualDiff version 1.0, Copyright (C) 2006 Yangli Hector Yee\n\
PerceptualDiff comes with ABSOLUTELY NO WARRANTY;\n\
This is free software, and you are welcome\n\
to redistribute it under certain conditions;\n\
See the GPL page for details: http://www.gnu.org/copyleft/gpl.html\n\n";
static const char *usage =
"PeceptualDiff image1.tif image2.tif\n\n\
Compares image1.tif and image2.tif using a perceptually based image metric\n\
Options:\n\
\t-verbose : Turns on verbose mode\n\
\t-fov deg : Field of view in degrees (0.1 to 89.9)\n\
\t-threshold p : #pixels p below which differences are ignored\n\
\t-gamma g : Value to convert rgb into linear space (default 2.2)\n\
\t-luminance l : White luminance (default 100.0 cdm^-2)\n\
\t-output o.ppm : Write difference to the file o.ppm\n\
\n\
\n Note: Input files can also be in the PNG format\
\n";
CompareArgs::CompareArgs()
{
ImgA = NULL;
ImgB = NULL;
ImgDiff = NULL;
Verbose = false;
FieldOfView = 45.0f;
Gamma = 2.2f;
ThresholdPixels = 100;
Luminance = 100.0f;
}
CompareArgs::~CompareArgs()
{
if (ImgA) delete ImgA;
if (ImgB) delete ImgB;
if (ImgDiff) delete ImgDiff;
}
bool CompareArgs::Parse_Args(int argc, char **argv)
{
if (argc < 3) {
ErrorStr = copyright;
ErrorStr += usage;
return false;
}
for (int i = 0; i < argc; i++) {
if (i == 1) {
#if HAVE_LIBTIFF
ImgA = RGBAImage::ReadTiff(argv[1]);
if (!ImgA) {
#endif /* HAVE_LIBTIFF */
ImgA = RGBAImage::ReadPNG(argv[1]);
if (!ImgA)
{
ErrorStr = "FAIL: Cannot open ";
ErrorStr += argv[1];
ErrorStr += "\n";
return false;
}
#if HAVE_LIBTIFF
}
#endif /* HAVE_LIBTIFF */
} else if (i == 2) {
#if HAVE_LIBTIFF
ImgB = RGBAImage::ReadTiff(argv[2]);
if (!ImgB) {
#endif /* HAVE_LIBTIFF */
ImgB = RGBAImage::ReadPNG(argv[2]);
if (!ImgB)
{
ErrorStr = "FAIL: Cannot open ";
ErrorStr += argv[2];
ErrorStr += "\n";
return false;
}
#if HAVE_LIBTIFF
}
#endif /* HAVE_LIBTIFF */
} else {
if (strstr(argv[i], "-fov")) {
if (i + 1 < argc) {
FieldOfView = (float) atof(argv[i + 1]);
}
} else if (strstr(argv[i], "-verbose")) {
Verbose = true;
} else if (strstr(argv[i], "-threshold")) {
if (i + 1 < argc) {
ThresholdPixels = atoi(argv[i + 1]);
}
} else if (strstr(argv[i], "-gamma")) {
if (i + 1 < argc) {
Gamma = (float) atof(argv[i + 1]);
}
}else if (strstr(argv[i], "-luminance")) {
if (i + 1 < argc) {
Luminance = (float) atof(argv[i + 1]);
}
}else if (strstr(argv[i], "-output")) {
if (i + 1 < argc) {
ImgDiff = new RGBAImage(ImgA->Get_Width(), ImgA->Get_Height(), argv[i+1]);
}
}
}
} // i
return true;
}
void CompareArgs::Print_Args()
{
printf("Field of view is %f degrees\n", FieldOfView);
printf("Threshold pixels is %d pixels\n", ThresholdPixels);
printf("The Gamma is %f\n", Gamma);
printf("The Display's luminance is %f candela per meter squared\n", Luminance);
}

View file

@ -1,44 +0,0 @@
/*
Comapre Args
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _COMPAREARGS_H
#define _COMPAREARGS_H
#include <string>
class RGBAImage;
// Args to pass into the comparison function
class CompareArgs
{
public:
CompareArgs();
~CompareArgs();
bool Parse_Args(int argc, char **argv);
void Print_Args();
RGBAImage *ImgA; // Image A
RGBAImage *ImgB; // Image B
RGBAImage *ImgDiff; // Diff image
bool Verbose; // Print lots of text or not
float FieldOfView; // Field of view in degrees
float Gamma; // The gamma to convert to linear color space
float Luminance; // the display's luminance
unsigned int ThresholdPixels; // How many pixels different to ignore
std::string ErrorStr; // Error string
};
#endif

View file

@ -1,87 +0,0 @@
/*
Laplacian Pyramid
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "LPyramid.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
LPyramid::LPyramid(float *image, int width, int height) :
Width(width),
Height(height)
{
// Make the Laplacian pyramid by successively
// copying the earlier levels and blurring them
for (int i=0; i<MAX_PYR_LEVELS; i++) {
if (i == 0) {
Levels[i] = Copy(image);
} else {
Levels[i] = new float[Width * Height];
Convolve(Levels[i], Levels[i - 1]);
}
}
}
LPyramid::~LPyramid()
{
for (int i=0; i<MAX_PYR_LEVELS; i++) {
if (Levels[i]) delete Levels[i];
}
}
float *LPyramid::Copy(float *img)
{
int max = Width * Height;
float *out = new float[max];
for (int i = 0; i < max; i++) out[i] = img[i];
return out;
}
void LPyramid::Convolve(float *a, float *b)
// convolves image b with the filter kernel and stores it in a
{
int y,x,i,j,nx,ny;
const float Kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f};
for (y=0; y<Height; y++) {
for (x=0; x<Width; x++) {
int index = y * Width + x;
a[index] = 0.0f;
for (i=-2; i<=2; i++) {
for (j=-2; j<=2; j++) {
nx=x+i;
ny=y+j;
if (nx<0) nx=-nx;
if (ny<0) ny=-ny;
if (nx>=Width) nx=2*(Width-1)-nx;
if (ny>=Height) ny=2*(Height-1)-ny;
a[index] += Kernel[i+2] * Kernel[j+2] * b[ny * Width + nx];
}
}
}
}
}
float LPyramid::Get_Value(int x, int y, int level)
{
int index = x + y * Width;
int l = level;
if (l > MAX_PYR_LEVELS) l = MAX_PYR_LEVELS;
return Levels[level][index];
}

View file

@ -1,38 +0,0 @@
/*
Laplacian Pyramid
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _LPYRAMID_H
#define _LPYRAMID_H
#define MAX_PYR_LEVELS 8
class LPyramid
{
public:
LPyramid(float *image, int width, int height);
virtual ~LPyramid();
float Get_Value(int x, int y, int level);
protected:
float *Copy(float *img);
void Convolve(float *a, float *b);
// Succesively blurred versions of the original image
float *Levels[MAX_PYR_LEVELS];
int Width;
int Height;
};
#endif // _LPYRAMID_H

View file

@ -1,14 +1,16 @@
EXTRA_PROGRAMS = perceptualdiff
perceptualdiff_SOURCES = \
CompareArgs.cpp \
CompareArgs.h \
LPyramid.cpp \
LPyramid.h \
Metric.cpp \
Metric.h \
PerceptualDiff.cpp \
RGBAImage.cpp \
RGBAImage.h
noinst_LTLIBRARIES = libpdiff.la
libpdiff_la_SOURCES = \
pdiff.h \
lpyramid.c \
lpyramid.h \
pdiff.c
LDADD = $(top_builddir)/src/libcairo.la
perceptualdiff_SOURCES = \
args.c \
args.h \
perceptualdiff.c
INCLUDES = -I$(top_srcdir)/src
LDADD = libpdiff.la $(top_builddir)/src/libcairo.la

View file

@ -1,348 +0,0 @@
/*
Metric
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Metric.h"
#include "CompareArgs.h"
#include "RGBAImage.h"
#include "LPyramid.h"
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265f
#endif
/*
* Given the adaptation luminance, this function returns the
* threshold of visibility in cd per m^2
* TVI means Threshold vs Intensity function
* This version comes from Ward Larson Siggraph 1997
*/
float tvi(float adaptation_luminance)
{
// returns the threshold luminance given the adaptation luminance
// units are candelas per meter squared
float log_a, r, result;
log_a = log10f(adaptation_luminance);
if (log_a < -3.94f) {
r = -2.86f;
} else if (log_a < -1.44f) {
r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f;
} else if (log_a < -0.0184f) {
r = log_a - 0.395f;
} else if (log_a < 1.9f) {
r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f;
} else {
r = log_a - 1.255f;
}
result = powf(10.0f , r);
return result;
}
// computes the contrast sensitivity function (Barten SPIE 1989)
// given the cycles per degree (cpd) and luminance (lum)
float csf(float cpd, float lum)
{
float a, b, result;
a = 440.0f * powf((1.0f + 0.7f / lum), -0.2f);
b = 0.3f * powf((1.0f + 100.0f / lum), 0.15f);
result = a * cpd * expf(-b * cpd) * sqrtf(1.0f + 0.06f * expf(b * cpd));
return result;
}
/*
* Visual Masking Function
* from Daly 1993
*/
float mask(float contrast)
{
float a, b, result;
a = powf(392.498f * contrast, 0.7f);
b = powf(0.0153f * a, 4.0f);
result = powf(1.0f + b, 0.25f);
return result;
}
// convert Adobe RGB (1998) with reference white D65 to XYZ
void AdobeRGBToXYZ(float r, float g, float b, float &x, float &y, float &z)
{
// matrix is from http://www.brucelindbloom.com/
x = r * 0.576700f + g * 0.185556f + b * 0.188212f;
y = r * 0.297361f + g * 0.627355f + b * 0.0752847f;
z = r * 0.0270328f + g * 0.0706879f + b * 0.991248f;
}
void XYZToLAB(float x, float y, float z, float &L, float &A, float &B)
{
static float xw = -1;
static float yw;
static float zw;
// reference white
if (xw < 0) {
AdobeRGBToXYZ(1, 1, 1, xw, yw, zw);
}
const float epsilon = 216.0f / 24389.0f;
const float kappa = 24389.0f / 27.0f;
float f[3];
float r[3];
r[0] = x / xw;
r[1] = y / yw;
r[2] = z / zw;
for (int i = 0; i < 3; i++) {
if (r[i] > epsilon) {
f[i] = powf(r[i], 1.0f / 3.0f);
} else {
f[i] = (kappa * r[i] + 16.0f) / 116.0f;
}
}
L = 116.0f * f[1] - 16.0f;
A = 500.0f * (f[0] - f[1]);
B = 200.0f * (f[1] - f[2]);
}
int Yee_Compare_Images(RGBAImage *image_a,
RGBAImage *image_b,
float gamma,
float luminance,
float field_of_view,
bool verbose);
bool Yee_Compare(CompareArgs &args)
{
if ((args.ImgA->Get_Width() != args.ImgB->Get_Width()) ||
(args.ImgA->Get_Height() != args.ImgB->Get_Height())) {
args.ErrorStr = "Image dimensions do not match\n";
return false;
}
unsigned int i, dim, pixels_failed;
dim = args.ImgA->Get_Width() * args.ImgA->Get_Height();
bool identical = true;
for (i = 0; i < dim; i++) {
if (args.ImgA->Get(i) != args.ImgB->Get(i)) {
identical = false;
break;
}
}
if (identical) {
args.ErrorStr = "Images are binary identical\n";
return true;
}
pixels_failed = Yee_Compare_Images (args.ImgA, args.ImgB,
args.Gamma, args.Luminance,
args.FieldOfView, args.Verbose);
if (pixels_failed < args.ThresholdPixels) {
args.ErrorStr = "Images are perceptually indistinguishable\n";
return true;
}
char different[100];
sprintf(different, "%d pixels are different\n", pixels_failed);
args.ErrorStr = "Images are visibly different\n";
args.ErrorStr += different;
if (args.ImgDiff) {
#if IMAGE_DIFF_CODE_ENABLED
if (args.ImgDiff->WritePPM()) {
args.ErrorStr += "Wrote difference image to ";
args.ErrorStr+= args.ImgDiff->Get_Name();
args.ErrorStr += "\n";
} else {
args.ErrorStr += "Could not write difference image to ";
args.ErrorStr+= args.ImgDiff->Get_Name();
args.ErrorStr += "\n";
}
#endif
args.ErrorStr += "Generation of image \"difference\" is currently disabled\n";
}
return false;
}
int Yee_Compare_Images(RGBAImage *image_a,
RGBAImage *image_b,
float gamma,
float luminance,
float field_of_view,
bool verbose)
{
unsigned int i, dim;
dim = image_a->Get_Width() * image_a->Get_Height();
// assuming colorspaces are in Adobe RGB (1998) convert to XYZ
float *aX = new float[dim];
float *aY = new float[dim];
float *aZ = new float[dim];
float *bX = new float[dim];
float *bY = new float[dim];
float *bZ = new float[dim];
float *aLum = new float[dim];
float *bLum = new float[dim];
float *aA = new float[dim];
float *bA = new float[dim];
float *aB = new float[dim];
float *bB = new float[dim];
if (verbose) printf("Converting RGB to XYZ\n");
unsigned int x, y, w, h;
w = image_a->Get_Width();
h = image_a->Get_Height();
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
float r, g, b, l;
i = x + y * w;
r = powf(image_a->Get_Red(i) / 255.0f, gamma);
g = powf(image_a->Get_Green(i) / 255.0f, gamma);
b = powf(image_a->Get_Blue(i) / 255.0f, gamma);
AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]);
XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]);
r = powf(image_b->Get_Red(i) / 255.0f, gamma);
g = powf(image_b->Get_Green(i) / 255.0f, gamma);
b = powf(image_b->Get_Blue(i) / 255.0f, gamma);
AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]);
XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]);
aLum[i] = aY[i] * luminance;
bLum[i] = bY[i] * luminance;
}
}
if (verbose) printf("Constructing Laplacian Pyramids\n");
LPyramid *la = new LPyramid(aLum, w, h);
LPyramid *lb = new LPyramid(bLum, w, h);
float num_one_degree_pixels = (float) (2 * tan(field_of_view * 0.5 * M_PI / 180) * 180 / M_PI);
float pixels_per_degree = w / num_one_degree_pixels;
if (verbose) printf("Performing test\n");
float num_pixels = 1;
unsigned int adaptation_level = 0;
for (i = 0; i < MAX_PYR_LEVELS; i++) {
adaptation_level = i;
if (num_pixels > num_one_degree_pixels) break;
num_pixels *= 2;
}
float cpd[MAX_PYR_LEVELS];
cpd[0] = 0.5f * pixels_per_degree;
for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1];
float csf_max = csf(3.248f, 100.0f);
float F_freq[MAX_PYR_LEVELS - 2];
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f);
unsigned int pixels_failed = 0;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
int index = x + y * w;
float contrast[MAX_PYR_LEVELS - 2];
float sum_contrast = 0;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1));
float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1));
float numerator = (n1 > n2) ? n1 : n2;
float d1 = fabsf(la->Get_Value(x,y,i+2));
float d2 = fabsf(lb->Get_Value(x,y,i+2));
float denominator = (d1 > d2) ? d1 : d2;
if (denominator < 1e-5f) denominator = 1e-5f;
contrast[i] = numerator / denominator;
sum_contrast += contrast[i];
}
if (sum_contrast < 1e-5) sum_contrast = 1e-5f;
float F_mask[MAX_PYR_LEVELS - 2];
float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level);
adapt *= 0.5f;
if (adapt < 1e-5) adapt = 1e-5f;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt));
}
float factor = 0;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast;
}
if (factor < 1) factor = 1;
if (factor > 10) factor = 10;
float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0));
bool pass = true;
// pure luminance test
if (delta > factor * tvi(adapt)) {
pass = false;
} else {
// CIE delta E test with modifications
float color_scale = 1.0f;
// ramp down the color test in scotopic regions
if (adapt < 10.0f) {
color_scale = 1.0f - (10.0f - color_scale) / 10.0f;
color_scale = color_scale * color_scale;
}
float da = aA[index] - bA[index];
float db = aB[index] - bB[index];
da = da * da;
db = db * db;
float delta_e = (da + db) * color_scale;
if (delta_e > factor) {
pass = false;
}
}
if (!pass)
pixels_failed++;
#if IMAGE_DIFF_ENABLED
if (!pass) {
if (args.ImgDiff) {
args.ImgDiff->Set(255, 0, 0, 255, index);
}
} else {
if (args.ImgDiff) {
args.ImgDiff->Set(0, 0, 0, 255, index);
}
}
#endif
}
}
if (aX) delete[] aX;
if (aY) delete[] aY;
if (aZ) delete[] aZ;
if (bX) delete[] bX;
if (bY) delete[] bY;
if (bZ) delete[] bZ;
if (aLum) delete[] aLum;
if (bLum) delete[] bLum;
if (la) delete la;
if (lb) delete lb;
if (aA) delete aA;
if (bA) delete bA;
if (aB) delete aB;
if (bB) delete bB;
return pixels_failed;
}

View file

@ -1,26 +0,0 @@
/*
Metric
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _METRIC_H
#define _METRIC_H
class CompareArgs;
// Image comparison metric using Yee's method
// References: A Perceptual Metric for Production Testing, Hector Yee, Journal of Graphics Tools 2004
bool Yee_Compare(CompareArgs &args);
#endif

View file

@ -1,45 +0,0 @@
/*
PerceptualDiff - a program that compares two images using a perceptual metric
based on the paper :
A perceptual metric for production testing. Journal of graphics tools, 9(4):33-40, 2004, Hector Yee
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <string>
#include "LPyramid.h"
#include "RGBAImage.h"
#include "CompareArgs.h"
#include "Metric.h"
int main(int argc, char **argv)
{
CompareArgs args;
if (!args.Parse_Args(argc, argv)) {
printf("%s", args.ErrorStr.c_str());
return -1;
} else {
if (args.Verbose) args.Print_Args();
}
int result = Yee_Compare(args) == true;
if (result) {
printf("PASS: %s\n", args.ErrorStr.c_str());
} else {
printf("FAIL: %s\n", args.ErrorStr.c_str());
}
return result;
}

View file

@ -36,10 +36,10 @@ a theatre has a field of view of around 25 degrees. Back row has a field of
-gamma g : The gamma to use to convert to RGB linear space. Default is 2.2
-luminance l: The luminance of the display the observer is seeing. Default
is 100 candela per meter squared
-output foo.ppm : Saves the difference image to foo.ppm
Credits
Hector Yee, project administrator and originator - hectorgon.blogspot.com
Scott Corley, for png file IO code
Mick Weiss, Linux build and release & QA
Mick Weiss, Linux build and release & QA
Carl Worth, Rewrite as library, depend on cairo, and port to C

View file

@ -1,144 +0,0 @@
/*
RGBAImage.cpp
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "RGBAImage.h"
#include "png.h"
#if HAVE_LIBTIFF
#include "tiff.h"
#include "tiffio.h"
// Reads Tiff Images
RGBAImage* RGBAImage::ReadTiff(char *filename)
{
RGBAImage *fimg = 0;
TIFF* tif = TIFFOpen(filename, "r");
char emsg[1024];
emsg[0] = 0;
if (tif) {
TIFFRGBAImage img;
if (TIFFRGBAImageBegin(&img, tif, 0, emsg)) {
size_t npixels;
uint32* raster;
npixels = img.width * img.height;
raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32));
if (raster != NULL) {
if (TIFFRGBAImageGet(&img, raster, img.width, img.height)) {
// result is in ABGR
fimg = new RGBAImage(img.width, img.height);
for (int y = img.height - 1; y >= 0; y--) {
for (int x = 0; x < (int) img.width; x++) {
fimg->Set(x,img.height - (y+1), raster[x + y * img.width]);
}
}
}
_TIFFfree(raster);
}
}
TIFFRGBAImageEnd(&img);
}
return fimg;
}
#endif /* HAVE_LIBTIFF */
// This portion was written by Scott Corley
RGBAImage* RGBAImage::ReadPNG(char *filename)
{
RGBAImage *fimg = 0;
FILE *fp=fopen(filename, "rb");
if (!fp)
{
return NULL;
}
png_byte header[8];
fread(header, 1, 8, fp);
bool is_png = !png_sig_cmp(header, 0, 8);
if (!is_png)
{
return NULL;
}
png_structp png_ptr = png_create_read_struct
(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
NULL, NULL);
if (!png_ptr)
return (NULL);
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_read_struct(&png_ptr,
(png_infopp)NULL, (png_infopp)NULL);
return (NULL);
}
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info)
{
png_destroy_read_struct(&png_ptr, &info_ptr,
(png_infopp)NULL);
return (NULL);
}
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, 8);
png_read_png(png_ptr, info_ptr, 0, NULL);
png_bytep *row_pointers;
row_pointers = png_get_rows(png_ptr, info_ptr);
fimg = new RGBAImage(png_ptr->width, png_ptr->height);
for (int y = 0; y < (int) png_ptr->height; y++) {
for (int x = 0; x < (int) png_ptr->width; x++) {
uint32_t value = 0;
if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)
value = ((uint32_t)row_pointers[y][x*4]) | (((uint32_t)row_pointers[y][x*4+1])<<8) | (((uint32_t)row_pointers[y][x*4+2])<<16) |(((uint32_t)row_pointers[y][x*4+3])<<24);
else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB)
value = ((uint32_t)row_pointers[y][x*3] /*B*/) | (((uint32_t)row_pointers[y][x*3+1] /*G*/)<<8) | (((uint32_t)row_pointers[y][x*3+2]/*R*/)<<16) | (0xFFUL << 24);
fimg->Set(x,y, value);
}
}
png_read_destroy(png_ptr, info_ptr, end_info);
return fimg;
}
bool RGBAImage::WritePPM()
{
if (Width <= 0) return false;
if (Height <=0 ) return false;
FILE *out = fopen(Name.c_str(), "wb");
if (!out) return false;
fprintf(out, "P6\n%d %d 255\n", Width, Height);
for (int y = 0; y < Height; y++) {
for (int x = 0; x < Width; x++) {
int i = x + y * Width;
unsigned char r = Get_Red(i);
unsigned char g = Get_Green(i);
unsigned char b = Get_Blue(i);
fwrite(&r, 1, 1, out);
fwrite(&g, 1, 1, out);
fwrite(&b, 1, 1, out);
}
}
fclose(out);
return true;
}

View file

@ -1,57 +0,0 @@
/*
RGBAImage.h
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _RGAIMAGE_H
#define _RGBAIMAGE_H
#include<string>
// assumes data is in the ABGR format
class RGBAImage
{
public:
RGBAImage(int w, int h, const char *name = 0)
{
Width = w;
Height = h;
if (name) Name = name;
Data = new unsigned int[w * h];
};
~RGBAImage() { if (Data) delete[] Data; }
unsigned char Get_Red(unsigned int i) { return (Data[i] & 0xFF); }
unsigned char Get_Green(unsigned int i) { return ((Data[i]>>8) & 0xFF); }
unsigned char Get_Blue(unsigned int i) { return ((Data[i]>>16) & 0xFF); }
unsigned char Get_Alpha(unsigned int i) { return ((Data[i]>>24) & 0xFF); }
void Set(unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned int i)
{ Data[i] = r | (g << 8) | (b << 16) | (a << 24); }
int Get_Width(void) const { return Width; }
int Get_Height(void) const { return Height; }
void Set(int x, int y, unsigned int d) { Data[x + y * Width] = d; }
unsigned int Get(int x, int y) const { return Data[x + y * Width]; }
unsigned int Get(int i) const { return Data[i]; }
const std::string &Get_Name(void) const { return Name; }
bool WritePPM();
static RGBAImage* ReadTiff(char *filename);
static RGBAImage* ReadPNG(char *filename);
protected:
int Width;
int Height;
std::string Name;
unsigned int *Data;
};
#endif

119
test/pdiff/args.c Normal file
View file

@ -0,0 +1,119 @@
/*
Comapre Args
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "args.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char* copyright =
"PerceptualDiff version 1.0, Copyright (C) 2006 Yangli Hector Yee\n\
PerceptualDiff comes with ABSOLUTELY NO WARRANTY;\n\
This is free software, and you are welcome\n\
to redistribute it under certain conditions;\n\
See the GPL page for details: http://www.gnu.org/copyleft/gpl.html\n\n";
static const char *usage =
"PeceptualDiff image1.tif image2.tif\n\n\
Compares image1.tif and image2.tif using a perceptually based image metric\n\
Options:\n\
\t-verbose : Turns on verbose mode\n\
\t-fov deg : Field of view in degrees (0.1 to 89.9)\n\
\t-threshold p : #pixels p below which differences are ignored\n\
\t-gamma g : Value to convert rgb into linear space (default 2.2)\n\
\t-luminance l : White luminance (default 100.0 cdm^-2)\n\
\n\
\n Note: Input files can also be in the PNG format\
\n";
void
args_init (args_t *args)
{
args->surface_a = NULL;
args->surface_b = NULL;
args->Verbose = false;
args->FieldOfView = 45.0f;
args->Gamma = 2.2f;
args->ThresholdPixels = 100;
args->Luminance = 100.0f;
}
void
args_fini (args_t *args)
{
cairo_surface_destroy (args->surface_a);
cairo_surface_destroy (args->surface_b);
}
bool
args_parse (args_t *args, int argc, char **argv)
{
int i;
if (argc < 3) {
fprintf (stderr, "%s", copyright);
fprintf (stderr, "%s", usage);
return false;
}
for (i = 0; i < argc; i++) {
if (i == 1) {
args->surface_a = cairo_image_surface_create_from_png (argv[1]);
if (cairo_surface_status (args->surface_a))
{
fprintf (stderr, "FAIL: Cannot open %s: %s\n",
argv[1], cairo_status_to_string (cairo_surface_status (args->surface_a)));
return false;
}
} else if (i == 2) {
args->surface_b = cairo_image_surface_create_from_png (argv[2]);
if (cairo_surface_status (args->surface_b))
{
fprintf (stderr, "FAIL: Cannot open %s: %s\n",
argv[2], cairo_status_to_string (cairo_surface_status (args->surface_b)));
return false;
}
} else {
if (strstr(argv[i], "-fov")) {
if (i + 1 < argc) {
args->FieldOfView = (float) atof(argv[i + 1]);
}
} else if (strstr(argv[i], "-verbose")) {
args->Verbose = true;
} else if (strstr(argv[i], "-threshold")) {
if (i + 1 < argc) {
args->ThresholdPixels = atoi(argv[i + 1]);
}
} else if (strstr(argv[i], "-gamma")) {
if (i + 1 < argc) {
args->Gamma = (float) atof(argv[i + 1]);
}
}else if (strstr(argv[i], "-luminance")) {
if (i + 1 < argc) {
args->Luminance = (float) atof(argv[i + 1]);
}
}
}
} /* i */
return true;
}
void
args_print (args_t *args)
{
printf("Field of view is %f degrees\n", args->FieldOfView);
printf("Threshold pixels is %d pixels\n", args->ThresholdPixels);
printf("The Gamma is %f\n", args->Gamma);
printf("The Display's luminance is %f candela per meter squared\n", args->Luminance);
}

46
test/pdiff/args.h Normal file
View file

@ -0,0 +1,46 @@
/*
Comapre Args
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _ARGS_H
#define _ARGS_H
#include "pdiff.h"
/* Args to pass into the comparison function */
typedef struct _args
{
cairo_surface_t *surface_a; /* Image A */
cairo_surface_t *surface_b; /* Image B */
bool Verbose; /* Print lots of text or not */
float FieldOfView; /* Field of view in degrees */
float Gamma; /* The gamma to convert to linear color space */
float Luminance; /* the display's luminance */
unsigned int ThresholdPixels; /* How many pixels different to ignore */
} args_t;
void
args_init (args_t *args);
void
args_fini (args_t *args);
bool
args_parse (args_t *args, int argc, char **argv);
void
args_print (args_t *args);
#endif

111
test/pdiff/lpyramid.c Normal file
View file

@ -0,0 +1,111 @@
/*
Laplacian Pyramid
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "lpyramid.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct _lpyramid {
/* Succesively blurred versions of the original image */
float *levels[MAX_PYR_LEVELS];
int width;
int height;
};
static void
convolve (lpyramid_t *pyramid, float *a, float *b)
/* convolves image b with the filter kernel and stores it in a */
{
int y,x,i,j,nx,ny;
const float Kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f};
int width = pyramid->width;
int height = pyramid->height;
for (y=0; y<height; y++) {
for (x=0; x<width; x++) {
int index = y * width + x;
a[index] = 0.0f;
for (i=-2; i<=2; i++) {
for (j=-2; j<=2; j++) {
nx=x+i;
ny=y+j;
if (nx<0) nx=-nx;
if (ny<0) ny=-ny;
if (nx>=width) nx=2*(width-1)-nx;
if (ny>=height) ny=2*(height-1)-ny;
a[index] += Kernel[i+2] * Kernel[j+2] * b[ny * width + nx];
}
}
}
}
}
/*
* Construction/Destruction
*/
lpyramid_t *
lpyramid_create (float *image, int width, int height)
{
lpyramid_t *pyramid;
int i;
pyramid = malloc (sizeof (lpyramid_t));
if (pyramid == NULL) {
fprintf (stderr, "Out of memory.\n");
exit (1);
}
pyramid->width = width;
pyramid->height = height;
/* Make the Laplacian pyramid by successively
* copying the earlier levels and blurring them */
for (i=0; i<MAX_PYR_LEVELS; i++) {
pyramid->levels[i] = malloc (width * height * sizeof (float));
if (pyramid->levels[i] == NULL) {
fprintf (stderr, "Out of memory.\n");
exit (1);
}
if (i == 0) {
memcpy (pyramid->levels[i], image, width * height * sizeof (float));
} else {
convolve(pyramid, pyramid->levels[i], pyramid->levels[i - 1]);
}
}
return pyramid;
}
void
lpyramid_destroy (lpyramid_t *pyramid)
{
int i;
for (i=0; i<MAX_PYR_LEVELS; i++)
free (pyramid->levels[i]);
}
float
lpyramid_get_value (lpyramid_t *pyramid, int x, int y, int level)
{
int index = x + y * pyramid->width;
int l = level;
if (l > MAX_PYR_LEVELS)
l = MAX_PYR_LEVELS;
return pyramid->levels[level][index];
}

32
test/pdiff/lpyramid.h Normal file
View file

@ -0,0 +1,32 @@
/*
Laplacian Pyramid
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _LPYRAMID_H
#define _LPYRAMID_H
#define MAX_PYR_LEVELS 8
typedef struct _lpyramid lpyramid_t;
lpyramid_t *
lpyramid_create (float *image, int width, int height);
void
lpyramid_destroy (lpyramid_t *pyramid);
float
lpyramid_get_value (lpyramid_t *pyramid, int x, int y, int level);
#endif /* _LPYRAMID_H */

361
test/pdiff/pdiff.c Normal file
View file

@ -0,0 +1,361 @@
/*
Metric
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "lpyramid.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "pdiff.h"
#ifndef M_PI
#define M_PI 3.14159265f
#endif
/*
* Given the adaptation luminance, this function returns the
* threshold of visibility in cd per m^2
* TVI means Threshold vs Intensity function
* This version comes from Ward Larson Siggraph 1997
*/
static float
tvi (float adaptation_luminance)
{
/* returns the threshold luminance given the adaptation luminance
units are candelas per meter squared
*/
float log_a, r, result;
log_a = log10f(adaptation_luminance);
if (log_a < -3.94f) {
r = -2.86f;
} else if (log_a < -1.44f) {
r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f;
} else if (log_a < -0.0184f) {
r = log_a - 0.395f;
} else if (log_a < 1.9f) {
r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f;
} else {
r = log_a - 1.255f;
}
result = powf(10.0f , r);
return result;
}
/* computes the contrast sensitivity function (Barten SPIE 1989)
* given the cycles per degree (cpd) and luminance (lum)
*/
static float
csf (float cpd, float lum)
{
float a, b, result;
a = 440.0f * powf((1.0f + 0.7f / lum), -0.2f);
b = 0.3f * powf((1.0f + 100.0f / lum), 0.15f);
result = a * cpd * expf(-b * cpd) * sqrtf(1.0f + 0.06f * expf(b * cpd));
return result;
}
/*
* Visual Masking Function
* from Daly 1993
*/
static float
mask (float contrast)
{
float a, b, result;
a = powf(392.498f * contrast, 0.7f);
b = powf(0.0153f * a, 4.0f);
result = powf(1.0f + b, 0.25f);
return result;
}
/* convert Adobe RGB (1998) with reference white D65 to XYZ */
static void
AdobeRGBToXYZ (float r, float g, float b, float *x, float *y, float *z)
{
/* matrix is from http://www.brucelindbloom.com/ */
*x = r * 0.576700f + g * 0.185556f + b * 0.188212f;
*y = r * 0.297361f + g * 0.627355f + b * 0.0752847f;
*z = r * 0.0270328f + g * 0.0706879f + b * 0.991248f;
}
static void
XYZToLAB (float x, float y, float z, float *L, float *A, float *B)
{
static float xw = -1;
static float yw;
static float zw;
const float epsilon = 216.0f / 24389.0f;
const float kappa = 24389.0f / 27.0f;
float f[3];
float r[3];
int i;
/* reference white */
if (xw < 0) {
AdobeRGBToXYZ(1, 1, 1, &xw, &yw, &zw);
}
r[0] = x / xw;
r[1] = y / yw;
r[2] = z / zw;
for (i = 0; i < 3; i++) {
if (r[i] > epsilon) {
f[i] = powf(r[i], 1.0f / 3.0f);
} else {
f[i] = (kappa * r[i] + 16.0f) / 116.0f;
}
}
*L = 116.0f * f[1] - 16.0f;
*A = 500.0f * (f[0] - f[1]);
*B = 200.0f * (f[1] - f[2]);
}
static uint32_t
_get_pixel (cairo_surface_t *surface, int i)
{
uint32_t *data;
data = (uint32_t *) cairo_image_surface_get_data (surface);
return data[i];
}
static unsigned char
_get_red (cairo_surface_t *surface, int i)
{
uint32_t pixel;
uint8_t alpha;
pixel = _get_pixel (surface, i);
alpha = (pixel & 0xff000000) >> 24;
if (alpha == 0)
return 0;
else
return (((pixel & 0x00ff0000) >> 16) * 255 + alpha / 2) / alpha;
}
static unsigned char
_get_green (cairo_surface_t *surface, int i)
{
uint32_t pixel;
uint8_t alpha;
pixel = _get_pixel (surface, i);
alpha = (pixel & 0xff000000) >> 24;
if (alpha == 0)
return 0;
else
return (((pixel & 0x0000ff00) >> 8) * 255 + alpha / 2) / alpha;
}
static unsigned char
_get_blue (cairo_surface_t *surface, int i)
{
uint32_t pixel;
uint8_t alpha;
pixel = _get_pixel (surface, i);
alpha = (pixel & 0xff000000) >> 24;
if (alpha == 0)
return 0;
else
return (((pixel & 0x000000ff) >> 0) * 255 + alpha / 2) / alpha;
}
static void *
xmalloc (size_t size)
{
void *buf;
buf = malloc (size);
if (buf == NULL) {
fprintf (stderr, "Out of memory.\n");
exit (1);
}
}
int
pdiff_compare (cairo_surface_t *surface_a,
cairo_surface_t *surface_b,
double gamma,
double luminance,
double field_of_view)
{
unsigned int dim = (cairo_image_surface_get_width (surface_a)
* cairo_image_surface_get_height (surface_a));
unsigned int i;
/* assuming colorspaces are in Adobe RGB (1998) convert to XYZ */
float *aX = xmalloc (dim * sizeof (float));
float *aY = xmalloc (dim * sizeof (float));
float *aZ = xmalloc (dim * sizeof (float));
float *bX = xmalloc (dim * sizeof (float));
float *bY = xmalloc (dim * sizeof (float));
float *bZ = xmalloc (dim * sizeof (float));
float *aLum = xmalloc (dim * sizeof (float));
float *bLum = xmalloc (dim * sizeof (float));
float *aA = xmalloc (dim * sizeof (float));
float *bA = xmalloc (dim * sizeof (float));
float *aB = xmalloc (dim * sizeof (float));
float *bB = xmalloc (dim * sizeof (float));
unsigned int x, y, w, h;
lpyramid_t *la, *lb;
float num_one_degree_pixels, pixels_per_degree, num_pixels;
unsigned int adaptation_level;
float cpd[MAX_PYR_LEVELS];
float F_freq[MAX_PYR_LEVELS - 2];
float csf_max;
unsigned int pixels_failed;
w = cairo_image_surface_get_width (surface_a);
h = cairo_image_surface_get_height (surface_a);
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
float r, g, b, l;
i = x + y * w;
r = powf(_get_red (surface_a, i) / 255.0f, gamma);
g = powf(_get_green (surface_a, i) / 255.0f, gamma);
b = powf(_get_blue (surface_a, i) / 255.0f, gamma);
AdobeRGBToXYZ(r,g,b,&aX[i],&aY[i],&aZ[i]);
XYZToLAB(aX[i], aY[i], aZ[i], &l, &aA[i], &aB[i]);
r = powf(_get_red (surface_b, i) / 255.0f, gamma);
g = powf(_get_green (surface_b, i) / 255.0f, gamma);
b = powf(_get_blue (surface_b, i) / 255.0f, gamma);
AdobeRGBToXYZ(r,g,b,&bX[i],&bY[i],&bZ[i]);
XYZToLAB(bX[i], bY[i], bZ[i], &l, &bA[i], &bB[i]);
aLum[i] = aY[i] * luminance;
bLum[i] = bY[i] * luminance;
}
}
la = lpyramid_create (aLum, w, h);
lb = lpyramid_create (bLum, w, h);
num_one_degree_pixels = (float) (2 * tan(field_of_view * 0.5 * M_PI / 180) * 180 / M_PI);
pixels_per_degree = w / num_one_degree_pixels;
num_pixels = 1;
adaptation_level = 0;
for (i = 0; i < MAX_PYR_LEVELS; i++) {
adaptation_level = i;
if (num_pixels > num_one_degree_pixels) break;
num_pixels *= 2;
}
cpd[0] = 0.5f * pixels_per_degree;
for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1];
csf_max = csf(3.248f, 100.0f);
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f);
pixels_failed = 0;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
int index = x + y * w;
float contrast[MAX_PYR_LEVELS - 2];
float F_mask[MAX_PYR_LEVELS - 2];
float factor;
float delta;
bool pass;
float sum_contrast = 0;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
float n1 = fabsf(lpyramid_get_value (la,x,y,i) - lpyramid_get_value (la,x,y,i + 1));
float n2 = fabsf(lpyramid_get_value (lb,x,y,i) - lpyramid_get_value (lb,x,y,i + 1));
float numerator = (n1 > n2) ? n1 : n2;
float d1 = fabsf(lpyramid_get_value(la,x,y,i+2));
float d2 = fabsf(lpyramid_get_value(lb,x,y,i+2));
float denominator = (d1 > d2) ? d1 : d2;
if (denominator < 1e-5f) denominator = 1e-5f;
contrast[i] = numerator / denominator;
sum_contrast += contrast[i];
}
if (sum_contrast < 1e-5) sum_contrast = 1e-5f;
float adapt = lpyramid_get_value(la,x,y,adaptation_level) + lpyramid_get_value(lb,x,y,adaptation_level);
adapt *= 0.5f;
if (adapt < 1e-5) adapt = 1e-5f;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt));
}
factor = 0;
for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast;
}
if (factor < 1) factor = 1;
if (factor > 10) factor = 10;
delta = fabsf(lpyramid_get_value(la,x,y,0) - lpyramid_get_value(lb,x,y,0));
pass = true;
/* pure luminance test */
if (delta > factor * tvi(adapt)) {
pass = false;
} else {
/* CIE delta E test with modifications */
float color_scale = 1.0f;
float da = aA[index] - bA[index];
float db = aB[index] - bB[index];
float delta_e;
/* ramp down the color test in scotopic regions */
if (adapt < 10.0f) {
color_scale = 1.0f - (10.0f - color_scale) / 10.0f;
color_scale = color_scale * color_scale;
}
da = da * da;
db = db * db;
delta_e = (da + db) * color_scale;
if (delta_e > factor) {
pass = false;
}
}
if (!pass)
pixels_failed++;
}
}
free (aX);
free (aY);
free (aZ);
free (bX);
free (bY);
free (bZ);
free (aLum);
free (bLum);
lpyramid_destroy (la);
lpyramid_destroy (lb);
free (aA);
free (bA);
free (aB);
free (bB);
return pixels_failed;
}

40
test/pdiff/pdiff.h Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright (C) 2006 Yangli Hector Yee
Copyright (C) 2006 Red Hat, Inc.
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _PDIFF_H
#define _PDIFF_H
#include <cairo.h>
typedef int bool;
#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
/* Image comparison metric using Yee's method (and a cairo interface)
* References: A Perceptual Metric for Production Testing, Hector Yee, Journal of Graphics Tools 2004
*/
int
pdiff_compare (cairo_surface_t *surface_a,
cairo_surface_t *surface_b,
double gamma,
double luminance,
double field_of_view);
#endif

101
test/pdiff/perceptualdiff.c Normal file
View file

@ -0,0 +1,101 @@
/*
PerceptualDiff - a program that compares two images using a perceptual metric
based on the paper :
A perceptual metric for production testing. Journal of graphics tools, 9(4):33-40, 2004, Hector Yee
Copyright (C) 2006 Yangli Hector Yee
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program;
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "lpyramid.h"
#include "args.h"
#include "pdiff.h"
static bool Yee_Compare(args_t *args)
{
int width_a, height_a, stride_a;
unsigned char *data_a, *row_a;
uint32_t *pixel_a;
int width_b, height_b, stride_b;
unsigned char *data_b, *row_b;
uint32_t *pixel_b;
unsigned int x, y, dim, pixels_failed;
bool identical = true;
width_a = cairo_image_surface_get_width (args->surface_a);
height_a = cairo_image_surface_get_height (args->surface_a);
stride_a = cairo_image_surface_get_stride (args->surface_a);
data_a = cairo_image_surface_get_data (args->surface_a);
width_b = cairo_image_surface_get_width (args->surface_b);
height_b = cairo_image_surface_get_height (args->surface_b);
stride_b = cairo_image_surface_get_stride (args->surface_b);
data_b = cairo_image_surface_get_data (args->surface_b);
if ((width_a != width_b) || (height_a != height_b)) {
printf ("FAIL: Image dimensions do not match\n");
return false;
}
identical = true;
for (y = 0; y < height_a; y++) {
row_a = data_a + y * stride_a;
row_b = data_b + y * stride_b;
pixel_a = (uint32_t *) row_a;
pixel_b = (uint32_t *) row_b;
for (x = 0; x < width_a; x++) {
if (*pixel_a != *pixel_b) {
identical = false;
}
pixel_a++;
pixel_b++;
}
}
if (identical) {
printf ("PASS: Images are binary identical\n");
return true;
}
pixels_failed = pdiff_compare (args->surface_a, args->surface_b,
args->Gamma, args->Luminance,
args->FieldOfView);
if (pixels_failed < args->ThresholdPixels) {
printf ("PASS: Images are perceptually indistinguishable\n");
return true;
}
printf("FAIL: Images are visibly different\n"
"%d pixels are different\n", pixels_failed);
return false;
}
int main(int argc, char **argv)
{
args_t args;
args_init (&args);
if (!args_parse (&args, argc, argv)) {
return -1;
} else {
if (args.Verbose)
args_print (&args);
}
return ! Yee_Compare(&args);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

@ -27,9 +27,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
const char png_filename[] = "romedalen.png";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -28,9 +28,9 @@
#include "cairo-test.h"
#include <stdio.h>
#define WIDTH 64
#define HEIGHT 64
#define PAD 10
#define WIDTH 16
#define HEIGHT 16
#define PAD 2
static void
draw_mask (cairo_t *cr, int x, int y)