Skip to content

Commit

Permalink
vectorscope: add RYB option
Browse files Browse the repository at this point in the history
fixes #9847
  • Loading branch information
phweyland committed Aug 29, 2021
1 parent 6b7e0b0 commit 34a2743
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 39 deletions.
28 changes: 28 additions & 0 deletions src/common/colorspaces_inline_conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,34 @@ static inline void dt_HSV_2_RGB(const dt_aligned_pixel_t HSV, dt_aligned_pixel_t
}


#ifdef _OPENMP
#pragma omp declare simd aligned(RGB, HCV: 16)
#endif
static inline void dt_RGB_2_HCV(const dt_aligned_pixel_t RGB, dt_aligned_pixel_t HCV)
{
const float min = fminf(RGB[0], fminf(RGB[1], RGB[2]));
const float max = fmaxf(RGB[0], fmaxf(RGB[1], RGB[2]));
const float delta = max - min;

const float V = max;
float C, H;

if(fabsf(max) > 1e-6f && fabsf(delta) > 1e-6f)
{
C = delta;
H = _dt_RGB_2_Hue(RGB, max, delta);
}
else
{
C = 0.0f;
H = 0.0f;
}

HCV[0] = H;
HCV[1] = C;
HCV[2] = V;
}

#ifdef _OPENMP
#pragma omp declare simd
#endif
Expand Down
27 changes: 27 additions & 0 deletions src/dtgtk/paint.c
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,33 @@ void dtgtk_cairo_paint_jzazbz(cairo_t *cr, gint x, gint y, gint w, gint h, gint
FINISH
}

