Skip to content
Commits on Source (11)
  • Pekka Paalanen's avatar
    tests/alpha-blending: move unpremult to color_util · 0d385ffa
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    More tests are going to need this.
    
    The API is changed to work by copy in and copy out to match the other
    color_util API. Hopefully this makes the caller code easier to read.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    0d385ffa
  • Pekka Paalanen's avatar
    tests: change rgb_diff_stat printing · 9026293b
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    Seems it will be common to print all four min/max/avg sets of errors, so
    move the printing code into a shared place.
    
    While 0.0-1.0 is the natural range for color values, people are often
    accustomed to working with 8-bit or even 10-bit pixel values. An error
    of +/- 1 in 8-bit is more intuitive than +/- 0.004 in floating-point.
    Hence 'scaling_bits' is added so the caller can determine the value
    scaling. This will scale both the reported error numbers, and the
    recorded error positions (rgb-tuples), so they are all comparable.
    
    I'm happy to get rid of those two macros as well.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    9026293b
  • Pekka Paalanen's avatar
    tests/color_util: constify *_stat_update() · f31d2666
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    These arguments are not meant to be changed, and a new test will need
    this const.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    f31d2666
  • Pekka Paalanen's avatar
    tests: add scalar_stat dumps · 912ea2cb
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    The new field in struct scalar_stat allows recording all tested values
    into a file. This is intended to replace ad hoc dumping code like in
    alpha-blending-test.c.
    
    To make it easy to set up, also offer a helper to open a writable file
    whose name consists of a custom prefix and test name.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    912ea2cb
  • Pekka Paalanen's avatar
    tests/color_util: make rgb_diff_stat pos explicit · 3f605424
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    The recently introduced rgb_diff_stat value dumping feature logs the
    "position" where the value or error was measured. The reference value
    was used as the position, but the problem with the reference value is
    that it is an output value and not an input value. Therefore mapping
    that back to which input values promoted the error is not easy.
    
    Fix that problem by passing the position explicitly into
    rgb_diff_stat_update(), just like it is already passed in to
    scalar_stat_update().
    
    Currently the only user simply passes the reference value as position,
    because there the input value is also the reference value. This is not
    true for future uses of rgb_diff_stat.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    3f605424
  • Pekka Paalanen's avatar
    tests: add rgb_diff_stat dumps · e103ef4d
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    This is a special case of scalar_stat dumps to record all of two-norm
    and RGB differences on the same line in the dump file.
    
    This makes the dump file easier to handle when you want full RGB errors
    recorded.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    e103ef4d
  • Pekka Paalanen's avatar
    tests/color_util: doc rgb_diff_stat and scalar_stat · be281478
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    Add documentation for test authors.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    be281478
  • Pekka Paalanen's avatar
    tests/alpha-blending: replace compare_float() with rgb_diff_stat · a0584e64
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    compare_float() was an ad hoc max error logger with optional debug
    logging.
    
    Now that we have rgb_diff_stat, we can get the same statistics and more
    with less code. It looks like we would lose the pixel index x, but that
    can be recovered from the dump file line number.
    
    This patch takes care to keep the test condition exactly the same as it
    was before. The statistics print-out has more details now.
    
    The recorded dump position is the foreground color as that varies while
    the background color is constant.
    
    An example Octave function is included to show how to visualize the
    rgb_diff_stat dump.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    a0584e64
  • Pekka Paalanen's avatar
    tests/alpha-blending: use two_norm tolerance · baf7ab57
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    Switch from per-channel max error tolerance to max two-norm (Euclidean
    distance) error. Geometrically this means that previously the accepted
    volume was a +/- tolerance cube around the reference point, and now it
    is a sphere with tolerance radius. This makes the check slightly
    stricter.
    
    The real benefit is simplifying the code.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    baf7ab57
  • Pekka Paalanen's avatar
    tests/color-icc-output: compare_float() to rgb_diff_stat · 3acb1c47
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    compare_float() was an ad hoc max error logger with optional debug
    logging.
    
    Now that we have rgb_diff_stat, we can get the same statistics and more
    with less code. It looks like we would lose the pixel index x, but that
    can be recovered from the dump file line number.
    
    This patch takes care to keep the test condition exactly the same as it
    was before. The statistics print-out has more details now.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    3acb1c47
  • Pekka Paalanen's avatar
    tests/color-icc-output: use two-norm tolerance · b5467ba2
    Pekka Paalanen authored and Pekka Paalanen's avatar Pekka Paalanen committed
    
    
    Switch from per-channel max error tolerance to max two-norm (Euclidean
    distance) error. Geometrically this means that previously the accepted
    volume was a +/- tolerance cube around the reference point, and now it
    is a sphere with tolerance radius.
    
    The real benefit is simplifying the code.
    
    The error tolerance are also changed to float. Integers cannot represent
    values between 1 and 2, and the jump from 1 to 2 would have been too
    much. AdobeRGB tolerance gets relaxed a bit, while BT2020 tolerance
    becomes stricter. The new tolerance values are the reported achieved
    two-norm max errors plus a bit of margin.
    
    Surprisingly the sRGB case tolerances remain strictly at zero, and
    that's no bug in the test.
    
    Signed-off-by: default avatarPekka Paalanen <pekka.paalanen@collabora.com>
    b5467ba2