void dtgtk_cairo_paint_ryb(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
{
PREAMBLE(1, 0, 0)

// FIXME: change icon to "RYB"

cairo_move_to(cr, 0.0, 0.7);
cairo_line_to(cr, 0.0, 0.2);
cairo_curve_to(cr, 0.5, 0.25, -0.2, 0.45, 0.45, 0.7);
cairo_stroke(cr);

cairo_move_to(cr, 0.5, 0.5);
cairo_line_to(cr, 1.0, 0.0);
cairo_stroke(cr);
cairo_move_to(cr, 0.75, 0.25);
cairo_line_to(cr, 0.4, 0.0);
cairo_stroke(cr);

cairo_move_to(cr, 0.55, 1.0);
cairo_line_to(cr, 0.55, 0.5);
cairo_curve_to(cr, 1.0, 0.55, 1.0, 0.7, 0.55, 0.75);
cairo_curve_to(cr, 1.3, 0.8, 1.3, 0.95, 0.4, 1.0);
cairo_stroke(cr);

FINISH
}

void dtgtk_cairo_paint_filmstrip(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
{
gdouble sw = 0.6;
Expand Down
2 changes: 2 additions & 0 deletions src/dtgtk/paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ void dtgtk_cairo_paint_rgb_parade(cairo_t *cr, gint x, gint y, gint w, gint h, g
void dtgtk_cairo_paint_luv(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint JzAzBz icon */
void dtgtk_cairo_paint_jzazbz(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint RYB icon */
void dtgtk_cairo_paint_ryb(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);

/** paint active modulegroup icon */
void dtgtk_cairo_paint_modulegroup_active(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
Expand Down
139 changes: 100 additions & 39 deletions src/libs/histogram.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "libs/lib.h"
#include "libs/lib_api.h"
#include "libs/colorpicker.h"
#include "common/splines.h"

#define HISTOGRAM_BINS 256
// # of gradations between each primary/secondary to draw the hue ring
Expand Down Expand Up @@ -83,14 +84,15 @@ typedef enum dt_lib_histogram_vectorscope_type_t
{
DT_LIB_HISTOGRAM_VECTORSCOPE_CIELUV = 0, // CIE 1976 u*v*
DT_LIB_HISTOGRAM_VECTORSCOPE_JZAZBZ,
DT_LIB_HISTOGRAM_VECTORSCOPE_RYB,
DT_LIB_HISTOGRAM_VECTORSCOPE_N // needs to be the last one
} dt_lib_histogram_vectorscope_type_t;

// FIXME: are these lists available from the enum/options in darktableconfig.xml?
const gchar *dt_lib_histogram_scope_type_names[DT_LIB_HISTOGRAM_SCOPE_N] = { "histogram", "waveform", "rgb parade", "vectorscope" };
const gchar *dt_lib_histogram_scale_names[DT_LIB_HISTOGRAM_SCALE_N] = { "logarithmic", "linear" };
const gchar *dt_lib_histogram_orient_names[DT_LIB_HISTOGRAM_ORIENT_N] = { "horizontal", "vertical" };
const gchar *dt_lib_histogram_vectorscope_type_names[DT_LIB_HISTOGRAM_VECTORSCOPE_N] = { "u*v*", "AzBz" };
const gchar *dt_lib_histogram_vectorscope_type_names[DT_LIB_HISTOGRAM_VECTORSCOPE_N] = { "u*v*", "AzBz", "RYB" };

typedef struct dt_lib_histogram_t
{
Expand Down Expand Up @@ -133,6 +135,8 @@ typedef struct dt_lib_histogram_t
dt_lib_histogram_scale_t vectorscope_scale;
double vectorscope_angle;
gboolean red, green, blue;
float *rgb2ryb_ypp;
float *ryb2rgb_ypp;
} dt_lib_histogram_t;

const char *name(dt_lib_module_t *self)
Expand Down Expand Up @@ -284,6 +288,28 @@ static void _lib_histogram_process_waveform(dt_lib_histogram_t *const d, const f
dt_free_align(partial_binned);
}

const float x_vtx[7] = {0.0, 0.166667, 0.333333, 0.5, 0.666667, 0.833333, 1.0};
const float rgb_y_vtx[7] = {0.0, 0.083333, 0.166667, 0.383838, 0.586575, 0.833333, 1.0};
const float ryb_y_vtx[] = {0.0, 0.333333, 0.472217, 0.611105, 0.715271, 0.833333, 1.0};

static void _ryb2rgb(const dt_aligned_pixel_t ryb, dt_aligned_pixel_t rgb, dt_lib_histogram_t *d)
{
dt_aligned_pixel_t HSV;
dt_RGB_2_HSV(ryb, HSV);
HSV[0] = interpolate_val(sizeof(x_vtx)/sizeof(float), (float *)x_vtx, HSV[0],
(float *)rgb_y_vtx, d->ryb2rgb_ypp, CUBIC_SPLINE);
dt_HSV_2_RGB(HSV, rgb);
}

static void _rgb2ryb(const dt_aligned_pixel_t rgb, dt_aligned_pixel_t ryb, dt_lib_histogram_t *d)
{
dt_aligned_pixel_t HSV;
dt_RGB_2_HSV(rgb, HSV);
HSV[0] = interpolate_val(sizeof(x_vtx)/sizeof(float), (float *)x_vtx, HSV[0],
(float *)ryb_y_vtx, d->rgb2ryb_ypp, CUBIC_SPLINE);
dt_HSV_2_RGB(HSV, ryb);
}

static inline float baselog(float x, float bound)
{
// FIXME: use dt's fastlog()?
Expand Down Expand Up @@ -327,9 +353,10 @@ static void _lib_histogram_vectorscope_bkgd(dt_lib_histogram_t *d, const dt_iop_
// white, middle row R,Y,G,C,B,M,R, top row black,
// scaled up via linear interpolation.

float vertex_rgb[6][4] DT_ALIGNED_PIXEL = {{1.f, 0.f, 0.f}, {1.f, 1.f, 0.f},
{0.f, 1.f, 0.f}, {0.f, 1.f, 1.f},
{0.f, 0.f, 1.f}, {1.f, 0.f, 1.f} };
const float vertex_rgb[6][4] DT_ALIGNED_PIXEL = {{1.f, 0.f, 0.f}, {1.f, 1.f, 0.f},
{0.f, 1.f, 0.f}, {0.f, 1.f, 1.f},
{0.f, 0.f, 1.f}, {1.f, 0.f, 1.f} };

float max_radius = 0.f;
const dt_lib_histogram_vectorscope_type_t vs_type = d->vectorscope_type;

Expand All @@ -338,7 +365,7 @@ static void _lib_histogram_vectorscope_bkgd(dt_lib_histogram_t *d, const dt_iop_
// maps to a very small radius in CIELuv.
cairo_pattern_t *p = cairo_pattern_create_mesh();
// initialize to make gcc-7 happy
dt_aligned_pixel_t prev_rgb_display = { 0.f }, first_rgb_display = { 0.f };
dt_aligned_pixel_t rgb_display = { 0.f }, prev_rgb_display = { 0.f }, first_rgb_display = { 0.f };
double px = 0., py= 0.;

for(int k=0; k<6; k++)
Expand All @@ -351,19 +378,31 @@ static void _lib_histogram_vectorscope_bkgd(dt_lib_histogram_t *d, const dt_iop_
dt_aligned_pixel_t rgb_scope, XYZ_D50, chromaticity;
for_each_channel(ch, aligned(vertex_rgb, delta, rgb_scope:16))
rgb_scope[ch] = vertex_rgb[k][ch] + delta[ch] * i;
dt_ioppr_rgb_matrix_to_xyz(rgb_scope, XYZ_D50, vs_prof->matrix_in_transposed, vs_prof->lut_in,
vs_prof->unbounded_coeffs_in, vs_prof->lutsize, vs_prof->nonlinearlut);
if(vs_type == DT_LIB_HISTOGRAM_VECTORSCOPE_CIELUV)
if(vs_type != DT_LIB_HISTOGRAM_VECTORSCOPE_RYB)
{
dt_aligned_pixel_t xyY;
dt_XYZ_to_xyY(XYZ_D50, xyY);
dt_xyY_to_Luv(xyY, chromaticity);
dt_ioppr_rgb_matrix_to_xyz(rgb_scope, XYZ_D50, vs_prof->matrix_in_transposed, vs_prof->lut_in,
vs_prof->unbounded_coeffs_in, vs_prof->lutsize, vs_prof->nonlinearlut);
if(vs_type == DT_LIB_HISTOGRAM_VECTORSCOPE_CIELUV)
{
dt_aligned_pixel_t xyY;
dt_XYZ_to_xyY(XYZ_D50, xyY);
dt_xyY_to_Luv(xyY, chromaticity);
}
else
{
dt_aligned_pixel_t XYZ_D65;
dt_XYZ_D50_2_XYZ_D65(XYZ_D50, XYZ_D65);
dt_XYZ_2_JzAzBz(XYZ_D65, chromaticity);
}
dt_XYZ_to_Rec709_D50(XYZ_D50, rgb_display);
}
else
{
dt_aligned_pixel_t XYZ_D65;
dt_XYZ_D50_2_XYZ_D65(XYZ_D50, XYZ_D65);
dt_XYZ_2_JzAzBz(XYZ_D65, chromaticity);
// get the color to be displayed
_ryb2rgb(rgb_scope, rgb_display, d);
const float alpha = M_PI * (0.33333 * ((float)k + (float)i / VECTORSCOPE_HUES));
chromaticity[1] = cosf(alpha) * 0.01;
chromaticity[2] = sinf(alpha) * 0.01;
}
d->hue_ring[k][i][0] = chromaticity[1];
d->hue_ring[k][i][1] = chromaticity[2];
Expand All @@ -373,8 +412,6 @@ static void _lib_histogram_vectorscope_bkgd(dt_lib_histogram_t *d, const dt_iop_
// Try to represent hue in profile colorspace. Do crude gamut
// clipping, and cairo_mesh_pattern_set_corner_color_rgb will
// clamp.
dt_aligned_pixel_t rgb_display;
dt_XYZ_to_Rec709_D50(XYZ_D50, rgb_display);
const float max_RGB = MAX(MAX(rgb_display[0], rgb_display[1]), rgb_display[2]);
for_each_channel(ch, aligned(rgb_display:16))
rgb_display[ch] = rgb_display[ch] / max_RGB;
Expand Down Expand Up @@ -534,30 +571,45 @@ static void _lib_histogram_process_vectorscope(dt_lib_histogram_t *d, const floa
for_each_channel(ch, aligned(px,RGB:16))
RGB[ch] += px[4U * (yy * roi->width + xx) + ch] * 0.25f;

// this goes to the PCS which has standard illuminant D50
dt_ioppr_rgb_matrix_to_xyz(RGB, XYZ_D50, vs_prof->matrix_in_transposed, vs_prof->lut_in,
vs_prof->unbounded_coeffs_in, vs_prof->lutsize, vs_prof->nonlinearlut);
// NOTE: see for comparison/reference rgb_to_JzCzhz() in color_picker.c
if(vs_type == DT_LIB_HISTOGRAM_VECTORSCOPE_CIELUV)
if(vs_type != DT_LIB_HISTOGRAM_VECTORSCOPE_RYB)
{
// FIXME: do have to worry about chromatic adaptation? this assumes that the histogram profile white point is the same as PCS whitepoint (D50) -- if we have a D65 whitepoint profile, how does the result change if we adapt to D65 then convert to L*u*v* with a D65 whitepoint?
dt_aligned_pixel_t xyY_D50;
dt_XYZ_to_xyY(XYZ_D50, xyY_D50);
// using D50 correct u*v* (not u'v') to be relative to the whitepoint (important for vectorscope) and as u*v* is more evenly spaced
dt_xyY_to_Luv(xyY_D50, chromaticity);
// this goes to the PCS which has standard illuminant D50
dt_ioppr_rgb_matrix_to_xyz(RGB, XYZ_D50, vs_prof->matrix_in_transposed, vs_prof->lut_in,
vs_prof->unbounded_coeffs_in, vs_prof->lutsize, vs_prof->nonlinearlut);
// NOTE: see for comparison/reference rgb_to_JzCzhz() in color_picker.c
if(vs_type == DT_LIB_HISTOGRAM_VECTORSCOPE_CIELUV)
{
// FIXME: do have to worry about chromatic adaptation? this assumes that the histogram profile white point is the same as PCS whitepoint (D50) -- if we have a D65 whitepoint profile, how does the result change if we adapt to D65 then convert to L*u*v* with a D65 whitepoint?
dt_aligned_pixel_t xyY_D50;
dt_XYZ_to_xyY(XYZ_D50, xyY_D50);
// using D50 correct u*v* (not u'v') to be relative to the whitepoint (important for vectorscope) and as u*v* is more evenly spaced
dt_xyY_to_Luv(xyY_D50, chromaticity);
}
else if(vs_type == DT_LIB_HISTOGRAM_VECTORSCOPE_JZAZBZ)
{
// FIXME: can skip a hop by pre-multipying matrices: see colorbalancergb and dt_develop_blendif_init_masking_profile() for how to make hacked profile
dt_aligned_pixel_t XYZ_D65;
// If the profile whitepoint is D65, its RGB -> XYZ conversion
// matrix has been adapted to D50 (PCS standard) via
// Bradford. Using Bradford again to adapt back to D65 gives a
// pretty clean reversal of the transform.
// FIXME: if the profile whitepoint is D50 (ProPhoto...), then should we use a nicer adaptation (CAT16?) to D65?
dt_XYZ_D50_2_XYZ_D65(XYZ_D50, XYZ_D65);
// FIXME: The bulk of processing time is spent in the XYZ -> JzAzBz conversion in the 2*3 powf() in X'Y'Z' -> L'M'S'. Making a LUT for these, using _apply_trc() to do powf() work. It only needs to be accurate enough to be about on the right pixel for a diam_px x diam_px plot
dt_XYZ_2_JzAzBz(XYZ_D65, chromaticity);
}
}
else
{
// FIXME: can skip a hop by pre-multipying matrices: see colorbalancergb and dt_develop_blendif_init_masking_profile() for how to make hacked profile
dt_aligned_pixel_t XYZ_D65;
// If the profile whitepoint is D65, its RGB -> XYZ conversion
// matrix has been adapted to D50 (PCS standard) via
// Bradford. Using Bradford again to adapt back to D65 gives a
// pretty clean reversal of the transform.
// FIXME: if the profile whitepoint is D50 (ProPhoto...), then should we use a nicer adaptation (CAT16?) to D65?
dt_XYZ_D50_2_XYZ_D65(XYZ_D50, XYZ_D65);
// FIXME: The bulk of processing time is spent in the XYZ -> JzAzBz conversion in the 2*3 powf() in X'Y'Z' -> L'M'S'. Making a LUT for these, using _apply_trc() to do powf() work. It only needs to be accurate enough to be about on the right pixel for a diam_px x diam_px plot
dt_XYZ_2_JzAzBz(XYZ_D65, chromaticity);
dt_aligned_pixel_t RYB, rgb, HCV;
// gamma corrected sRGB -> linear sRGB
for_each_channel(ch, aligned(rgb, RGB:16))
rgb[ch] = RGB[ch] <= 0.04045f ? RGB[ch] / 12.92f : powf((RGB[ch] + 0.055f) / (1.0f + 0.055f), 2.4f);
_rgb2ryb(rgb, RYB, d);
dt_RGB_2_HCV(RYB, HCV);
const float alpha = 2.0 * M_PI * HCV[0];
chromaticity[1] = cosf(alpha) * HCV[1] * 0.01;
chromaticity[2] = sinf(alpha) * HCV[1] * 0.01;
}
// FIXME: we ignore the L or Jz components -- do they optimize out of the above code, or would in particular a XYZ_2_AzBz but helpful?
if(vs_scale == DT_LIB_HISTOGRAM_SCALE_LOGARITHMIC)
Expand Down Expand Up @@ -933,10 +985,10 @@ static void _lib_histogram_draw_vectorscope(dt_lib_histogram_t *d, cairo_t *cr,
cairo_push_group(cr);
cairo_set_source(cr, bkgd_pat);
cairo_mask(cr, graph_pat);
cairo_set_operator(cr, CAIRO_OPERATOR_HARD_LIGHT);
// cairo_set_operator(cr, CAIRO_OPERATOR_HARD_LIGHT);
cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.55);
cairo_mask(cr, graph_pat);

cairo_pattern_destroy(bkgd_pat);
cairo_surface_destroy(bkgd_surface);
cairo_pattern_destroy(graph_pat);
Expand Down Expand Up @@ -1326,6 +1378,11 @@ static void _vectorscope_view_update(dt_lib_histogram_t *d)
dtgtk_button_set_paint(DTGTK_BUTTON(d->colorspace_button),
dtgtk_cairo_paint_jzazbz, CPF_NONE, NULL);
break;
case DT_LIB_HISTOGRAM_VECTORSCOPE_RYB:
gtk_widget_set_tooltip_text(d->colorspace_button, _("set view to RYB"));
dtgtk_button_set_paint(DTGTK_BUTTON(d->colorspace_button),
dtgtk_cairo_paint_ryb, CPF_NONE, NULL);
break;
case DT_LIB_HISTOGRAM_VECTORSCOPE_N:
dt_unreachable_codepath();
}
Expand Down Expand Up @@ -1771,6 +1828,9 @@ void gui_init(dt_lib_module_t *self)
// initially no vectorscope to draw
d->vectorscope_radius = 0.f;

d->rgb2ryb_ypp = interpolate_set(sizeof(x_vtx)/sizeof(float), (float *)x_vtx, (float *)ryb_y_vtx, CUBIC_SPLINE);
d->ryb2rgb_ypp = interpolate_set(sizeof(x_vtx)/sizeof(float), (float *)x_vtx, (float *)rgb_y_vtx, CUBIC_SPLINE);

// proxy functions and data so that pixelpipe or tether can
// provide data for a histogram
// FIXME: do need to pass self, or can wrap a callback as a lambda
Expand Down Expand Up @@ -1928,7 +1988,8 @@ void gui_cleanup(dt_lib_module_t *self)
dt_free_align(d->vectorscope_graph);
dt_free_align(d->vectorscope_bkgd);
dt_pthread_mutex_destroy(&d->lock);

g_free(d->rgb2ryb_ypp);
g_free(d->ryb2rgb_ypp);
g_free(self->data);
self->data = NULL;
}
Expand Down

0 comments on commit 34a2743

Please sign in to comment.