......@@ -27,6 +27,7 @@
#include "config.h"
#include <math.h>
#include <stdio.h>
#include "weston-test-client-helper.h"
#include "weston-test-fixture-compositor.h"
......@@ -94,20 +95,6 @@ premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b)
return c;
}
static void
unpremult_float(struct color_float *cf)
{
int i;
if (cf->a == 0.0f) {
for (i = 0; i < COLOR_CHAN_NUM; i++)
cf->rgb[i] = 0.0f;
} else {
for (i = 0; i < COLOR_CHAN_NUM; i++)
cf->rgb[i] /= cf->a;
}
}
static void
fill_alpha_pattern(struct buffer *buf)
{
......@@ -133,81 +120,25 @@ fill_alpha_pattern(struct buffer *buf)
}
}
static bool
compare_float(float ref, float dst, int x, const char *chan, float *max_diff)
{
#if 0
/*
* This file can be loaded in Octave for visualization.
*
* S = load('compare_float_dump.txt');
*
* rvec = S(S(:,1)==114, 2:3);
* gvec = S(S(:,1)==103, 2:3);
* bvec = S(S(:,1)==98, 2:3);
*
* figure
* subplot(3, 1, 1);
* plot(rvec(:,1), rvec(:,2) .* 255, 'r');
* subplot(3, 1, 2);
* plot(gvec(:,1), gvec(:,2) .* 255, 'g');
* subplot(3, 1, 3);
* plot(bvec(:,1), bvec(:,2) .* 255, 'b');
*/
static FILE *fp = NULL;
if (!fp)
fp = fopen("compare_float_dump.txt", "w");
fprintf(fp, "%d %d %f\n", chan[0], x, dst - ref);
fflush(fp);
#endif
float diff = fabsf(ref - dst);
if (diff > *max_diff)
*max_diff = diff;
/*
* Allow for +/- 1.5 code points of error in non-linear 8-bit channel
* value. This is necessary for the BLEND_LINEAR case.
*
* With llvmpipe, we could go as low as +/- 0.65 code points of error
* and still pass.
*
* AMD Polaris 11 would be ok with +/- 1.0 code points error threshold
* if not for one particular case of blending (a=254, r=0) into r=255,
* which results in error of 1.29 code points.
*/
if (diff < 1.5f / 255.f)
return true;
testlog("x=%d %s: ref %f != dst %f, delta %f\n",
x, chan, ref, dst, dst - ref);
return false;
}
enum blend_space {
BLEND_NONLINEAR,
BLEND_LINEAR,
};
static bool
verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32,
int x, struct color_float *max_diff,
enum blend_space space)
static void
compare_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32,
struct rgb_diff_stat *diffstat,
enum blend_space space)
{
const char *const chan_name[COLOR_CHAN_NUM] = { "r", "g", "b" };
struct color_float bg = a8r8g8b8_to_float(bg32);
struct color_float fg = a8r8g8b8_to_float(fg32);
struct color_float dst = a8r8g8b8_to_float(dst32);
struct color_float ref;
bool ok = true;
int i;
unpremult_float(&bg);
unpremult_float(&fg);
unpremult_float(&dst);
bg = color_float_unpremult(bg);
fg = color_float_unpremult(fg);
dst = color_float_unpremult(dst);
if (space == BLEND_LINEAR) {
sRGB_linearize(&bg);
......@@ -220,12 +151,7 @@ verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32,
if (space == BLEND_LINEAR)
sRGB_delinearize(&ref);
for (i = 0; i < COLOR_CHAN_NUM; i++) {
ok = compare_float(ref.rgb[i], dst.rgb[i], x,
chan_name[i], &max_diff->rgb[i]) && ok;
}
return ok;
rgb_diff_stat_update(diffstat, &ref, &dst, &fg);
}
static uint8_t
......@@ -273,10 +199,34 @@ static bool
check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot,
enum blend_space space)
{
FILE *dump = NULL;
#if 0
/*
* This file can be loaded in Octave for visualization. Find the script
* in tests/visualization/weston_plot_rgb_diff_stat.m and call it with
*
* weston_plot_rgb_diff_stat('alpha_blend-f01-dump.txt', 255, 8)
*/
dump = fopen_dump_file("dump");
#endif
/*
* Allow for +/- 1.5 code points of error in non-linear 8-bit channel
* value. This is necessary for the BLEND_LINEAR case.
*
* With llvmpipe, we could go as low as +/- 0.65 code points of error
* and still pass.
*
* AMD Polaris 11 would be ok with +/- 1.0 code points error threshold
* if not for one particular case of blending (a=254, r=0) into r=255,
* which results in error of 1.29 code points.
*/
const float tolerance = 1.5f / 255.f;
uint32_t *bg_row = get_middle_row(bg);
uint32_t *fg_row = get_middle_row(fg);
uint32_t *shot_row = get_middle_row(shot);
struct color_float max_diff = { .rgb = { 0.0f, 0.0f, 0.0f } };
struct rgb_diff_stat diffstat = { .dump = dump, };
bool ret = true;
int x;
......@@ -284,14 +234,17 @@ check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot,
if (!pixels_monotonic(shot_row, x))
ret = false;
if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x],
shot_row[x], x, &max_diff,
space))
ret = false;
compare_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x], shot_row[x],
&diffstat, space);
}
testlog("%s max diff: r=%f, g=%f, b=%f\n",
__func__, max_diff.r, max_diff.g, max_diff.b);
if (diffstat.two_norm.max > tolerance)
ret = false;
rgb_diff_stat_print(&diffstat, __func__, 8);
if (dump)
fclose(dump);
return ret;
}
......
......@@ -27,6 +27,7 @@
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <linux/limits.h>
#include <lcms2.h>
......@@ -136,8 +137,10 @@ struct setup_args {
struct fixture_metadata meta;
int ref_image_index;
const struct lcms_pipeline *pipeline;
/**
* 2/255 or 3/255 maximum possible error, where 255 is 8 bit max value
* Two-norm color error tolerance in units of 1.0/255, computed in
* output electrical space.
*
* Tolerance depends more on the 1D LUT used for the
* inv EOTF than the tested 3D LUT size:
......@@ -147,7 +150,8 @@ struct setup_args {
* in GL-renderer, then we should fix the tolerance
* as the error should reduce a lot.
*/
int tolerance;
float tolerance;
/**
* 3DLUT dimension size
*/
......@@ -159,12 +163,12 @@ struct setup_args {
};
static const struct setup_args my_setup_args[] = {
/* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance */
{ { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->BT2020" }, 2, &pipeline_BT2020, 5, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0, 17, PTYPE_CLUT, 0.0005 },
{ { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1, 17, PTYPE_CLUT, 0.0065 },
/* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance */
{ { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1.4, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->BT2020" }, 2, &pipeline_BT2020, 4.5, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 },
{ { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1.8, 17, PTYPE_CLUT, 0.0065 },
};
static void
......@@ -175,7 +179,7 @@ test_roundtrip(uint8_t r, uint8_t g, uint8_t b, cmsPipeline *pip,
struct color_float out = {};
cmsPipelineEvalFloat(in.rgb, out.rgb, pip);
rgb_diff_stat_update(stat, &in, &out);
rgb_diff_stat_update(stat, &in, &out, &in);
}
/*
......@@ -189,8 +193,6 @@ test_roundtrip(uint8_t r, uint8_t g, uint8_t b, cmsPipeline *pip,
static void
roundtrip_verification(cmsPipeline *DToB, cmsPipeline *BToD, float tolerance)
{
const char *const chan_name[COLOR_CHAN_NUM] = { "r", "g", "b" };
unsigned i;
unsigned r, g, b;
struct rgb_diff_stat stat = {};
cmsPipeline *pip;
......@@ -211,15 +213,7 @@ roundtrip_verification(cmsPipeline *DToB, cmsPipeline *BToD, float tolerance)
cmsPipelineFree(pip);
testlog("DToB->BToD roundtrip error statistics (%u samples):\n",
stat.two_norm.count);
for (i = 0; i < COLOR_CHAN_NUM; i++) {
testlog(" ch %s error:\n", chan_name[i]);
scalar_stat_print_rgb8bit(&stat.rgb[i]);
}
testlog(" Two-norm error:\n");
scalar_stat_print_rgb8bit(&stat.two_norm);
rgb_diff_stat_print(&stat, "DToB->BToD roundtrip", 8);
assert(stat.two_norm.max < tolerance);
}
......@@ -523,67 +517,29 @@ gen_ramp_rgb(pixman_image_t *image, int bitwidth, int width_bar)
}
static bool
compare_float(float ref, float dst, int x, const char *chan,
float *max_diff, float max_allow_diff)
process_pipeline_comparison(const struct buffer *src_buf,
const struct buffer *shot_buf,
const struct setup_args * arg)
{
FILE *dump = NULL;
#if 0
/*
* This file can be loaded in Octave for visualization.
*
* S = load('compare_float_dump.txt');
* This file can be loaded in Octave for visualization. Find the script
* in tests/visualization/weston_plot_rgb_diff_stat.m and call it with
*
* rvec = S(S(:,1)==114, 2:3);
* gvec = S(S(:,1)==103, 2:3);
* bvec = S(S(:,1)==98, 2:3);
*
* figure
* subplot(3, 1, 1);
* plot(rvec(:,1), rvec(:,2) .* 255, 'r');
* subplot(3, 1, 2);
* plot(gvec(:,1), gvec(:,2) .* 255, 'g');
* subplot(3, 1, 3);
* plot(bvec(:,1), bvec(:,2) .* 255, 'b');
* weston_plot_rgb_diff_stat('opaque_pixel_conversion-f05-dump.txt')
*/
static FILE *fp = NULL;
if (!fp)
fp = fopen("compare_float_dump.txt", "w");
fprintf(fp, "%d %d %f\n", chan[0], x, dst - ref);
fflush(fp);
dump = fopen_dump_file("dump");
#endif
float diff = fabsf(ref - dst);
if (diff > *max_diff)
*max_diff = diff;
if (diff <= max_allow_diff)
return true;
testlog("x=%d %s: ref %f != dst %f, delta %f\n",
x, chan, ref, dst, dst - ref);
return false;
}
static bool
process_pipeline_comparison(const struct buffer *src_buf,
const struct buffer *shot_buf,
const struct setup_args * arg)
{
const char *const chan_name[COLOR_CHAN_NUM] = { "r", "g", "b" };
const float max_pixel_value = 255.0;
struct color_float max_diff_pipeline = { .rgb = { 0.0f, 0.0f, 0.0f } };
float max_allow_diff = arg->tolerance / max_pixel_value;
float max_err = 0.0f;
bool ok = true;
struct image_header ih_src = image_header_from(src_buf->image);
struct image_header ih_shot = image_header_from(shot_buf->image);
int y, x;
int chan;
struct color_float pix_src;
struct color_float pix_src_pipeline;
struct color_float pix_shot;
struct rgb_diff_stat diffstat = { .dump = dump };
bool ok;
/* no point to compare different images */
assert(ih_src.width == ih_shot.width);
......@@ -596,33 +552,30 @@ process_pipeline_comparison(const struct buffer *src_buf,
for (x = 0; x < ih_src.width; x++) {
pix_src = a8r8g8b8_to_float(row_ptr[x]);
pix_shot = a8r8g8b8_to_float(row_ptr_shot[x]);
/* do pipeline processing */
process_pixel_using_pipeline(arg->pipeline->pre_fn,
&arg->pipeline->mat,
arg->pipeline->post_fn,
&pix_src, &pix_src_pipeline);
/* check if pipeline matches to shader variant */
for (chan = 0; chan < COLOR_CHAN_NUM; chan++) {
ok &= compare_float(pix_src_pipeline.rgb[chan],
pix_shot.rgb[chan],
x, chan_name[chan],
&max_diff_pipeline.rgb[chan],
max_allow_diff);
}
rgb_diff_stat_update(&diffstat,
&pix_src_pipeline, &pix_shot,
&pix_src);
}
}
for (chan = 0; chan < COLOR_CHAN_NUM; chan++)
max_err = MAX(max_err, max_diff_pipeline.rgb[chan]);
ok = diffstat.two_norm.max <= arg->tolerance / 255.0f;
testlog("%s %s %s tol_req %d, tol_cal %f, max diff: r=%f, g=%f, b=%f %s\n",
__func__, ok == true? "SUCCESS":"FAILURE",
testlog("%s %s %s tolerance %f %s\n", __func__,
ok ? "SUCCESS" : "FAILURE",
arg->meta.name, arg->tolerance,
max_err * max_pixel_value,
max_diff_pipeline.r, max_diff_pipeline.g, max_diff_pipeline.b,
arg->type == PTYPE_MATRIX_SHAPER ? "matrix-shaper" : "cLUT");
rgb_diff_stat_print(&diffstat, __func__, 8);
if (dump)
fclose(dump);
return ok;
}
......
......@@ -280,6 +280,24 @@ sRGB_delinearize(struct color_float *cf)
*cf = color_float_apply_curve(TRANSFER_FN_SRGB_EOTF_INVERSE, *cf);
}
struct color_float
color_float_unpremult(struct color_float in)
{
static const struct color_float transparent = {
.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 0.0f,
};
struct color_float out;
int i;
if (in.a == 0.0f)
return transparent;
for (i = 0; i < COLOR_CHAN_NUM; i++)
out.rgb[i] = in.rgb[i] / in.a;
out.a = in.a;
return out;
}
/*
* Returns the result of the matrix-vector multiplication mat * c.
*/
......@@ -355,8 +373,29 @@ lcmsMAT3_invert(struct lcmsMAT3 *result, const struct lcmsMAT3 *mat)
lcmsMAT3_from_weston_matrix(result, &inv);
}
/** Update scalar statistics
*
* \param stat The statistics structure to update.
* \param val A sample of the variable whose statistics you are collecting.
* \param pos The "position" that generated the current value.
*
* Accumulates min, max, sum and count statistics with the given value.
* Stores the position related to the current max and min each.
*
* To use this, declare a variable of type struct scalar_stat and
* zero-initialize it. Repeatedly call scalar_stat_update() to accumulate
* statistics. Then either directly read out what you are interested in from
* the structure, or use the related accessor or printing functions.
*
* If you also want to collect a debug log of all calls to this function,
* initialize the .dump member to a writable file handle. This is easiest
* with fopen_dump_file(). Remember to fclose() the handle after you have
* no more samples to add.
*/
void
scalar_stat_update(struct scalar_stat *stat, double val, struct color_float *pos)
scalar_stat_update(struct scalar_stat *stat,
double val,
const struct color_float *pos)
{
if (stat->count == 0 || stat->min > val) {
stat->min = val;
......@@ -370,25 +409,21 @@ scalar_stat_update(struct scalar_stat *stat, double val, struct color_float *pos
stat->sum += val;
stat->count++;
if (stat->dump) {
fprintf(stat->dump, "%.8g %.5g %.5g %.5g %.5g\n",
val, pos->r, pos->g, pos->b, pos->a);
}
}
/** Return the average of the previously seen values. */
float
scalar_stat_avg(const struct scalar_stat *stat)
{
return stat->sum / stat->count;
}
#define RGB888_FMT "(%3u, %3u, %3u)"
#define RGB888_VAL(cf) (unsigned)round((cf).r * 255.0), (unsigned)round((cf).g * 255.0), (unsigned)round((cf).b * 255.0)
void
scalar_stat_print_rgb8bit(const struct scalar_stat *stat)
{
testlog(" min %8.5f at " RGB888_FMT "\n", stat->min, RGB888_VAL(stat->min_pos));
testlog(" max %8.5f at " RGB888_FMT "\n", stat->max, RGB888_VAL(stat->max_pos));
testlog(" avg %8.5f\n", scalar_stat_avg(stat));
}
/** Print scalar statistics with pos.r only */
void
scalar_stat_print_float(const struct scalar_stat *stat)
{
......@@ -397,19 +432,107 @@ scalar_stat_print_float(const struct scalar_stat *stat)
testlog(" avg %11.5g\n", scalar_stat_avg(stat));
}
static void
print_stat_at_pos(const char *lim, double val, struct color_float pos, double scale)
{
testlog(" %s %8.5f at rgb(%7.2f, %7.2f, %7.2f)\n",
lim, val * scale, pos.r * scale, pos.g * scale, pos.b * scale);
}
static void
print_rgb_at_pos(const struct scalar_stat *stat, double scale)
{
print_stat_at_pos("min", stat->min, stat->min_pos, scale);
print_stat_at_pos("max", stat->max, stat->max_pos, scale);
testlog(" avg %8.5f\n", scalar_stat_avg(stat) * scale);
}
/** Print min/max/avg for each R/G/B/two-norm statistics
*
* \param stat The statistics to print.
* \param title A custom title to include in the heading which shall be printed
* like "%s error statistics:".
* \param scaling_bits Determines a scaling factor for the printed numbers as
* 2^scaling_bits - 1.
*
* Usually RGB values are stored in unsigned integer representation. 8-bit
* integer range is [0, 255] for example. Passing scaling_bits=8 will multiply
* all values (differences, two-norm errors, and position values) by
* 2^8 - 1 = 255. This makes interpreting the recorded errors more intuitive
* through the integer encoding precision perspective.
*/
void
rgb_diff_stat_print(const struct rgb_diff_stat *stat,
const char *title, unsigned scaling_bits)
{
const char *const chan_name[COLOR_CHAN_NUM] = { "r", "g", "b" };
float scale = exp2f(scaling_bits) - 1.0f;
unsigned i;
assert(scaling_bits > 0);
testlog("%s error statistics, %u samples, value range 0.0 - %.1f:\n",
title, stat->two_norm.count, scale);
for (i = 0; i < COLOR_CHAN_NUM; i++) {
testlog(" ch %s (signed):\n", chan_name[i]);
print_rgb_at_pos(&stat->rgb[i], scale);
}
testlog(" rgb two-norm:\n");
print_rgb_at_pos(&stat->two_norm, scale);
}
/** Update RGB difference statistics
*
* \param stat The statistics structure to update.
* \param ref The reference color to compare to.
* \param val The color produced by the algorithm under test; a sample.
* \param pos The position to be recorded with extremes.
*
* Computes the RGB difference by subtracting the reference color from the
* sample. This signed difference is tracked separately for each color channel
* in a scalar_stat to find the min, max, and average signed difference. The
* two-norm (Euclidean length) of the RGB difference vector is tracked in
* another scalar_stat.
*
* The position is stored separately for each of the eight min/max
* R/G/B/two-norm values recorded. A good way to use position is to record
* the algorithm input color.
*
* To use this, declare a variable of type struct rgb_diff_stat and
* zero-initalize it. Repeatedly call rgb_diff_stat_update() to accumulate
* statistics. Then either directly read out what you are interested in from
* the structure or use rgb_diff_stat_print().
*
* If you also want to collect a debug log of all calls to this function,
* initialize the .dump member to a writable file handle. This is easiest
* with fopen_dump_file(). Remember to fclose() the handle after you have
* no more samples to add.
*/
void
rgb_diff_stat_update(struct rgb_diff_stat *stat,
struct color_float *ref, struct color_float *val)
const struct color_float *ref,
const struct color_float *val,
const struct color_float *pos)
{
unsigned i;
double ssd = 0.0;
double diff[COLOR_CHAN_NUM];
double two_norm;
for (i = 0; i < COLOR_CHAN_NUM; i++) {
double diff = val->rgb[i] - ref->rgb[i];
diff[i] = val->rgb[i] - ref->rgb[i];
scalar_stat_update(&stat->rgb[i], diff, ref);
ssd += diff * diff;
scalar_stat_update(&stat->rgb[i], diff[i], pos);
ssd += diff[i] * diff[i];
}
two_norm = sqrt(ssd);
scalar_stat_update(&stat->two_norm, sqrt(ssd), ref);
scalar_stat_update(&stat->two_norm, two_norm, pos);
if (stat->dump) {
fprintf(stat->dump, "%.8g %.8g %.8g %.8g %.5g %.5g %.5g %.5g\n",
two_norm,
diff[COLOR_CHAN_R], diff[COLOR_CHAN_G], diff[COLOR_CHAN_B],
pos->r, pos->g, pos->b, pos->a);
}
}
......@@ -28,6 +28,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
enum color_chan_index {
COLOR_CHAN_R = 0,
......@@ -107,6 +108,9 @@ process_pixel_using_pipeline(enum transfer_fn pre_curve,
const struct color_float *in,
struct color_float *out);
struct color_float
color_float_unpremult(struct color_float in);
struct color_float
color_float_apply_matrix(const struct lcmsMAT3 *mat, struct color_float c);
......@@ -119,6 +123,10 @@ transfer_fn_name(enum transfer_fn fn);
void
lcmsMAT3_invert(struct lcmsMAT3 *result, const struct lcmsMAT3 *mat);
/** Scalar statistics
*
* See scalar_stat_update().
*/
struct scalar_stat {
double min;
struct color_float min_pos;
......@@ -128,25 +136,57 @@ struct scalar_stat {
double sum;
unsigned count;
/** Debug dump into file
*
* Initialize this to a writable file to get a record of all values
* ever fed through this statistics accumulator. The file shall be
* text with one value and its position per line:
* val pos.r pos.g pos.b pos.a
*
* Set to NULL to not record.
*/
FILE *dump;
};
/** RGB difference statistics
*
* See rgb_diff_stat_update().
*/
struct rgb_diff_stat {
struct scalar_stat rgb[COLOR_CHAN_NUM];
struct scalar_stat two_norm;
/** Debug dump into file
*
* Initialize this to a writable file to get a record of all values
* ever fed through this statistics accumulator. The file shall be
* text with the two-norm error, the rgb difference, and their position
* per line:
* norm diff.r diff.g diff.b pos.r pos.g pos.b pos.a
*
* Set to NULL to not record.
*/
FILE *dump;
};
void
scalar_stat_update(struct scalar_stat *stat, double val, struct color_float *pos);
scalar_stat_update(struct scalar_stat *stat,
double val,
const struct color_float *pos);
float
scalar_stat_avg(const struct scalar_stat *stat);
void
scalar_stat_print_rgb8bit(const struct scalar_stat *stat);
void
scalar_stat_print_float(const struct scalar_stat *stat);
void
rgb_diff_stat_update(struct rgb_diff_stat *stat,
struct color_float *ref, struct color_float *val);
const struct color_float *ref,
const struct color_float *val,
const struct color_float *pos);
void
rgb_diff_stat_print(const struct rgb_diff_stat *stat,
const char *title, unsigned scaling_bits);
% -- weston_plot_rgb_diff_stat (fname)
% -- weston_plot_rgb_diff_stat (fname, scale)
% -- weston_plot_rgb_diff_stat (fname, scale, x_column)
% Plot an rgb_diff_stat dump file
%
% Creates a new figure and draws four sub-plots: R difference,
% G difference, B difference, and two-norm error.
%
% Scale defaults to 255. It is used to multiply both x and y values
% in all plots. Note that R, G and B plots will contain horizontal lines
% at y = +/- 0.5 to help you see the optimal rounding error range for
% the integer encoding [0, scale]. Two-norm plot contains a horizontal
% line at y = sqrt(0.75) which represents an error sphere with the radius
% equal to the two-norm of RGB error (0.5, 0.5, 0.5).
%
% By default, x-axis is sample index, not multiplied by scale. If
% x_column is given, it is a column index in the dump file to be used as
% the x-axis values, multiplied by scale. Indices start from 1, not 0.
% SPDX-FileCopyrightText: 2022 Collabora, Ltd.
% SPDX-License-Identifier: MIT
function weston_plot_rgb_diff_stat(fname, scale, x_column);
S = load(fname);
if nargin < 2
scale = 255;
end
if nargin < 3
x = 1:size(S, 1);
else
x = S(:, x_column) .* scale;
end
x_lim = [min(x) max(x)];
evec = S(:, 1) .* scale; # two-norm error
rvec = S(:, 2) .* scale; # r diff
gvec = S(:, 3) .* scale; # g diff
bvec = S(:, 4) .* scale; # b diff
figure
subplot(4, 1, 1);
plot(x, rvec, 'r');
plus_minus_half_lines(x_lim);
title(fname, "Interpreter", "none");
ylabel('R diff');
axis("tight");
subplot(4, 1, 2);
plot(x, gvec, 'g');
plus_minus_half_lines(x_lim);
ylabel('G diff');
axis("tight");
subplot(4, 1, 3);
plot(x, bvec, 'b');
plus_minus_half_lines(x_lim);
ylabel('B diff');
axis("tight");
subplot(4, 1, 4);
plot(x, evec, 'k');
hold on;
plot(x_lim, [1 1] .* sqrt(0.75), 'k:');
ylabel('Two-norm');
axis("tight");
max_abs_err = [max(abs(rvec)) max(abs(gvec)) max(abs(bvec))]
function plus_minus_half_lines(x_lim);
hold on;
plot(x_lim, [0.5 -0.5; 0.5 -0.5], 'k:');
......@@ -38,6 +38,7 @@
#include "test-config.h"
#include "shared/os-compatibility.h"
#include "shared/string-helpers.h"
#include "shared/xalloc.h"
#include <libweston/zalloc.h>
#include "weston-test-client-helper.h"
......@@ -1144,6 +1145,36 @@ image_filename(const char *basename)
return filename;
}
/** Open a writable file
*
* \param suffix Custom file name suffix.
* \return FILE pointer, or NULL on failure.
*
* The file name consists of output path, test name, and the given suffix.
* If environment variable WESTON_TEST_OUTPUT_PATH is set, it is used as the
* directory path, otherwise the current directory is used.
*
* The file will be writable. If it exists, it is truncated, otherwise it is
* created. Failures are logged.
*/
FILE *
fopen_dump_file(const char *suffix)
{
char *fname;
FILE *fp;
str_printf(&fname, "%s/%s-%s.txt", output_path(),
get_test_name(), suffix);
fp = fopen(fname, "w");
if (!fp) {
testlog("Error: failed to open file '%s' for writing: %s\n",
fname, strerror(errno));
}
free(fname);
return fp;
}
struct format_map_entry {
cairo_format_t cairo;
pixman_format_code_t pixman;
......
......@@ -250,6 +250,9 @@ screenshot_reference_filename(const char *basename, uint32_t seq);
char *
image_filename(const char *basename);
FILE *
fopen_dump_file(const char *suffix);
bool
check_images_match(pixman_image_t *img_a, pixman_image_t *img_b,
const struct rectangle *clip,
......