data/kernels/basic.cl
Rama master · réplica de lectura del repositorio original.
/*
This file is part of darktable,
Copyright (C) 2009-2026 darktable developers.
darktable 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 3 of the License, or
(at your option) any later version.
darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#include "colorspace.h"
#include "color_conversion.h"
#include "common.h"
#include "rgb_norms.h"
#include "lut3d.h"
#include "diffuse.cl"
int
BL(const int row, const int col)
{
return (((row & 1) << 1) + (col & 1));
}
kernel void
rawprepare_1f(read_only image2d_t in, write_only image2d_t out,
const int width, const int height,
const int cx, const int cy,
global const float *sub, global const float *div, global const float *rdiv,
const int rx, const int ry)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pix = read_imageui(in, sampleri, (int2)(x + cx, y + cy)).x;
const int id = BL(ry+cy+y, rx+cx+x);
const float n = pix - sub[id];
const float q = n * rdiv[id];
const float rem = fma(-q, div[id], n);
const float oval = fma(rem, rdiv[id], q);
write_imagef(out, (int2)(x, y), oval);
}
// the gain at map_pt, interpolated as in rawprepare's process(). The
// linear sampler would do the same, but GPUs may compute its weights with
// as little as 8 bits of precision, which shows in the image
static float _gainmap_gain(read_only image2d_t map, const int2 map_size, const float2 map_pt)
{
const float2 pt = clamp(map_pt, (float2)(0.0f, 0.0f), convert_float2(map_size));
const int2 i0 = min(convert_int2(pt), map_size - 1);
const int2 i1 = min(i0 + 1, map_size - 1);
const float2 frac = pt - convert_float2(i0);
const float top = (1.0f - frac.x) * read_imagef(map, sampleri, (int2)(i0.x, i0.y)).x
+ frac.x * read_imagef(map, sampleri, (int2)(i1.x, i0.y)).x;
const float bottom = (1.0f - frac.x) * read_imagef(map, sampleri, (int2)(i0.x, i1.y)).x
+ frac.x * read_imagef(map, sampleri, (int2)(i1.x, i1.y)).x;
return (1.0f - frac.y) * top + frac.y * bottom;
}
kernel void
rawprepare_1f_gainmap(read_only image2d_t in, write_only image2d_t out,
const int width, const int height,
const int cx, const int cy,
global const float *sub, global const float *div, global const float *rdiv,
const int rx, const int ry,
read_only image2d_t map0, read_only image2d_t map1,
read_only image2d_t map2, read_only image2d_t map3,
const int2 map_size, const float2 im_to_rel,
const float2 rel_to_map, const float2 map_origin)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pix = read_imageui(in, sampleri, (int2)(x + cx, y + cy)).x;
const int id = BL(ry+cy+y, rx+cx+x);
const float n = pix - sub[id];
const float q = n * rdiv[id];
const float rem = fma(-q, div[id], n);
float pixel_scaled = fma(rem, rdiv[id], q);
const float2 map_pt = ((float2)(rx+cx+x,ry+cy+y) * im_to_rel - map_origin) * rel_to_map;
switch(id)
{
case 0:
pixel_scaled *= _gainmap_gain(map0, map_size, map_pt);
break;
case 1:
pixel_scaled *= _gainmap_gain(map1, map_size, map_pt);
break;
case 2:
pixel_scaled *= _gainmap_gain(map2, map_size, map_pt);
break;
case 3:
pixel_scaled *= _gainmap_gain(map3, map_size, map_pt);
break;
}
write_imagef(out, (int2)(x, y), pixel_scaled);
}
kernel void
rawprepare_1f_unnormalized(read_only image2d_t in, write_only image2d_t out,
const int width, const int height,
const int cx, const int cy,
global const float *sub, global const float *div, global const float *rdiv,
const int rx, const int ry)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pix = read_imagef(in, sampleri, (int2)(x + cx, y + cy)).x;
const int id = BL(ry+cy+y, rx+cx+x);
const float n = pix - sub[id];
const float q = n * rdiv[id];
const float rem = fma(-q, div[id], n);
const float oval = fma(rem, rdiv[id], q);
write_imagef(out, (int2)(x, y), oval);
}
kernel void
rawprepare_1f_unnormalized_gainmap(read_only image2d_t in, write_only image2d_t out,
const int width, const int height,
const int cx, const int cy,
global const float *sub, global const float *div, global const float *rdiv,
const int rx, const int ry,
read_only image2d_t map0, read_only image2d_t map1,
read_only image2d_t map2, read_only image2d_t map3,
const int2 map_size, const float2 im_to_rel,
const float2 rel_to_map, const float2 map_origin)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pix = read_imagef(in, sampleri, (int2)(x + cx, y + cy)).x;
const int id = BL(ry+cy+y, rx+cx+x);
const float n = pix - sub[id];
const float q = n * rdiv[id];
const float rem = fma(-q, div[id], n);
float pixel_scaled = fma(rem, rdiv[id], q);
const float2 map_pt = ((float2)(rx+cx+x,ry+cy+y) * im_to_rel - map_origin) * rel_to_map;
switch(id)
{
case 0:
pixel_scaled *= _gainmap_gain(map0, map_size, map_pt);
break;
case 1:
pixel_scaled *= _gainmap_gain(map1, map_size, map_pt);
break;
case 2:
pixel_scaled *= _gainmap_gain(map2, map_size, map_pt);
break;
case 3:
pixel_scaled *= _gainmap_gain(map3, map_size, map_pt);
break;
}
write_imagef(out, (int2)(x, y), pixel_scaled);
}
kernel void
rawprepare_4f(read_only image2d_t in, write_only image2d_t out,
const int width, const int height,
const int cx, const int cy,
global const float *sub, global const float *div, global const float *rdiv,
const int rx, const int ry)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 sub4 = (const float4)(sub[0], sub[1], sub[2], sub[3]);
const float4 div4 = (const float4)(div[0], div[1], div[2], div[3]);
const float4 rdiv4 = (const float4)(rdiv[0], rdiv[1], rdiv[2], rdiv[3]);
const float4 pix = readpixel(in, x + cx, y + cy);
const float4 n = pix - sub4;
const float4 q = n * rdiv4;
const float4 rem = fma(-q, div4, n);
float4 oval = fma(rem, rdiv4, q);
oval.w = 0.0f;
write_imagef(out, (int2)(x, y), oval);
}
kernel void
invert_1f(read_only image2d_t in, write_only image2d_t out, const int width, const int height, global float *color,
const unsigned int filters, const int rx, const int ry)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pixel = read_imagef(in, sampleri, (int2)(x, y)).x;
const float inv_pixel = color[FC(ry+y, rx+x, filters)] - pixel;
write_imagef (out, (int2)(x, y), (float4)(clipf(inv_pixel), 0.0f, 0.0f, 0.0f));
}
kernel void
invert_4f(read_only image2d_t in, write_only image2d_t out, const int width, const int height, global float *color,
const unsigned int filters, const int rx, const int ry)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = read_imagef(in, sampleri, (int2)(x, y));
pixel.x = color[0] - pixel.x;
pixel.y = color[1] - pixel.y;
pixel.z = color[2] - pixel.z;
pixel.xyz = clamp(pixel.xyz, 0.0f, 1.0f);
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
whitebalance_1f(read_only image2d_t in, write_only image2d_t out, const int width, const int height, global float *coeffs,
const unsigned int filters, global const unsigned char (*const xtrans)[6])
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float pixel = readsingle(in, x, y);
write_imagef(out, (int2)(x, y), pixel * coeffs[FC(y, x, filters)]);
}
kernel void
whitebalance_1f_xtrans(read_only image2d_t in, write_only image2d_t out, const int width, const int height, global float *coeffs,
const unsigned int filters, global const unsigned char (*const xtrans)[6])
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float pixel = readsingle(in, x, y);
write_imagef(out, (int2)(x, y), pixel * coeffs[FCxtrans(y, x, xtrans)]);
}
kernel void
whitebalance_4f(read_only image2d_t in, write_only image2d_t out, const int width, const int height, global float *coeffs,
const unsigned int filters, global const unsigned char (*const xtrans)[6])
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
write_imagef (out, (int2)(x, y), (float4)(pixel.x * coeffs[0], pixel.y * coeffs[1], pixel.z * coeffs[2], pixel.w));
}
/* kernel for the exposure plugin. should work transparently with float4 and float image2d. */
kernel void
exposure (read_only image2d_t in, write_only image2d_t out, const int width, const int height, const float black, const float scale)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
pixel.xyz = ((pixel - black ) * scale).xyz;
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the highlights plugin. */
kernel void
highlights_4f_clip (read_only image2d_t in, write_only image2d_t out, const int width, const int height, const float clip)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
// 4f/pixel means that this has been debayered already.
// it's thus hopeless to recover highlights here (this code path is just used for preview and non-raw images)
float4 pixel = Areadpixel(in, x, y);
// default: // 0, DT_IOP_HIGHLIGHTS_CLIP
pixel.x = fmin(clip, pixel.x);
pixel.y = fmin(clip, pixel.y);
pixel.z = fmin(clip, pixel.z);
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
highlights_1f_clip (read_only image2d_t in, write_only image2d_t out,
const int iwidth, const int iheight,
const int owidth, const int oheight,
const float4 clip, const int dx, const int dy,
const int filters, global const unsigned char (*const xtrans)[6])
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= owidth || y >= oheight) return;
const int irow = y + dy;
const int icol = x + dx;
float pixel = 0.0f;
float clips[4] = { clip.x, clip.y, clip.z, clip.w};
if((icol >= 0) && (irow >= 0) && (irow < iheight) && (icol < iwidth))
{
const int color = fcol(irow, icol, filters, xtrans);
pixel = Areadsingle(in, icol, irow);
pixel = fmin(clips[color], pixel);
}
write_imagef(out, (int2)(x, y), pixel);
}
kernel void highlights_false_color(read_only image2d_t in,
write_only image2d_t out,
const int owidth,
const int oheight,
const int iwidth,
const int iheight,
const int dx,
const int dy,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
const float4 clip)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= owidth || y >= oheight) return;
const int irow = y + dy;
const int icol = x + dx;
float oval = 0.0f;
float clips[4] = { clip.x, clip.y, clip.z, clip.w};
if((irow >= 0) && (icol >= 0) && (icol < iwidth) && (irow < iheight))
{
const float ival = Areadsingle(in, icol, irow);
const int c = fcol(irow, icol, filters, xtrans);
oval = (ival < clips[c]) ? 0.2f * ival : 1.0f;
}
write_imagef (out, (int2)(x, y), oval);
}
static float _calc_refavg(read_only image2d_t in,
global const unsigned char (*const xtrans)[6],
const unsigned int filters,
int row,
int col,
int maxrow,
int maxcol,
global const float *correction)
{
float mean[3] = { 0.0f, 0.0f, 0.0f };
int cnt[3] = { 0, 0, 0 };
for(int dy = max(0, row - 1); dy < min(maxrow - 1, row + 2); dy++)
{
for(int dx = max(0, col - 1); dx < min(maxcol - 1, col + 2); dx++)
{
const float val = fmax(0.0f, Areadsingle(in, dx, dy));
const int c = fcol(dy, dx, filters, xtrans);
mean[c] += val;
cnt[c] += 1;
}
}
for(int c = 0; c < 3; c++)
mean[c] = cnt[c] > 0 ? cbrt((correction[c] * mean[c]) / (float)cnt[c]) : 0.0f;
float croot_refavg[3] = { 0.5f * (mean[1] + mean[2]), 0.5f * (mean[0] + mean[2]), 0.5f * (mean[0] + mean[1])};
int color = fcol(row, col, filters, xtrans);
return fcube(croot_refavg[color]);
}
kernel void highlights_initmask(read_only image2d_t in,
global char *inmask,
const int msize,
const int mwidth,
const int mheight,
const int iwidth,
const int iheight,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
global const float *clips)
{
const int mcol = get_global_id(0);
const int mrow = get_global_id(1);
if(mcol >= mwidth || mrow >= mheight)
return;
int mdx = mad24(mrow, mwidth, mcol);
char mbuff[3] = { 0, 0, 0 };
int irow = 3*mrow;
int icol = 3*mcol;
for(int y = irow; y < irow + 3; y++)
{
for(int x = icol; x < icol + 3; x++)
{
if(y < iheight && x < iwidth)
{
int color = fcol(y, x, filters, xtrans);
float val = Areadsingle(in, x, y);
mbuff[color] += (val >= clips[color]) ? 1 : 0;
}
}
}
for(int c = 0; c < 3; c++)
inmask[c*msize + mdx] = mbuff[c] ? 1 : 0;
}
static char _mask_dilated(global char *in, const int w1)
{
if(in[0])
return 1;
if(in[-w1-1] | in[-w1] | in[-w1+1] | in[-1] | in[1] | in[w1-1] | in[w1] | in[w1+1])
return 1;
const int w2 = 2*w1;
const int w3 = 3*w1;
return (in[-w3-2] | in[-w3-1] | in[-w3] | in[-w3+1] | in[-w3+2] |
in[-w2-3] | in[-w2-2] | in[-w2-1] | in[-w2] | in[-w2+1] | in[-w2+2] | in[-w2+3] |
in[-w1-3] | in[-w1-2] | in[-w1+2] | in[-w1+3] |
in[-3] | in[-2] | in[2] | in[3] |
in[w1-3] | in[w1-2] | in[w1+2] | in[w1+3] |
in[w2-3] | in[w2-2] | in[w2-1] | in[w2] | in[w2+1] | in[w2+2] | in[w2+3] |
in[w3-2] | in[w3-1] | in[w3] | in[w3+1] | in[w3+2]) ? 1 : 0;
}
kernel void highlights_dilatemask(global char *in,
global char *out,
const int msize,
const int mwidth,
const int mheight)
{
const int col = get_global_id(0);
const int row = get_global_id(1);
if(col >= mwidth || row >= mheight)
return;
int mx = mad24(row, mwidth, col);
bool safe = col >= 3 && row >= 3 && col < mwidth - 3 && row < mheight - 3;
out[mx] = safe ? _mask_dilated(in + mx, mwidth) : in[mx];
out[mx+msize] = safe ? _mask_dilated(in + mx + msize, mwidth) : in[mx + msize];
out[mx+2*msize] = safe ? _mask_dilated(in + mx + 2*msize, mwidth) : in[mx + 2*msize];
}
kernel void highlights_opposed(read_only image2d_t in,
write_only image2d_t out,
const int owidth,
const int oheight,
const int iwidth,
const int iheight,
const int dx,
const int dy,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
global const float *clips,
global const float *chroma,
global const float *correction)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= owidth || y >= oheight) return;
int irow = y + dy;
int icol = x + dx;
float val = 0.0f;
if((icol >= 0) && (icol < iwidth) && (irow >= 0) && (irow < iheight))
{
val = Areadsingle(in, icol, irow);
const int color = fcol(irow, icol, filters, xtrans);
if(val >= clips[color])
{
const float ref = _calc_refavg(in, xtrans, filters, irow, icol, iheight, iwidth, correction);
val = fmax(val, ref + chroma[color]);
}
}
write_imagef(out, (int2)(x, y), val);
}
// linear raws, see _process_linear_opposed() in src/iop/hlreconstruct/opposed.c
static float _calc_linear_refavg(const float4 pixel, const int color)
{
const float ins[3] = { cbrt(fmax(0.0f, pixel.x)), cbrt(fmax(0.0f, pixel.y)), cbrt(fmax(0.0f, pixel.z)) };
const float opp[3] = { 0.5f*(ins[1]+ins[2]), 0.5f*(ins[0]+ins[2]), 0.5f*(ins[0]+ins[1]) };
return fcube(opp[color]);
}
// a mask cell covers 3x3 pixels; the last row and column of cells also take
// the pixels left over by the division, as _raw_to_cmap() clamps to them
kernel void highlights_initmask_linear(read_only image2d_t in,
global char *inmask,
const int msize,
const int mwidth,
const int mheight,
const int iwidth,
const int iheight,
const float4 clips)
{
const int mcol = get_global_id(0);
const int mrow = get_global_id(1);
if(mcol >= mwidth || mrow >= mheight)
return;
const int irow = 3*mrow;
const int icol = 3*mcol;
const int rowend = mrow == mheight - 1 ? iheight : irow + 3;
const int colend = mcol == mwidth - 1 ? iwidth : icol + 3;
char mbuff[3] = { 0, 0, 0 };
for(int y = irow; y < rowend; y++)
{
for(int x = icol; x < colend; x++)
{
const float4 val = readpixel(in, x, y);
mbuff[0] |= val.x >= clips.x;
mbuff[1] |= val.y >= clips.y;
mbuff[2] |= val.z >= clips.z;
}
}
const int mdx = mad24(mrow, mwidth, mcol);
for(int c = 0; c < 3; c++)
inmask[c*msize + mdx] = mbuff[c] ? 1 : 0;
}
kernel void highlights_opposed_linear(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const float4 clips,
const float4 chroma)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 pixel = readpixel(in, x, y);
const float clip[3] = { clips.x, clips.y, clips.z };
const float chr[3] = { chroma.x, chroma.y, chroma.z };
const float inval[3] = { fmax(0.0f, pixel.x), fmax(0.0f, pixel.y), fmax(0.0f, pixel.z) };
float oval[3];
for(int c = 0; c < 3; c++)
oval[c] = inval[c] >= clip[c] ? fmax(inval[c], _calc_linear_refavg(pixel, c) + chr[c]) : inval[c];
write_imagef(out, (int2)(x, y), (float4)(oval[0], oval[1], oval[2], pixel.w));
}
#define SQRT3 1.7320508075688772935274463415058723669f
#define SQRT12 3.4641016151377545870548926830117447339f // 2*SQRT3
kernel void
highlights_1f_lch_bayer (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const float clip, const int filters)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
int clipped = 0;
float R = 0.0f;
float Gmin = FLT_MAX;
float Gmax = -FLT_MAX;
float B = 0.0f;
float pixel = 0.0f;
// sample 1 bayer block. thus we will have 2 green values.
for(int jj = 0; jj <= 1; jj++)
{
for(int ii = 0; ii <= 1; ii++)
{
const float val = readsingle(in, x+ii, y+jj);
pixel = (ii == 0 && jj == 0) ? val : pixel;
clipped = (clipped || (val > clip));
const int c = FC(y + jj, x + ii, filters);
switch(c)
{
case 0:
R = val;
break;
case 1:
Gmin = fmin(Gmin, val);
Gmax = fmax(Gmax, val);
break;
case 2:
B = val;
break;
}
}
}
if(clipped)
{
const float Ro = fmin(R, clip);
const float Go = fmin(Gmin, clip);
const float Bo = fmin(B, clip);
const float L = (R + Gmax + B) / 3.0f;
float C = SQRT3 * (R - Gmax);
float H = 2.0f * B - Gmax - R;
const float Co = SQRT3 * (Ro - Go);
const float Ho = 2.0f * Bo - Go - Ro;
const float ratio = (R != Gmax && Gmax != B) ? sqrt((Co * Co + Ho * Ho) / (C * C + H * H)) : 1.0f;
C *= ratio;
H *= ratio;
/*
* backtransform proof, sage:
*
* R,G,B,L,C,H = var('R,G,B,L,C,H')
* solve([L==(R+G+B)/3, C==sqrt(3)*(R-G), H==2*B-G-R], R, G, B)
*
* result:
* [[R == 1/6*sqrt(3)*C - 1/6*H + L, G == -1/6*sqrt(3)*C - 1/6*H + L, B == 1/3*H + L]]
*/
const int c = FC(y, x, filters);
C = (c == 1) ? -C : C;
pixel = L;
pixel += (c == 2) ? H / 3.0f : -H / 6.0f + C / SQRT12;
}
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
highlights_1f_lch_xtrans (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const float clip, global const unsigned char (*const xtrans)[6],
local float *buffer)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int xlsz = get_local_size(0);
const int ylsz = get_local_size(1);
const int xlid = get_local_id(0);
const int ylid = get_local_id(1);
const int xgid = get_group_id(0);
const int ygid = get_group_id(1);
// individual control variable in this work group and the work group size
const int l = mad24(ylid, xlsz, xlid);
const int lsz = mul24(xlsz, ylsz);
// stride and maximum capacity of local buffer
// cells of 1*float per pixel with a surrounding border of 2 cells
const int stride = xlsz + 2*2;
const int maxbuf = mul24(stride, ylsz + 2*2);
// coordinates of top left pixel of buffer
// this is 2 pixel left and above of the work group origin
const int xul = mul24(xgid, xlsz) - 2;
const int yul = mul24(ygid, ylsz) - 2;
// populate local memory buffer
for(int n = 0; n <= maxbuf/lsz; n++)
{
const int bufidx = mad24(n, lsz, l);
if(bufidx >= maxbuf) continue;
const int xx = xul + bufidx % stride;
const int yy = yul + bufidx / stride;
buffer[bufidx] = readsingle(in, xx, yy);
}
// center buffer around current x,y-Pixel
buffer += mad24(ylid + 2, stride, xlid + 2);
barrier(CLK_LOCAL_MEM_FENCE);
if(x >= width || y >= height) return;
float pixel = 0.0f;
if(x < 2 || x > width - 3 || y < 2 || y > height - 3)
{
// fast path for border
pixel = fmin(clip, buffer[0]);
}
else
{
// if current pixel is clipped, always reconstruct
int clipped = (buffer[0] > clip);
if(!clipped)
{
clipped = 1;
// check if there is any 3x3 block touching the current
// pixel which has no clipping, as then we don't need to
// reconstruct the current pixel. This avoids zippering in
// edge transitions from clipped to unclipped areas. The
// X-Trans sensor seems prone to this, unlike Bayer, due
// to its irregular pattern.
for(int offset_j = -2; offset_j <= 0; offset_j++)
{
for(int offset_i = -2; offset_i <= 0; offset_i++)
{
if(clipped)
{
clipped = 0;
for(int jj = offset_j; jj <= offset_j + 2; jj++)
{
for(int ii = offset_i; ii <= offset_i + 2; ii++)
{
const float val = buffer[mad24(jj, stride, ii)];
clipped = (clipped || (val > clip));
}
}
}
}
}
}
if(clipped)
{
float mean[3] = { 0.0f, 0.0f, 0.0f };
int cnt[3] = { 0, 0, 0 };
float RGBmax[3] = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for(int jj = -1; jj <= 1; jj++)
{
for(int ii = -1; ii <= 1; ii++)
{
const float val = buffer[mad24(jj, stride, ii)];
const int c = FCxtrans(y + jj, x + ii, xtrans);
mean[c] += val;
cnt[c]++;
RGBmax[c] = fmax(RGBmax[c], val);
}
}
const float Ro = fmin(mean[0]/cnt[0], clip);
const float Go = fmin(mean[1]/cnt[1], clip);
const float Bo = fmin(mean[2]/cnt[2], clip);
const float R = RGBmax[0];
const float G = RGBmax[1];
const float B = RGBmax[2];
const float L = (R + G + B) / 3.0f;
float C = SQRT3 * (R - G);
float H = 2.0f * B - G - R;
const float Co = SQRT3 * (Ro - Go);
const float Ho = 2.0f * Bo - Go - Ro;
if(R != G && G != B)
{
const float ratio = sqrt((Co * Co + Ho * Ho) / (C * C + H * H));
C *= ratio;
H *= ratio;
}
float RGB[3] = { 0.0f, 0.0f, 0.0f };
RGB[0] = L - H / 6.0f + C / SQRT12;
RGB[1] = L - H / 6.0f - C / SQRT12;
RGB[2] = L + H / 3.0f;
pixel = RGB[FCxtrans(y, x, xtrans)];
}
else
pixel = buffer[0];
}
write_imagef (out, (int2)(x, y), pixel);
}
#undef SQRT3
#undef SQRT12
kernel void
interpolate_and_mask(read_only image2d_t input,
write_only image2d_t interpolated,
write_only image2d_t clipping_mask,
global const float *clips,
global const float *wb,
const unsigned int filters,
const int width,
const int height)
{
// Bilinear interpolation
const int j = get_global_id(0); // = x
const int i = get_global_id(1); // = y
if(j >= width || i >= height) return;
/** On current mesa we have to use one sampler in one kernel!! */
const float center = fmax(0.0f, Areadsingle(input, j, i));
const int c = FC(i, j, filters);
float R = 0.f;
float G = 0.f;
float B = 0.f;
int R_clipped = 0;
int G_clipped = 0;
int B_clipped = 0;
if(i == 0 || j == 0 || i == height - 1 || j == width - 1)
{
// We are on the image edges. We don't need to demosaic,
// just set R = G = B = center and record clipping.
// This will introduce a marginal error close to edges, mostly irrelevant
// because we are dealing with local averages anyway, later on.
// Also we remosaic the image at the end, so only the relevant channel gets picked.
// Finally, it's unlikely that the borders of the image get clipped due to vignetting.
R = G = B = center;
R_clipped = G_clipped = B_clipped = (center > clips[c]);
}
else
{
// fetch neighbours and cache them for perf
const size_t i_prev = (i - 1);
const size_t i_next = (i + 1);
const size_t j_prev = (j - 1);
const size_t j_next = (j + 1);
const float north = Areadsingle(input, j, i_prev);
const float south = Areadsingle(input, j, i_next);
const float west = Areadsingle(input, j_prev, i);
const float east = Areadsingle(input, j_next, i);
const float north_east = Areadsingle(input, j_next, i_prev);
const float north_west = Areadsingle(input, j_prev, i_prev);
const float south_east = Areadsingle(input, j_next, i_next);
const float south_west = Areadsingle(input, j_prev, i_next);
if(c == GREEN) // green pixel
{
G = center;
G_clipped = (center > clips[GREEN]);
}
else // non-green pixel
{
// interpolate inside an X/Y cross
G = (north + south + east + west) / 4.f;
G_clipped = (north > clips[GREEN] || south > clips[GREEN] || east > clips[GREEN] || west > clips[GREEN]);
}
if(c == RED ) // red pixel
{
R = center;
R_clipped = (center > clips[RED]);
}
else // non-red pixel
{
if(FC(i - 1, j, filters) == RED && FC(i + 1, j, filters) == RED)
{
// we are on a red column, so interpolate column-wise
R = (north + south) / 2.f;
R_clipped = (north > clips[RED] || south > clips[RED]);
}
else if(FC(i, j - 1, filters) == RED && FC(i, j + 1, filters) == RED)
{
// we are on a red row, so interpolate row-wise
R = (west + east) / 2.f;
R_clipped = (west > clips[RED] || east > clips[RED]);
}
else
{
// we are on a blue row, so interpolate inside a square
R = (north_west + north_east + south_east + south_west) / 4.f;
R_clipped = (north_west > clips[RED] || north_east > clips[RED] || south_west > clips[RED]
|| south_east > clips[RED]);
}
}
if(c == BLUE ) // blue pixel
{
B = center;
B_clipped = (center > clips[BLUE]);
}
else // non-blue pixel
{
if(FC(i - 1, j, filters) == BLUE && FC(i + 1, j, filters) == BLUE)
{
// we are on a blue column, so interpolate column-wise
B = (north + south) / 2.f;
B_clipped = (north > clips[BLUE] || south > clips[BLUE]);
}
else if(FC(i, j - 1, filters) == BLUE && FC(i, j + 1, filters) == BLUE)
{
// we are on a red row, so interpolate row-wise
B = (west + east) / 2.f;
B_clipped = (west > clips[BLUE] || east > clips[BLUE]);
}
else
{
// we are on a red row, so interpolate inside a square
B = (north_west + north_east + south_east + south_west) / 4.f;
B_clipped = (north_west > clips[BLUE] || north_east > clips[BLUE] || south_west > clips[BLUE]
|| south_east > clips[BLUE]);
}
}
}
const float4 RGB = {R, G, B, dtcl_sqrt(R * R + G * G + B * B) };
const float4 clipped = { R_clipped ? 1.0f : 0.0f, G_clipped ? 1.0f : 0.0f, B_clipped ? 1.0f : 0.0f, (R_clipped || G_clipped || B_clipped) ? 1.0f : 0.0f };
const float4 WB4 = { wb[0], wb[1], wb[2], 1.0f };
write_imagef(interpolated, (int2)(j, i), RGB / WB4);
write_imagef(clipping_mask, (int2)(j, i), clipped);
}
kernel void
remosaic_and_replace(read_only image2d_t input,
read_only image2d_t interpolated,
read_only image2d_t clipping_mask,
write_only image2d_t output,
global const float *wb,
const unsigned int filters,
const int width,
const int height)
{
// Take RGB ratios and norm, reconstruct RGB and remosaic the image
const int j = get_global_id(0); // = x
const int i = get_global_id(1); // = y
if(j >= width || i >= height) return;
const int c = FC(i, j, filters);
const float4 center = Areadpixel(interpolated, j, i);
const float4 clipmask = Areadpixel(clipping_mask, j, i);
const float opacity = isfinite(clipmask.w) ? clipf(clipmask.w) : 0.0f;
const float pix_in = readsingle(input, j, i);
const float corrections[4] = { center.x * wb[0], center.y * wb[1], center.z * wb[2], 0.0f};
const float correction = fmax(corrections[c], 0.0f);
write_imagef(output, (int2)(j, i), (float4)(mix(pix_in, correction, opacity), 0.0f, 0.0f, 0.0f));
}
kernel void
box_blur_5x5(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 acc = 0.f;
for(int ii = -2; ii < 3; ++ii)
{
for(int jj = -2; jj < 3; ++jj)
{
const int row = clamp(y + ii, 0, height - 1);
const int col = clamp(x + jj, 0, width - 1);
acc += fmax(0.0f, Areadpixel(in, col, row));
}
}
acc *= 0.04f;
write_imagef(out, (int2)(x, y), acc);
}
enum wavelets_scale_t
{
ANY_SCALE = 1 << 0, // any wavelets scale : reconstruct += HF
FIRST_SCALE = 1 << 1, // first wavelets scale : reconstruct = 0
LAST_SCALE = 1 << 2, // last wavelets scale : reconstruct += residual
};
kernel void
guide_laplacians(read_only image2d_t HF,
read_only image2d_t LF,
read_only image2d_t mask,
read_only image2d_t output_r,
write_only image2d_t output_w,
const int width,
const int height,
const int mult,
const float noise_level,
const int salt,
const unsigned int scale,
const float radius_sq)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float alpha = read_imagef(mask, sampleri, (int2)(x, y)).w;
const float alpha_comp = 1.f - alpha;
float4 high_frequency = readpixel(HF, x, y);
float4 out;
if(alpha > 0.f) // reconstruct
{
// non-local neighbours coordinates
const int j_neighbours[3] = { max(x - mult, 0), x, min(x + mult, width - 1) };
const int i_neighbours[3] = { max(y - mult, 0), y, min(y + mult, height - 1) };
// fetch non-local pixels and store them locally and contiguously
float4 neighbour_pixel_HF[9];
neighbour_pixel_HF[3 * 0 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[0]));
neighbour_pixel_HF[3 * 0 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[0]));
neighbour_pixel_HF[3 * 0 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[0]));
neighbour_pixel_HF[3 * 1 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[1]));
neighbour_pixel_HF[3 * 1 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[1]));
neighbour_pixel_HF[3 * 1 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[1]));
neighbour_pixel_HF[3 * 2 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[2]));
neighbour_pixel_HF[3 * 2 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[2]));
neighbour_pixel_HF[3 * 2 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[2]));
// Compute the linear fit of the laplacian of chromaticity against the laplacian of the norm
// that is the chromaticity filter guided by the norm
// Get the local average per channel
const float ninth = 1.0f / 9.0f;
float4 means_HF = 0.f;
for(int k = 0; k < 9; k++)
{
means_HF += neighbour_pixel_HF[k];
}
means_HF *= ninth;
// Get the local variance per channel
float4 variance_HF = 0.f;
for(int k = 0; k < 9; k++)
{
variance_HF += sqf(neighbour_pixel_HF[k] - means_HF);
}
variance_HF *= ninth;
// Find the channel most likely to contain details = max( variance(HF) )
// But since OpenCL is not designed to iterate over float4,
// we need to check each channel in sequence
int guiding_channel_HF = ALPHA;
float guiding_value_HF = 0.f;
if(variance_HF.x > guiding_value_HF)
{
guiding_value_HF = variance_HF.x;
guiding_channel_HF = RED;
}
if(variance_HF.y > guiding_value_HF)
{
guiding_value_HF = variance_HF.y;
guiding_channel_HF = GREEN;
}
if(variance_HF.z > guiding_value_HF)
{
guiding_value_HF = variance_HF.z;
guiding_channel_HF = BLUE;
}
// Extract the guiding values for HF and LF now
// so we can proceed after with vectorized code
float means_HF_guide = 0.f;
float variance_HF_guide = 0.f;
float channel_guide_HF[9];
float high_frequency_guide = 0.f;
if(guiding_channel_HF == RED)
{
means_HF_guide = means_HF.x;
variance_HF_guide = variance_HF.x;
high_frequency_guide = high_frequency.x;
for(int k = 0; k < 9; k++) channel_guide_HF[k] = neighbour_pixel_HF[k].x;
}
else if(guiding_channel_HF == GREEN)
{
means_HF_guide = means_HF.y;
variance_HF_guide = variance_HF.y;
high_frequency_guide = high_frequency.y;
for(int k = 0; k < 9; k++) channel_guide_HF[k] = neighbour_pixel_HF[k].y;
}
else // BLUE
{
means_HF_guide = means_HF.z;
variance_HF_guide = variance_HF.z;
high_frequency_guide = high_frequency.z;
for(int k = 0; k < 9; k++) channel_guide_HF[k] = neighbour_pixel_HF[k].z;
}
// Compute the linear regression channel = f(guide)
float4 covariance_HF = 0.f;
for(int k = 0; k < 9; k++)
{
covariance_HF += (neighbour_pixel_HF[k] - means_HF)
* (channel_guide_HF[k] - means_HF_guide);
}
covariance_HF *= ninth;
const float scale_multiplier = 1.f / radius_sq;
const float4 alpha_ch = readpixel(mask, x, y);
const float4 a_HF = fmax(covariance_HF / variance_HF_guide, 0.f);
const float4 b_HF = means_HF - a_HF * means_HF_guide;
// Guide all channels by the norms
high_frequency = alpha_ch * scale_multiplier * (a_HF * high_frequency_guide + b_HF)
+ (1.f - alpha_ch * scale_multiplier) * high_frequency;
}
if(scale & FIRST_SCALE)
{
// out is not inited yet
out = high_frequency;
}
else
{
// just accumulate HF
out = read_imagef(output_r, sampleri, (int2)(x, y)) + high_frequency;
}
if(scale & LAST_SCALE)
{
// add the residual and clamp
out = fmax(out + readpixel(LF, x, y), (float4)0.f);
}
// Last step of RGB reconstruct : add noise
if((scale & LAST_SCALE) && salt && alpha > 0.f)
{
// Init random number generator
unsigned int state[4] = { splitmix32(x + 1), splitmix32((x + 1) * (y + 3)), splitmix32(1337), splitmix32(666) };
xoshiro128plus(state);
xoshiro128plus(state);
xoshiro128plus(state);
xoshiro128plus(state);
// Model noise on the max RGB
const float4 sigma = out * noise_level;
float4 noise = dt_noise_generator_simd(DT_NOISE_POISSONIAN, out, sigma, state);
// Ensure the noise only brightens the image, since it's clipped
noise = out + fabs(noise - out);
out = fmax(alpha * noise + alpha_comp * out, 0.f);
}
if(scale & LAST_SCALE)
{
// Break the RGB channels into ratios/norm for the next step of reconstruction
const float4 out_2 = out * out;
const float norm = fmax(sqrt(out_2.x + out_2.y + out_2.z), 1e-6f);
out /= norm;
out.w = norm;
}
write_imagef(output_w, (int2)(x, y), out);
}
kernel void
diffuse_color(read_only image2d_t HF,
read_only image2d_t LF,
read_only image2d_t mask,
read_only image2d_t output_r,
write_only image2d_t output_w,
const int width,
const int height,
const int mult,
const unsigned int scale,
const float first_order_factor)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 alpha = readpixel(mask, x, y);
float4 high_frequency = readpixel(HF, x, y);
const float norm_backup = high_frequency.w;
float4 out;
if(alpha.w > 0.f) // reconstruct
{
// non-local neighbours coordinates
const int j_neighbours[3] = { max(x - mult, 0), x, min(x + mult, width - 1) };
const int i_neighbours[3] = { max(y - mult, 0), y, min(y + mult, height - 1) };
// fetch non-local pixels and store them locally and contiguously
float4 neighbour_pixel_HF[9];
neighbour_pixel_HF[3 * 0 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[0]));
neighbour_pixel_HF[3 * 0 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[0]));
neighbour_pixel_HF[3 * 0 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[0]));
neighbour_pixel_HF[3 * 1 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[1]));
neighbour_pixel_HF[3 * 1 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[1]));
neighbour_pixel_HF[3 * 1 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[1]));
neighbour_pixel_HF[3 * 2 + 0] = read_imagef(HF, samplerA, (int2)(j_neighbours[0], i_neighbours[2]));
neighbour_pixel_HF[3 * 2 + 1] = read_imagef(HF, samplerA, (int2)(j_neighbours[1], i_neighbours[2]));
neighbour_pixel_HF[3 * 2 + 2] = read_imagef(HF, samplerA, (int2)(j_neighbours[2], i_neighbours[2]));
float4 update = 0.f;
// Compute the laplacian in the direction parallel to the steepest gradient on the norm
float anisotropic_kernel_isophote[9] = { 0.25f, 0.5f, 0.25f, 0.5f, -3.f, 0.5f, 0.25f, 0.5f, 0.25f };
// Convolve the filter to get the laplacian
float4 laplacian_HF = 0.f;
for(int k = 0; k < 9; k++)
{
laplacian_HF += neighbour_pixel_HF[k] * anisotropic_kernel_isophote[k];
}
// Diffuse
const float4 multipliers_HF = { 1.f / B_SPLINE_TO_LAPLACIAN, 1.f / B_SPLINE_TO_LAPLACIAN, 1.f / B_SPLINE_TO_LAPLACIAN, 0.f };
high_frequency += alpha * multipliers_HF * (laplacian_HF - first_order_factor * high_frequency);
high_frequency.w = norm_backup;
}
if(scale & FIRST_SCALE)
{
// out is not inited yet
out = high_frequency;
}
else
{
// just accumulate HF
out = readpixel(output_r, x, y) + high_frequency;
}
if(scale & LAST_SCALE)
{
// add the residual and clamp
out = fmax(out + readpixel(LF, x, y), (float4)0.f);
// renormalize ratios
if(alpha.w > 0.f)
{
const float4 out_sq = sqf(out);
const float norm = sqrt(out_sq.x + out_sq.y + out_sq.z);
if(norm > 1e-4f) out.xyz /= norm;
}
// Last scale : reconstruct RGB from ratios and norm - norm stays in the 4th channel
// we need it to evaluate the gradient
out.xyz *= out.w;
}
write_imagef(output_w, (int2)(x, y), out);
}
float
lookup_unbounded_twosided(read_only image2d_t lut, const float x, constant float *a)
{
// in case the tone curve is marked as linear, return the fast
// path to linear unbounded (does not clip x at 1)
if(a[0] >= 0.0f)
{
const float ar = 1.0f/a[0];
const float al = 1.0f - 1.0f/a[3];
if(x < ar && x >= al)
{
// lut lookup
const int xi = clamp((int)(x * 0x10000ul), 0, 0xffff);
const int2 p = (int2)((xi & 0xff), (xi >> 8));
return read_imagef(lut, sampleri, p).x;
}
else
{
// two-sided extrapolation (with inverted x-axis for left side)
const float xx = (x >= ar) ? x : 1.0f - x;
constant float *aa = (x >= ar) ? a : a + 3;
return aa[1] * dtcl_pow(xx*aa[0], aa[2]);
}
}
else return x;
}
float
lerp_lookup_unbounded0(read_only image2d_t lut, const float x, global const float *a)
{
// in case the tone curve is marked as linear, return the fast
// path to linear unbounded (does not clip x at 1)
if(a[0] >= 0.0f)
{
if(x < 1.0f)
{
const float ft = clamp(x * (float)0xffff, 0.0f, (float)0xffff);
const int t = ft < 0xfffe ? ft : 0xfffe;
const float f = ft - t;
const int2 p1 = (int2)((t & 0xff), (t >> 8));
const int2 p2 = (int2)(((t + 1) & 0xff), ((t + 1) >> 8));
const float l1 = read_imagef(lut, sampleri, p1).x;
const float l2 = read_imagef(lut, sampleri, p2).x;
return l1 * (1.0f - f) + l2 * f;
}
else return a[1] * dtcl_pow(x*a[0], a[2]);
}
else return x;
}
static float4 _lookup_hsm(const float4 hsv,
const int hue_div,
const int sat_div,
const int val_div,
global const float *hsm)
{
const float h = (hsv.x - floor(hsv.x)) * (float)hue_div;
const float s = clipf(hsv.y) * (float)(sat_div-1);
const float v = clipf(hsv.z) * (float)(val_div-1);
const int hi[2] = { min((int)h, hue_div-1), (min((int)h, hue_div-1)+1) % hue_div };
const int si[2] = { min((int)s, sat_div-1), min((int)s+1, sat_div-1) };
const int vi[2] = { min((int)v, val_div-1), min((int)v+1, val_div-1) };
const float hf = clipf(h - (float)hi[0]);
const float sf = clipf(s - (float)si[0]);
const float vf = clipf(v - (float)vi[0]);
float4 correction = (float4)0.0f;
for(int z = 0; z < 2; z++)
for(int y = 0; y < 2; y++)
for(int x = 0; x < 2; x++)
{
// DNG stores saturation first, then hue, then value, with three floats per cell
const int index = 3 * ((vi[z] * hue_div + hi[y]) * sat_div + si[x]);
const float weight = (z ? vf : 1.0f - vf)
* (y ? hf : 1.0f - hf)
* (x ? sf : 1.0f - sf);
correction.x += weight * hsm[index + 0];
correction.y += weight * hsm[index + 1];
correction.z += weight * hsm[index + 2];
}
return correction;
}
static float _srgb_to_linear(const float x)
{
return x <= 0.04045f ? x / 12.92f : dtcl_pow((x + 0.055f) / 1.055f, 2.4f);
}
static float _linear_to_srgb(const float x)
{
return x <= 0.0031308f ? 12.92f * x : 1.055f * dtcl_pow(x, 1.0f / 2.4f) - 0.055f;
}
/* kernel for the plugin colorin: unbound processing */
kernel void
colorin_unbound (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
global float *cmat, global float *lmat,
read_only image2d_t lutr, read_only image2d_t lutg, read_only image2d_t lutb,
const int blue_mapping, global const float (*const a)[3], global const float *corr,
const float scale, const int hue_div, const int sat_div, const int val_div, global const float *dev_hsm, const int encoding, const int use_hsm, const int hdr_transfer)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 corval = (const float4)(corr[0], corr[1], corr[2], corr[3]);
float4 pixel = corval * readpixel(in, x, y);
const float alpha = pixel.w;
float cam[3], XYZ[3];
if(hdr_transfer)
{
cam[0] = pixel.x;
cam[1] = pixel.y;
cam[2] = pixel.z;
dt_hdr_decode(cam, hdr_transfer);
}
else
{
cam[0] = lerp_lookup_unbounded0(lutr, pixel.x, a[0]);
cam[1] = lerp_lookup_unbounded0(lutg, pixel.y, a[1]);
cam[2] = lerp_lookup_unbounded0(lutb, pixel.z, a[2]);
}
if(blue_mapping)
{
const float YY = cam[0] + cam[1] + cam[2];
if(YY > 0.0f)
{
// manual gamut mapping. these values cause trouble when converting back from Lab to sRGB:
const float zz = cam[2] / YY;
// lower amount and higher bound_z make the effect smaller.
// the effect is weakened the darker input values are, saturating at bound_Y
const float bound_z = 0.5f, bound_Y = 0.8f;
const float amount = 0.11f;
if (zz > bound_z)
{
const float t = (zz - bound_z) / (1.0f - bound_z) * fmin(1.0f, YY / bound_Y);
cam[1] += t * amount;
cam[2] -= t * amount;
}
}
}
// now convert camera to XYZ using the color matrix
for(int j=0;j<3;j++)
{
XYZ[j] = 0.0f;
for(int i=0;i<3;i++) XYZ[j] += cmat[3*j+i] * cam[i];
}
float4 xyz = (float4)(XYZ[0], XYZ[1], XYZ[2], 0.0f);
pixel.xyz = XYZ_to_Lab(xyz).xyz;
if(use_hsm)
{
float4 prophoto_rgb = Lab_to_prophotorgb(pixel) / (float4)scale;
float4 hsv = RGB_2_HSV(prophoto_rgb);
// Encode valueV for table lookup if needed
float v_encoded = hsv.z;
if(encoding == 1) // sRGB
v_encoded = _linear_to_srgb(clipf(v_encoded));
// Use encoded value for table lookup (need to temporarily set hsv[2])
float4 hsv_for_lookup = hsv;
hsv_for_lookup.z = v_encoded;
float4 correction = _lookup_hsm(hsv_for_lookup, hue_div, sat_div, val_div, dev_hsm);
hsv.x += correction.x / 360.0f;
hsv.x -= floor(hsv.x); // stay within the 360° hue circle
hsv.y = clipf(hsv.y * correction.y);
float v_corrected = v_encoded * correction.z;
if(encoding == 1) // sRGB
v_corrected = _srgb_to_linear(v_corrected);
hsv.z = clipf(v_corrected);
prophoto_rgb = HSV_2_RGB(hsv) * (float4)scale;
pixel = prophotorgb_to_Lab(prophoto_rgb);
}
pixel.w = alpha;
write_imagef(out, (int2)(x, y), pixel);
}
/* kernel for the plugin colorin: the LittleCMS transform baked into a 3D LUT
over input values 0..1, see _bake_clut() */
kernel void
colorin_clut(read_only image2d_t in, write_only image2d_t out, const int width, const int height,
global const float *clut, const int level, global const float *corr)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 corval = (const float4)(corr[0], corr[1], corr[2], corr[3]);
const float4 raw = readpixel(in, x, y);
float4 pixel = dt_lut3d_tetrahedral(clip4(corval * raw), clut, level);
pixel.w = raw.w;
write_imagef(out, (int2)(x, y), pixel);
}
/* kernel for the plugin colorin: with clipping */
kernel void
colorin_clipping (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
global float *cmat, global float *lmat,
read_only image2d_t lutr, read_only image2d_t lutg, read_only image2d_t lutb,
const int blue_mapping, global const float (*const a)[3], global const float *corr,
const float scale, const int hue_div, const int sat_div, const int val_div, global const float *dev_hsm, const int encoding, const int use_hsm, const int hdr_transfer)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 corval = (const float4)(corr[0], corr[1], corr[2], corr[3]);
float4 pixel = corval * readpixel(in, x, y);
const float alpha = pixel.w;
float cam[3], RGB[3], XYZ[3];
if(hdr_transfer)
{
cam[0] = pixel.x;
cam[1] = pixel.y;
cam[2] = pixel.z;
dt_hdr_decode(cam, hdr_transfer);
}
else
{
cam[0] = lerp_lookup_unbounded0(lutr, pixel.x, a[0]);
cam[1] = lerp_lookup_unbounded0(lutg, pixel.y, a[1]);
cam[2] = lerp_lookup_unbounded0(lutb, pixel.z, a[2]);
}
if(blue_mapping)
{
const float YY = cam[0] + cam[1] + cam[2];
if(YY > 0.0f)
{
// manual gamut mapping. these values cause trouble when converting back from Lab to sRGB:
const float zz = cam[2] / YY;
// lower amount and higher bound_z make the effect smaller.
// the effect is weakened the darker input values are, saturating at bound_Y
const float bound_z = 0.5f, bound_Y = 0.8f;
const float amount = 0.11f;
if (zz > bound_z)
{
const float t = (zz - bound_z) / (1.0f - bound_z) * fmin(1.0f, YY / bound_Y);
cam[1] += t * amount;
cam[2] -= t * amount;
}
}
}
// convert camera to RGB using the first color matrix
for(int j=0;j<3;j++)
{
RGB[j] = 0.0f;
for(int i=0;i<3;i++) RGB[j] += cmat[3*j+i] * cam[i];
}
// clamp at this stage
for(int i=0; i<3; i++) RGB[i] = clipf(RGB[i]);
// convert clipped RGB to XYZ
for(int j=0;j<3;j++)
{
XYZ[j] = 0.0f;
for(int i=0;i<3;i++) XYZ[j] += lmat[3*j+i] * RGB[i];
}
float4 xyz = (float4)(XYZ[0], XYZ[1], XYZ[2], 0.0f);
pixel.xyz = XYZ_to_Lab(xyz).xyz;
if(use_hsm)
{
float4 prophoto_rgb = Lab_to_prophotorgb(pixel) / (float4)scale;
float4 hsv = RGB_2_HSV(prophoto_rgb);
// Encode valueV for table lookup if needed
float v_encoded = hsv.z;
if(encoding == 1) // sRGB
v_encoded = _linear_to_srgb(clipf(v_encoded));
// Use encoded value for table lookup (need to temporarily set hsv[2])
float4 hsv_for_lookup = hsv;
hsv_for_lookup.z = v_encoded;
float4 correction = _lookup_hsm(hsv_for_lookup, hue_div, sat_div, val_div, dev_hsm);
hsv.x += correction.x / 360.0f;
hsv.x -= floor(hsv.x); // stay within the 360° hue circle
hsv.y = clipf(hsv.y * correction.y);
float v_corrected = v_encoded * correction.z;
if(encoding == 1) // sRGB
v_corrected = _srgb_to_linear(v_corrected);
hsv.z = clipf(v_corrected);
prophoto_rgb = HSV_2_RGB(hsv) * (float4)scale;
pixel = prophotorgb_to_Lab(prophoto_rgb);
}
pixel.w = alpha;
write_imagef(out, (int2)(x, y), pixel);
}
/* kernel for input profile conversion directly to working RGB */
kernel void
colorin_rgb (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
global float *cmat, global float *lmat,
read_only image2d_t lutr, read_only image2d_t lutg, read_only image2d_t lutb,
const int blue_mapping, global const float (*const a)[3], global const float *corr,
const float scale, const int hue_div, const int sat_div, const int val_div, global const float *dev_hsm, const int encoding, const int use_hsm, const int hdr_transfer,
const int clipping, constant dt_colorspaces_iccprofile_info_cl_t *work,
read_only image2d_t work_lut, const int lab_input)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 corval = (const float4)(corr[0], corr[1], corr[2], corr[3]);
const float4 input = readpixel(in, x, y);
float4 pixel = corval * input;
const float alpha = input.w;
if(lab_input)
{
pixel = matrix_product(Lab_to_XYZ(input), work->matrix_out);
if(work->nonlinearlut || work->hdr_transfer)
pixel = apply_trc_out(pixel, work, work_lut);
pixel.w = alpha;
write_imagef(out, (int2)(x, y), pixel);
return;
}
float cam[3];
if(hdr_transfer)
{
cam[0] = pixel.x;
cam[1] = pixel.y;
cam[2] = pixel.z;
dt_hdr_decode(cam, hdr_transfer);
}
else
{
cam[0] = lerp_lookup_unbounded0(lutr, pixel.x, a[0]);
cam[1] = lerp_lookup_unbounded0(lutg, pixel.y, a[1]);
cam[2] = lerp_lookup_unbounded0(lutb, pixel.z, a[2]);
}
if(blue_mapping)
{
const float YY = cam[0] + cam[1] + cam[2];
if(YY > 0.0f)
{
// manual gamut mapping. these values cause trouble when converting back from Lab to sRGB:
const float zz = cam[2] / YY;
// lower amount and higher bound_z make the effect smaller.
// the effect is weakened the darker input values are, saturating at bound_Y
const float bound_z = 0.5f, bound_Y = 0.5f;
const float amount = 0.11f;
if(zz > bound_z)
{
const float t = (zz - bound_z) / (1.0f - bound_z) * fmin(1.0f, YY / bound_Y);
cam[1] += t * amount;
cam[2] -= t * amount;
}
}
}
// the first matrix maps directly to working RGB, or to the clipping gamut
float4 linear = (float4)(0.0f);
for(int j = 0; j < 3; j++)
for(int i = 0; i < 3; i++) linear[j] += cmat[3*j+i] * cam[i];
if(clipping)
{
float4 clipped = clamp(linear, 0.0f, 1.0f);
linear = (float4)(0.0f);
for(int j = 0; j < 3; j++)
for(int i = 0; i < 3; i++) linear[j] += lmat[3*j+i] * clipped[i];
}
pixel = linear;
if(use_hsm)
{
float4 prophoto_rgb = XYZ_to_prophotorgb(matrix_product(pixel, work->matrix_in)) / (float4)scale;
float4 hsv = RGB_2_HSV(prophoto_rgb);
// encode value for table lookup if needed
float v_encoded = hsv.z;
if(encoding == 1) // sRGB
v_encoded = _linear_to_srgb(clipf(v_encoded));
// use the encoded value for table lookup
float4 hsv_for_lookup = hsv;
hsv_for_lookup.z = v_encoded;
float4 correction = _lookup_hsm(hsv_for_lookup, hue_div, sat_div, val_div, dev_hsm);
hsv.x += correction.x / 360.0f;
hsv.x -= floor(hsv.x); // stay within the 360° hue circle
hsv.y = clipf(hsv.y * correction.y);
float v_corrected = v_encoded * correction.z;
if(encoding == 1) // sRGB
v_corrected = _srgb_to_linear(v_corrected);
hsv.z = clipf(v_corrected);
prophoto_rgb = HSV_2_RGB(hsv) * (float4)scale;
pixel = matrix_product(prophotorgb_to_XYZ(prophoto_rgb), work->matrix_out);
}
if(work->nonlinearlut || work->hdr_transfer)
pixel = apply_trc_out(pixel, work, work_lut);
pixel.w = alpha;
write_imagef(out, (int2)(x, y), pixel);
}
/* kernel for the tonecurve plugin. */
kernel void
tonecurve (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
read_only image2d_t table_L, read_only image2d_t table_a, read_only image2d_t table_b,
const int autoscale_ab, const int unbound_ab, constant float *coeffs_L, constant float *coeffs_ab,
const float low_approximation, const int preserve_colors,
constant dt_colorspaces_iccprofile_info_cl_t *profile_info, read_only image2d_t lut)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
const float L_in = pixel.x/100.0f;
// use lut or extrapolation:
const float L = lookup_unbounded_last_node(table_L, L_in, coeffs_L);
if (autoscale_ab == 0)
{
const float a_in = (pixel.y + 128.0f) / 256.0f;
const float b_in = (pixel.z + 128.0f) / 256.0f;
if (unbound_ab == 0)
{
pixel.y = lookup(table_a, a_in);
pixel.z = lookup(table_b, b_in);
}
else
{
// use lut or two-sided extrapolation
pixel.y = lookup_unbounded_twosided(table_a, a_in, coeffs_ab);
pixel.z = lookup_unbounded_twosided(table_b, b_in, coeffs_ab + 6);
}
pixel.x = L;
}
else if(autoscale_ab == 1)
{
if(L_in > 0.01f)
{
pixel.y *= L/pixel.x;
pixel.z *= L/pixel.x;
}
else
{
pixel.y *= low_approximation;
pixel.z *= low_approximation;
}
pixel.x = L;
}
else if(autoscale_ab == 2)
{
float4 xyz = Lab_to_XYZ(pixel);
xyz.x = lookup_unbounded_last_node(table_L, xyz.x, coeffs_L);
xyz.y = lookup_unbounded_last_node(table_L, xyz.y, coeffs_L);
xyz.z = lookup_unbounded_last_node(table_L, xyz.z, coeffs_L);
pixel.xyz = XYZ_to_Lab(xyz).xyz;
}
else if(autoscale_ab == 3)
{
float4 rgb = Lab_to_prophotorgb(pixel);
if (preserve_colors == DT_RGB_NORM_NONE)
{
rgb.x = lookup_unbounded_last_node(table_L, rgb.x, coeffs_L);
rgb.y = lookup_unbounded_last_node(table_L, rgb.y, coeffs_L);
rgb.z = lookup_unbounded_last_node(table_L, rgb.z, coeffs_L);
}
else
{
float ratio = 1.f;
float lum = dt_rgb_norm(rgb, preserve_colors, 1, profile_info, lut);
if(lum > 0.f)
{
const float curve_lum = lookup_unbounded_last_node(table_L, lum, coeffs_L);
ratio = curve_lum / lum;
}
rgb.xyz *= ratio;
}
pixel.xyz = prophotorgb_to_Lab(rgb).xyz;
}
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the colorcorrection plugin. */
__kernel void
colorcorrection (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const float saturation, const float a_scale, const float a_base,
const float b_scale, const float b_base)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
pixel.y = saturation*(pixel.y + pixel.x * a_scale + a_base);
pixel.z = saturation*(pixel.z + pixel.x * b_scale + b_base);
write_imagef (out, (int2)(x, y), pixel);
}
void
mul_mat_vec_2(const float4 m, const float2 *p, float2 *o)
{
(*o).x = (*p).x*m.x + (*p).y*m.y;
(*o).y = (*p).x*m.z + (*p).y*m.w;
}
void
backtransform(float2 *p, float2 *o, const float4 m, const float2 t)
{
(*p).y /= (1.0f + (*p).x*t.x);
(*p).x /= (1.0f + (*p).y*t.y);
mul_mat_vec_2(m, p, o);
}
void
keystone_backtransform(float2 *i, const float4 k_space, const float2 ka, const float4 ma, const float2 mb)
{
float xx = (*i).x - k_space.x;
float yy = (*i).y - k_space.y;
/*float u = ka.x-kb.x+kc.x-kd.x;
float v = ka.x-kb.x;
float w = ka.x-kd.x;
float z = ka.x;
//(*i).x = (xx/k_space.z)*(yy/k_space.w)*(ka.x-kb.x+kc.x-kd.x) - (xx/k_space.z)*(ka.x-kb.x) - (yy/k_space.w)*(ka.x-kd.x) + ka.x + k_space.x;
(*i).x = (xx/k_space.z)*(yy/k_space.w)*u - (xx/k_space.z)*v - (yy/k_space.w)*w + z + k_space.x;
u = ka.y-kb.y+kc.y-kd.y;
v = ka.y-kb.y;
w = ka.y-kd.y;
z = ka.y;
//(*i).y = (xx/k_space.z)*(yy/k_space.w)*(ka.y-kb.y+kc.y-kd.y) - (xx/k_space.z)*(ka.y-kb.y) - (yy/k_space.w)*(ka.y-kd.y) + ka.y + k_space.y;
(*i).y = (xx/k_space.z)*(yy/k_space.w)*u - (xx/k_space.z)*v - (yy/k_space.w)*w + z + k_space.y;*/
float div = ((ma.z*xx-ma.x*yy)*mb.y+(ma.y*yy-ma.w*xx)*mb.x+ma.x*ma.w-ma.y*ma.z);
(*i).x = (ma.w*xx-ma.y*yy)/div + ka.x;
(*i).y =-(ma.z*xx-ma.x*yy)/div + ka.y;
}
float
interpolation_func_bicubic(float t)
{
float r;
t = fabs(t);
r = (t >= 2.0f) ? 0.0f : ((t > 1.0f) ? (0.5f*(t*(-t*t + 5.0f*t - 8.0f) + 4.0f)) : (0.5f*(t*(3.0f*t*t - 5.0f*t) + 2.0f)));
return r;
}
#define DT_LANCZOS_EPSILON (1e-9f)
#if 0
float
interpolation_func_lanczos(float width, float t)
{
float ta = fabs(t);
float r = (ta > width) ? 0.0f : ((ta < DT_LANCZOS_EPSILON) ? 1.0f : width*native_sin(M_PI_F*t)*native_sin(M_PI_F*t/width)/(M_PI_F*M_PI_F*t*t));
return r;
}
#else
float
sinf_fast(float t)
{
/***** if you change this function, you must also change the copy in src/common/math.h *****/
const float a = 4.0f/(M_PI_F*M_PI_F);
const float p = 0.225f;
t = a*t*(M_PI_F - fabs(t));
return p*(t*fabs(t) - t) + t;
}
float
interpolation_func_lanczos(float width, float t)
{
/* Compute a value for sinf(pi.t) in [-pi pi] for which the value will be
* correct */
int a = (int)t;
float r = t - (float)a;
// Compute the correct sign for sinf(pi.r)
union { float f; unsigned int i; } sign;
sign.i = ((a&1)<<31) | 0x3f800000;
return (DT_LANCZOS_EPSILON + width*sign.f*sinf_fast(M_PI_F*r)*sinf_fast(M_PI_F*t/width))/(DT_LANCZOS_EPSILON + M_PI_F*M_PI_F*t*t);
}
#endif
// the taps of _maketaps_bilinear(), _maketaps_bicubic() and _maketaps_lanczos()
// in src/common/interpolation.c, by the dt_interpolation_type of the interpolator
static inline float _interpolation_tap(const int type, const float width, const float t)
{
if(type == 0) return 1.0f - fabs(t);
if(type == 1)
{
const float ta = fabs(t);
return ta <= 1.0f ? 0.5f * (ta * (3.0f * ta * ta - 5.0f * ta) + 2.0f)
: 0.5f * (ta * (-ta * ta + 5.0f * ta - 8.0f) + 4.0f);
}
return interpolation_func_lanczos(width, t);
}
// one pixel by dt_interpolation_compute_pixel4c() at (px, py): the interpolator's
// taps and norm, mirrored borders and zero outside the image
static inline float4 _interpolation_pixel4(read_only image2d_t in,
const float px,
const float py,
const int in_width,
const int in_height,
const int itype,
const int iwidth)
{
const int ix = (int)floor(px);
const int iy = (int)floor(py);
if(ix < -1 || iy < -1 || ix >= in_width || iy >= in_height)
return (float4)0.0f;
const int fx = ix - iwidth + 1;
const int fy = iy - iwidth + 1;
const float tx = px - (float)fx;
const float ty = py - (float)fy;
float kh[6], kv[6];
float normh = 0.0f, normv = 0.0f;
for(int k = 0; k < 2 * iwidth; k++)
{
kh[k] = _interpolation_tap(itype, iwidth, tx - k);
kv[k] = _interpolation_tap(itype, iwidth, ty - k);
normh += kh[k];
normv += kv[k];
}
// the CPU kernels report a norm of 1 for bilinear and bicubic
if(itype < 2) normh = normv = 1.0f;
float4 pixel = (float4)0.0f;
for(int i = 0; i < 2 * iwidth; i++)
{
const int yy = clip_mirror(fy + i, in_height - 1);
float4 h = (float4)0.0f;
for(int j = 0; j < 2 * iwidth; j++)
h += kh[j] * readpixel(in, clip_mirror(fx + j, in_width - 1), yy);
pixel += kv[i] * h;
}
return pixel * (1.0f / (normh * normv));
}
// rotate pixels: one output pixel at the back transformed position
kernel void
rotatepixels(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int in_width,
const int in_height,
const int2 roi_out,
const int2 roi_in,
const float4 m,
const float2 center,
const int itype,
const int iwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pix = roi_out.x + x;
const float piy = roi_out.y + y;
const float px = pix * m.x + piy * m.y + center.x - roi_in.x;
const float py = pix * m.z + piy * m.w + center.y - roi_in.y;
write_imagef(out, (int2)(x, y), _interpolation_pixel4(in, px, py, in_width, in_height, itype, iwidth));
}
// scale pixels: one output pixel at ((x, y) + shift) * scale - offset of the
// input, see _sampling_grid() in src/iop/scalepixels.c
kernel void
scalepixels(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int in_width,
const int in_height,
const float2 scale,
const int2 shift,
const float2 offset,
const int itype,
const int iwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float px = (x + shift.x) * scale.x - offset.x;
const float py = (y + shift.y) * scale.y - offset.y;
write_imagef(out, (int2)(x, y), _interpolation_pixel4(in, px, py, in_width, in_height, itype, iwidth));
}
/* kernel for clip&rotate: bilinear interpolation */
__kernel void
clip_rotate_bilinear(read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int in_width, const int in_height,
const int2 roi_in, const float2 roi_out, const float scale_in, const float scale_out,
const int flip, const float2 t, const float2 k, const float4 mat,
const float4 k_space, const float2 ka, const float4 ma, const float2 mb)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float2 pi, po;
pi.x = roi_out.x + x + 0.5f;
pi.y = roi_out.y + y + 0.5f;
pi.x -= flip ? t.y * scale_out : t.x * scale_out;
pi.y -= flip ? t.x * scale_out : t.y * scale_out;
pi /= scale_out;
backtransform(&pi, &po, mat, k);
po *= scale_in;
po.x += t.x * scale_in;
po.y += t.y * scale_in;
if (k_space.z > 0.0f) keystone_backtransform(&po,k_space,ka,ma,mb);
po.x -= roi_in.x + 0.5f;
po.y -= roi_in.y + 0.5f;
const int ii = (int)po.x;
const int jj = (int)po.y;
float4 o = (ii >=0 && jj >= 0 && ii < in_width && jj < in_height) ? read_imagef(in, samplerf, po) : (float4)0.0f;
write_ipixel(out, (int2)(x, y), o);
}
/* kernel for clip&rotate: bicubic interpolation */
__kernel void
clip_rotate_bicubic(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int in_width,
const int in_height,
const int2 roi_in,
const float2 roi_out,
const float scale_in,
const float scale_out,
const int flip,
const float2 t,
const float2 k,
const float4 mat,
const float4 k_space,
const float2 ka,
const float4 ma,
const float2 mb)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float2 pi, po;
pi.x = roi_out.x + x + 0.5f;
pi.y = roi_out.y + y + 0.5f;
pi.x -= flip ? t.y * scale_out : t.x * scale_out;
pi.y -= flip ? t.x * scale_out : t.y * scale_out;
pi /= scale_out;
backtransform(&pi, &po, mat, k);
po *= scale_in;
po.x += t.x * scale_in;
po.y += t.y * scale_in;
if (k_space.z > 0.0f) keystone_backtransform(&po,k_space,ka,ma,mb);
po.x -= roi_in.x + 0.5f;
po.y -= roi_in.y + 0.5f;
int tx = po.x;
int ty = po.y;
float4 pixel = (float4)0.0f;
float weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_bicubic((float)i - po.x);
float wy = interpolation_func_bicubic((float)j - po.y);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, in_width-1), clip_mirror(j, in_height-1)) * (float4)w;
weight += w;
}
pixel = (tx >= 0 && ty >= 0 && tx < in_width && ty < in_height) ? pixel / weight : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernel for clip&rotate: lanczos2 interpolation */
__kernel void
clip_rotate_lanczos2(read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int in_width, const int in_height,
const int2 roi_in, const float2 roi_out, const float scale_in, const float scale_out,
const int flip, const float2 t, const float2 k, const float4 mat,
const float4 k_space, const float2 ka, const float4 ma, const float2 mb)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float2 pi, po;
pi.x = roi_out.x + x + 0.5f;
pi.y = roi_out.y + y + 0.5f;
pi.x -= flip ? t.y * scale_out : t.x * scale_out;
pi.y -= flip ? t.x * scale_out : t.y * scale_out;
pi /= scale_out;
backtransform(&pi, &po, mat, k);
po *= scale_in;
po.x += t.x * scale_in;
po.y += t.y * scale_in;
if (k_space.z > 0.0f) keystone_backtransform(&po,k_space,ka,ma,mb);
po.x -= roi_in.x + 0.5f;
po.y -= roi_in.y + 0.5f;
int tx = po.x;
int ty = po.y;
float4 pixel = (float4)0.0f;
float weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_lanczos(2, (float)i - po.x);
float wy = interpolation_func_lanczos(2, (float)j - po.y);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, in_width-1), clip_mirror(j, in_height-1)) * (float4)w;
weight += w;
}
pixel = (tx >= 0 && ty >= 0 && tx < in_width && ty < in_height) ? pixel / weight : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernel for clip&rotate: lanczos3 interpolation */
__kernel void
clip_rotate_lanczos3(read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int in_width, const int in_height,
const int2 roi_in, const float2 roi_out, const float scale_in, const float scale_out,
const int flip, const float2 t, const float2 k, const float4 mat,
const float4 k_space, const float2 ka, const float4 ma, const float2 mb)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 3;
if(x >= width || y >= height) return;
float2 pi, po;
pi.x = roi_out.x + x + 0.5f;
pi.y = roi_out.y + y + 0.5f;
pi.x -= flip ? t.y * scale_out : t.x * scale_out;
pi.y -= flip ? t.x * scale_out : t.y * scale_out;
pi /= scale_out;
backtransform(&pi, &po, mat, k);
po *= scale_in;
po.x += t.x * scale_in;
po.y += t.y * scale_in;
if (k_space.z > 0.0f) keystone_backtransform(&po,k_space,ka,ma,mb);
po.x -= roi_in.x + 0.5f;
po.y -= roi_in.y + 0.5f;
int tx = (int)po.x;
int ty = (int)po.y;
float4 pixel = (float4)0.0f;
float weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_lanczos(3, (float)i - po.x);
float wy = interpolation_func_lanczos(3, (float)j - po.y);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, in_width-1), clip_mirror(j, in_height-1)) * (float4)w;
weight += w;
}
pixel = (tx >= 0 && ty >= 0 && tx < in_width && ty < in_height) ? pixel / weight : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
float maketaps_bilinear(float *taps,
const int num_taps,
const float width,
const float first_tap,
const float interval)
{
float iter[4];
float vt[4];
for(int c = 0; c < 4; c++)
iter[c] = 4.0f * interval;
for(int c = 0; c < 4; c++)
vt[c] = first_tap + (float)c * interval;
const int runs = (num_taps + 3) / 4;
for(int i = 0; i < runs; i++)
{
for(int c = 0; c < 4; c++)
taps[4*i + c] = 1.0f - (vt[c] < 0.0f ? -vt[c] : vt[c]);
// prepare next iteration
for(int c = 0; c < 4; c++)
vt[c] += iter[c];
}
return 1.0f; //kernel norm is 1.0f by construction
}
float maketaps_bicubic(float *taps,
const int num_taps,
const float width,
const float first_tap,
const float interval)
{
float iter[4];
float vt[4];
for(int c = 0; c < 4; c++)
iter[c] = 4.0f * interval;
for(int c = 0; c < 4; c++)
vt[c] = first_tap + (float)c * interval;
const int runs = (num_taps + 3) / 4;
for(int i = 0; i < runs; i++)
{
// compute and store the values for the current four taps
float vt_abs[4];
float t2[4]; // tap-squared
for(int c = 0; c < 4; c++)
{
vt_abs[c] = vt[c] < 0.0f ? -vt[c] : vt[c];
t2[c] = vt[c] * vt[c];
}
float t5[4];
float mt2_add_t5_sub_8[4];
for(int c = 0; c < 4; c++)
{
t5[c] = 5.0f * vt_abs[c];
mt2_add_t5_sub_8[c] = t5[c] - 8.0f - t2[c];
}
float b[4];
float r12[4];
for(int c = 0; c < 4; c++)
{
b[c] = vt_abs[c] * mt2_add_t5_sub_8[c] + 4.0f;
r12[c] = b[c] * 0.5f; // the value for 1 < t < 2
}
float t23[4];
float e[4];
float r01[4];
for(int c = 0; c < 4; c++)
{
t23[c] = 3.0f * t2[c] - t5[c];
e[c] = t23[c] * vt_abs[c] + 2.0f;
r01[c] = e[c] * 0.5f;
}
// combine the values depending on whether abs(tap) is less than one or not
for(int c = 0; c < 4; c++)
{
taps[4*i + c] = vt_abs[c] <= 1.0f ? r01[c] : r12[c];
}
// prepare next iteration
for(int c = 0; c < 4; c++)
vt[c] += iter[c];
}
return 1.0f; //kernel norm is 1.0f by construction
}
void vector_sin(const float *arg, float *sine)
{
const float a = 4.0f / (M_PI_F * M_PI_F);
float abs_arg[4];
for(int c = 0; c < 4; c++)
abs_arg[c] = (arg[c] < 0.0f) ? -arg[c] : arg[c];
float scaled[4];
for(int c = 0; c < 4; c++)
scaled[c] = a * arg[c] * (M_PI_F - abs_arg[c]);
float abs_scaled[4];
for(int c = 0; c < 4; c++)
abs_scaled[c] = (scaled[c] < 0.0f) ? -scaled[c] : scaled[c];
for(int c = 0; c < 4; c++)
sine[c] = scaled[c] * (0.225f * (abs_scaled[c] - 1.0f) + 1.0f);
}
float maketaps_lanczos(float *taps,
const int num_taps,
const float width,
const float first_tap,
const float interval)
{
float iter[4];
float vt[4];
for(int c = 0; c < 4; c++)
iter[c] = 4.0f * interval;
for(int c = 0; c < 4; c++)
vt[c] = first_tap + (float)c * interval;
float vw[4];
for(int c = 0; c < 4; c++)
vw[c] = width;
const int runs = (num_taps + 3) / 4;
for(int i = 0; i < runs; i++)
{
float r[4];
float sign[4];
for(int c = 0; c < 4; c++)
{
const int a = (int)vt[c];
r[c] = vt[c] - (float)a;
sign[c] = (a & 1) ? -1.0f : 1.0f;
}
float sine_arg1[4];
float sine_arg2[4];
for(int c = 0; c < 4; c++)
{
sine_arg1[c] = M_PI_F * r[c];
sine_arg2[c] = M_PI_F * vt[c] / vw[c];
}
float sine1[4];
float sine2[4];
vector_sin(sine_arg1, sine1);
vector_sin(sine_arg2, sine2);
float num[4];
float denom[4];
for(int c = 0; c < 4; c++)
{
num[c] = (vw[c] * sign[c] * sine1[c] * sine2[c]) + 1e-9f;
denom[c] = (M_PI_F*M_PI_F * vt[c] * vt[c]) + 1e-9f;
}
for(int c = 0; c < 4; c++)
{
taps[4*i + c] = num[c] / denom[c];
}
// prepare next iteration
for(int c = 0; c < 4; c++)
vt[c] += iter[c];
}
float norm = 0.0f;
for(int i = 0; i < num_taps; i++)
norm += taps[i];
return norm;
}
float compute_upsampling_taps(const int itor_mode,
const int itor_width,
float *taps,
float tt)
{
const int f = (int)floor(tt) - itor_width + 1;
const float t = tt - (float)f;
// itor_mode is the dt_interpolation_type: lanczos2 and lanczos3 share the
// lanczos taps and differ only in width, as in src/common/interpolation.c
if(itor_mode == 1)
return maketaps_bicubic(taps, 2*itor_width, (float)itor_width, t, -1.0f);
else if(itor_mode == 2 || itor_mode == 3)
return maketaps_lanczos(taps, 2*itor_width, (float)itor_width, t, -1.0f);
else
return maketaps_bilinear(taps, 2*itor_width, (float)itor_width, t, -1.0f);
}
static inline float _get_image_channel(read_only image2d_t in,
const int x,
const int y,
const int c)
{
float4 pixel = Areadpixel(in, x, y); // we ensure secure locations in callers
if(c == 0)
return pixel.x;
else if(c == 1)
return pixel.y;
else if(c == 2)
return pixel.z;
return pixel.w;
}
// Keep in sync with defines in interpolation
#define MAX_HALF_FILTER_WIDTH 3
#define MAX_KERNEL_REQ ((2 * (MAX_HALF_FILTER_WIDTH) + 3) & (~3))
float interpolation_compute_single(read_only image2d_t in,
const int itor_mode,
const int itor_width,
const float x,
const float y,
const int width,
const int height,
const int channel)
{
float kernelh[MAX_KERNEL_REQ];
float kernelv[MAX_KERNEL_REQ];
int ix = (int)x;
int iy = (int)y;
float s = 0.0f;
if(ix >= 0 && iy >= 0 && ix < width && iy < height)
{
// Compute both horizontal and vertical kernels
float normh = compute_upsampling_taps(itor_mode, itor_width, kernelh, x);
float normv = compute_upsampling_taps(itor_mode, itor_width, kernelv, y);
iy -= itor_width - 1;
ix -= itor_width - 1;
// Apply the kernel
for(int i = 0; i < 2 * itor_width; i++)
{
float h = 0.0f;
for(int j = 0; j < 2 * itor_width; j++)
{
h += kernelh[j] * _get_image_channel(in, clip_mirror(ix+j, width-1), clip_mirror(iy+i, height-1), channel);
}
s += kernelv[i] * h;
}
s = s / (normh * normv);
}
return s;
}
#undef MAX_KERNEL_REQ
#undef MAX_HALF_FILTER_WIDTH
/* lens plugin: the lensfun values of pixel (x, y), interpolated bilinearly
from a grid of every step-th pixel, gwidth points to a row and stride floats
to a point. This is the OpenCL version of _lens_grid_row() in
src/iop/lens.cc */
static inline float _lens_grid_lerp(const float a, const float b, const float t)
{
// mixf() of the CPU path, except that a weight of zero skips b: a grid of
// step 1 can hold non-finite values, which must not leak into the
// neighboring pixels
return t > 0.0f ? fma(t, b - a, a) : a;
}
static inline void _lens_grid_sample(global const float *grid, const int x, const int y,
const int step, const int gwidth, const int stride,
const int count, float *out)
{
const int i = x / step;
const int j = y / step;
const float tx = (float)(x - i * step) / (float)step;
const float ty = (float)(y - j * step) / (float)step;
global const float *p00 = grid + (j * gwidth + i) * stride;
global const float *p01 = p00 + gwidth * stride;
for(int n = 0; n < count; n++)
{
const float left = _lens_grid_lerp(p00[n], p01[n], ty);
const float right = _lens_grid_lerp(p00[stride + n], p01[stride + n], ty);
out[n] = _lens_grid_lerp(left, right, tx);
}
}
/* lens plugin: a lensfun coordinate relative to the input, held inside it as
in the CPU path of src/iop/lens.cc. Coordinates past the edge sample the
edge pixel there; mirroring the taps alone would reflect them back into the
image. fmin() and fmax() also map NaN to an edge as the CPU path does. */
static inline float _lens_coord(const float v, const int offset, const int size)
{
return fmax(fmin(v - (float)offset, (float)size - 1.0f), 0.0f);
}
/* kernels for the lens plugin: bilinear interpolation */
kernel void
lens_distort_bilinear (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int iwidth, const int iheight, const int roi_in_x, const int roi_in_y, global float *pi,
const int do_nan_checks, const int step, const int gwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel;
float rx, ry;
float ppi[6];
_lens_grid_sample(pi, x, y, step, gwidth, 6, 6, ppi);
if(do_nan_checks)
{
bool valid = true;
for(int i = 0; i < 6; i++) valid = valid && isfinite(ppi[i]);
if(!valid)
{
pixel = (float4)0.0f;
write_imagef (out, (int2)(x, y), pixel);
return;
}
}
rx = _lens_coord(ppi[0], roi_in_x, iwidth);
ry = _lens_coord(ppi[1], roi_in_y, iheight);
{
const float fx = floor(rx);
const float fy = floor(ry);
const float dx = rx - fx;
const float dy = ry - fy;
const int x0 = clip_mirror((int)fx, iwidth - 1);
const int x1 = clip_mirror((int)fx + 1, iwidth - 1);
const int y0 = clip_mirror((int)fy, iheight - 1);
const int y1 = clip_mirror((int)fy + 1, iheight - 1);
const float4 px = (1.0f - dy) * ((1.0f - dx) * Areadpixel(in, x0, y0) + dx * Areadpixel(in, x1, y0))
+ dy * ((1.0f - dx) * Areadpixel(in, x0, y1) + dx * Areadpixel(in, x1, y1));
pixel.x = px.x;
}
rx = _lens_coord(ppi[2], roi_in_x, iwidth);
ry = _lens_coord(ppi[3], roi_in_y, iheight);
{
const float fx = floor(rx);
const float fy = floor(ry);
const float dx = rx - fx;
const float dy = ry - fy;
const int x0 = clip_mirror((int)fx, iwidth - 1);
const int x1 = clip_mirror((int)fx + 1, iwidth - 1);
const int y0 = clip_mirror((int)fy, iheight - 1);
const int y1 = clip_mirror((int)fy + 1, iheight - 1);
const float4 px = (1.0f - dy) * ((1.0f - dx) * Areadpixel(in, x0, y0) + dx * Areadpixel(in, x1, y0))
+ dy * ((1.0f - dx) * Areadpixel(in, x0, y1) + dx * Areadpixel(in, x1, y1));
pixel.yw = px.yw;
}
rx = _lens_coord(ppi[4], roi_in_x, iwidth);
ry = _lens_coord(ppi[5], roi_in_y, iheight);
{
const float fx = floor(rx);
const float fy = floor(ry);
const float dx = rx - fx;
const float dy = ry - fy;
const int x0 = clip_mirror((int)fx, iwidth - 1);
const int x1 = clip_mirror((int)fx + 1, iwidth - 1);
const int y0 = clip_mirror((int)fy, iheight - 1);
const int y1 = clip_mirror((int)fy + 1, iheight - 1);
const float4 px = (1.0f - dy) * ((1.0f - dx) * Areadpixel(in, x0, y0) + dx * Areadpixel(in, x1, y0))
+ dy * ((1.0f - dx) * Areadpixel(in, x0, y1) + dx * Areadpixel(in, x1, y1));
pixel.z = px.z;
}
pixel = all(isfinite(pixel.xyz)) ? pixel : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernels for the lens plugin: bicubic interpolation */
kernel void
lens_distort_bicubic (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int iwidth,
const int iheight,
const int roi_in_x,
const int roi_in_y,
global float *pi,
const int do_nan_checks,
const int step,
const int gwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float4 pixel = (float4)0.0f;
float rx, ry;
int tx, ty;
float sum, weight;
float2 sum2;
float ppi[6];
_lens_grid_sample(pi, x, y, step, gwidth, 6, 6, ppi);
if(do_nan_checks)
{
bool valid = true;
for(int i = 0; i < 6; i++) valid = valid && isfinite(ppi[i]);
if(!valid)
{
pixel = (float4)0.0f;
write_imagef (out, (int2)(x, y), pixel);
return;
}
}
rx = _lens_coord(ppi[0], roi_in_x, iwidth);
ry = _lens_coord(ppi[1], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_bicubic((float)i - rx);
float wy = interpolation_func_bicubic((float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).x * w;
weight += w;
}
pixel.x = sum/weight;
rx = _lens_coord(ppi[2], roi_in_x, iwidth);
ry = _lens_coord(ppi[3], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum2 = (float2)0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_bicubic((float)i - rx);
float wy = interpolation_func_bicubic((float)j - ry);
float w = wx * wy;
sum2 += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).yw * w;
weight += w;
}
pixel.yw = sum2/weight;
rx = _lens_coord(ppi[4], roi_in_x, iwidth);
ry = _lens_coord(ppi[5], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_bicubic((float)i - rx);
float wy = interpolation_func_bicubic((float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).z * w;
weight += w;
}
pixel.z = sum/weight;
pixel = all(isfinite(pixel.xyz)) ? pixel : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernels for the lens plugin: lanczos2 interpolation */
kernel void
lens_distort_lanczos2 (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int iwidth,
const int iheight,
const int roi_in_x,
const int roi_in_y,
global float *pi,
const int do_nan_checks,
const int step,
const int gwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float4 pixel = (float4)0.0f;
float rx, ry;
int tx, ty;
float sum, weight;
float2 sum2;
float ppi[6];
_lens_grid_sample(pi, x, y, step, gwidth, 6, 6, ppi);
if(do_nan_checks)
{
bool valid = true;
for(int i = 0; i < 6; i++) valid = valid && isfinite(ppi[i]);
if(!valid)
{
pixel = (float4)0.0f;
write_imagef (out, (int2)(x, y), pixel);
return;
}
}
rx = _lens_coord(ppi[0], roi_in_x, iwidth);
ry = _lens_coord(ppi[1], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(2, (float)i - rx);
float wy = interpolation_func_lanczos(2, (float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).x * w;
weight += w;
}
pixel.x = sum/weight;
rx = _lens_coord(ppi[2], roi_in_x, iwidth);
ry = _lens_coord(ppi[3], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum2 = (float2)0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(2, (float)i - rx);
float wy = interpolation_func_lanczos(2, (float)j - ry);
float w = wx * wy;
sum2 += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).yw * w;
weight += w;
}
pixel.yw = sum2/weight;
rx = _lens_coord(ppi[4], roi_in_x, iwidth);
ry = _lens_coord(ppi[5], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(2, (float)i - rx);
float wy = interpolation_func_lanczos(2, (float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).z * w;
weight += w;
}
pixel.z = sum/weight;
pixel = all(isfinite(pixel.xyz)) ? pixel : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernels for the lens plugin: lanczos3 interpolation */
kernel void
lens_distort_lanczos3 (read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int iwidth, const int iheight, const int roi_in_x, const int roi_in_y, global float *pi,
const int do_nan_checks, const int step, const int gwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 3;
if(x >= width || y >= height) return;
float4 pixel = (float4)0.0f;
float rx, ry;
int tx, ty;
float sum, weight;
float2 sum2;
float ppi[6];
_lens_grid_sample(pi, x, y, step, gwidth, 6, 6, ppi);
if(do_nan_checks)
{
bool valid = true;
for(int i = 0; i < 6; i++) valid = valid && isfinite(ppi[i]);
if(!valid)
{
pixel = (float4)0.0f;
write_imagef (out, (int2)(x, y), pixel);
return;
}
}
rx = _lens_coord(ppi[0], roi_in_x, iwidth);
ry = _lens_coord(ppi[1], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(3, (float)i - rx);
float wy = interpolation_func_lanczos(3, (float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).x * w;
weight += w;
}
pixel.x = sum/weight;
rx = _lens_coord(ppi[2], roi_in_x, iwidth);
ry = _lens_coord(ppi[3], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum2 = (float2)0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(3, (float)i - rx);
float wy = interpolation_func_lanczos(3, (float)j - ry);
float w = wx * wy;
sum2 += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).yw * w;
weight += w;
}
pixel.yw = sum2/weight;
rx = _lens_coord(ppi[4], roi_in_x, iwidth);
ry = _lens_coord(ppi[5], roi_in_y, iheight);
tx = floor(rx);
ty = floor(ry);
sum = 0.0f;
weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
int i = tx + ii;
int j = ty + jj;
float wx = interpolation_func_lanczos(3, (float)i - rx);
float wy = interpolation_func_lanczos(3, (float)j - ry);
float w = wx * wy;
sum += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)).z * w;
weight += w;
}
pixel.z = sum/weight;
pixel = all(isfinite(pixel.xyz)) ? pixel : (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernel for the ashift module: bilinear interpolation */
kernel void
ashift_bilinear(read_only image2d_t in, write_only image2d_t out, const int width, const int height,
const int iwidth, const int iheight, const int2 roi_in, const int2 roi_out,
const float in_scale, const float out_scale, const float2 clip, global float *homograph)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float pin[3], pout[3];
// convert output pixel coordinates to original image coordinates
pout[0] = roi_out.x + x + clip.x;
pout[1] = roi_out.y + y + clip.y;
pout[0] = fp32_div(pout[0], out_scale);
pout[1] = fp32_div(pout[1], out_scale);
pout[2] = 1.0f;
// apply homograph
for(int i = 0; i < 3; i++)
{
pin[i] = 0.0f;
for(int j = 0; j < 3; j++) pin[i] = fma(homograph[3 * i + j], pout[j], pin[i]);
}
// convert to input pixel coordinates
pin[0] = fp32_div(pin[0], pin[2]);
pin[1] = fp32_div(pin[1], pin[2]);
pin[0] *= in_scale;
pin[1] *= in_scale;
pin[0] -= roi_in.x;
pin[1] -= roi_in.y;
const float rx = pin[0];
const float ry = pin[1];
/** don't get output values by interpolation from input image using fast hardware bilinear interpolation
as that can't mirror and thus doesn't match CPU output.
Instead do that manually.
*/
float4 pixel = (float4)0.0f;
if(rx >= -1.0f && ry >= -1.0f && rx < (float)iwidth && ry < (float)iheight)
{
const float fx = floor(rx);
const float fy = floor(ry);
const float dx = rx - fx;
const float dy = ry - fy;
const int x0 = clip_mirror((int)fx, iwidth - 1);
const int x1 = clip_mirror((int)fx + 1, iwidth - 1);
const int y0 = clip_mirror((int)fy, iheight - 1);
const int y1 = clip_mirror((int)fy + 1, iheight - 1);
pixel = (1.0f - dy) * ((1.0f - dx) * Areadpixel(in, x0, y0) + dx * Areadpixel(in, x1, y0))
+ dy * ((1.0f - dx) * Areadpixel(in, x0, y1) + dx * Areadpixel(in, x1, y1));
}
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernel for the ashift module: bicubic interpolation */
kernel void
ashift_bicubic (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int iwidth,
const int iheight,
const int2 roi_in,
const int2 roi_out,
const float in_scale,
const float out_scale,
const float2 clip,
global float *homograph)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float pin[3], pout[3];
// convert output pixel coordinates to original image coordinates
pout[0] = roi_out.x + x + clip.x;
pout[1] = roi_out.y + y + clip.y;
pout[0] = fp32_div(pout[0], out_scale);
pout[1] = fp32_div(pout[1], out_scale);
pout[2] = 1.0f;
// apply homograph
for(int i = 0; i < 3; i++)
{
pin[i] = 0.0f;
for(int j = 0; j < 3; j++) pin[i] = fma(homograph[3 * i + j], pout[j], pin[i]);
}
// convert to input pixel coordinates
pin[0] = fp32_div(pin[0], pin[2]);
pin[1] = fp32_div(pin[1], pin[2]);
pin[0] *= in_scale;
pin[1] *= in_scale;
pin[0] -= roi_in.x;
pin[1] -= roi_in.y;
// get output values by interpolation from input image
float rx = pin[0];
float ry = pin[1];
int tx = floor(rx);
int ty = floor(ry);
float4 pixel = (float4)0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_bicubic((float)i - rx);
float wy = interpolation_func_bicubic((float)j - ry);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)) * (float4)w;
}
pixel = (tx >= -1 && ty >= -1 && tx < iwidth && ty < iheight)
? pixel
: (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernel for the ashift module: lanczos2 interpolation */
kernel void
ashift_lanczos2(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int iwidth,
const int iheight,
const int2 roi_in,
const int2 roi_out,
const float in_scale,
const float out_scale,
const float2 clip,
global float *homograph)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 2;
if(x >= width || y >= height) return;
float pin[3], pout[3];
// convert output pixel coordinates to original image coordinates
pout[0] = roi_out.x + x + clip.x;
pout[1] = roi_out.y + y + clip.y;
pout[0] = fp32_div(pout[0], out_scale);
pout[1] = fp32_div(pout[1], out_scale);
pout[2] = 1.0f;
// apply homograph
for(int i = 0; i < 3; i++)
{
pin[i] = 0.0f;
for(int j = 0; j < 3; j++) pin[i] = fma(homograph[3 * i + j], pout[j], pin[i]);
}
// convert to input pixel coordinates
pin[0] = fp32_div(pin[0], pin[2]);
pin[1] = fp32_div(pin[1], pin[2]);
pin[0] *= in_scale;
pin[1] *= in_scale;
pin[0] -= roi_in.x;
pin[1] -= roi_in.y;
// get output values by interpolation from input image
float rx = pin[0];
float ry = pin[1];
int tx = floor(rx);
int ty = floor(ry);
float4 pixel = (float4)0.0f;
float weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_lanczos(2, (float)i - rx);
float wy = interpolation_func_lanczos(2, (float)j - ry);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)) * (float4)w;
weight += w;
}
pixel = (tx >= -1 && ty >= -1 && tx < iwidth && ty < iheight)
? pixel/weight
: (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
/* kernels for the ashift module: lanczos3 interpolation */
kernel void
ashift_lanczos3(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int iwidth,
const int iheight,
const int2 roi_in,
const int2 roi_out,
const float in_scale,
const float out_scale,
const float2 clip,
global float *homograph)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int kwidth = 3;
if(x >= width || y >= height) return;
float pin[3], pout[3];
// convert output pixel coordinates to original image coordinates
pout[0] = roi_out.x + x + clip.x;
pout[1] = roi_out.y + y + clip.y;
pout[0] = fp32_div(pout[0], out_scale);
pout[1] = fp32_div(pout[1], out_scale);
pout[2] = 1.0f;
// apply homograph
for(int i = 0; i < 3; i++)
{
pin[i] = 0.0f;
for(int j = 0; j < 3; j++) pin[i] = fma(homograph[3 * i + j], pout[j], pin[i]);
}
// convert to input pixel coordinates
pin[0] = fp32_div(pin[0], pin[2]);
pin[1] = fp32_div(pin[1], pin[2]);
pin[0] *= in_scale;
pin[1] *= in_scale;
pin[0] -= roi_in.x;
pin[1] -= roi_in.y;
// get output values by interpolation from input image
float rx = pin[0];
float ry = pin[1];
int tx = floor(rx);
int ty = floor(ry);
float4 pixel = (float4)0.0f;
float weight = 0.0f;
for(int jj = 1 - kwidth; jj <= kwidth; jj++)
{
for(int ii= 1 - kwidth; ii <= kwidth; ii++)
{
const int i = tx + ii;
const int j = ty + jj;
float wx = interpolation_func_lanczos(3, (float)i - rx);
float wy = interpolation_func_lanczos(3, (float)j - ry);
float w = wx * wy;
pixel += Areadpixel(in, clip_mirror(i, iwidth-1), clip_mirror(j, iheight-1)) * (float4)w;
weight += w;
}
}
pixel = (tx >= -1 && ty >= -1 && tx < iwidth && ty < iheight)
? pixel/weight
: (float4)0.0f;
write_ipixel(out, (int2)(x, y), pixel);
}
float _calc_vignette_spline(const float radius,
global float *spline,
const int splinesize)
{
if(radius >= 1.0f) return spline[splinesize-1];
const float r = radius * (float)(splinesize-1 - 1);
const int i = (int)r;
const float frac = r - (float)i;
const float p0 = spline[i];
return p0 + (spline[i+1] - p0) * frac;
}
float _interpolate_linear_spline(global float *xi,
global float *yi,
const int ni,
const float x)
{
if(x < xi[0]) return yi[0];
for(int i = 1; i < ni; i++)
{
if(x >= xi[i - 1] && x <= xi[i])
{
const float dydx = (yi[i] - yi[i - 1]) / (xi[i] - xi[i - 1]);
return yi[i - 1] + (x - xi[i - 1]) * dydx;
}
}
return yi[ni - 1];
}
kernel void md_vignette(read_only image2d_t in,
write_only image2d_t out,
global float *knots_vig,
global float *vig,
const int width,
const int height,
const float w2,
const float h2,
const float r,
const int roix,
const int roiy,
const int knots)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float cx = ((float)(roix + x) - w2);
const float cy = ((float)(roiy + y) - h2);
const float4 spline =
_interpolate_linear_spline(knots_vig, vig, knots, r * hypot(cx, cy));
float4 pixel = Areadpixel(in, x, y);
float mask = pixel.w;
pixel.x = fp32_div(pixel.x, fmax(1e-4f, spline.x));
pixel.y = fp32_div(pixel.y, fmax(1e-4f, spline.y));
pixel.z = fp32_div(pixel.z, fmax(1e-4f, spline.z));
pixel.w = mask;
write_imagef (out, (int2)(x, y), pixel);
}
kernel void lens_man_vignette(read_only image2d_t in,
write_only image2d_t out,
global float *spline,
const int width,
const int height,
const float w2,
const float h2,
const int roix,
const int roiy,
const float inv_maxr,
const float intensity,
const int splinesize,
const int vigmask)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float dx = ((float)(roix + x) - w2);
const float dy = ((float)(roiy + y) - h2);
const float radius = dt_fast_hypot(dx, dy) * inv_maxr;
const float4 val = fmax(0.0f, intensity * _calc_vignette_spline(radius, spline, splinesize));
float4 pixel = Areadpixel(in, x, y);
const float mask = pixel.w;
pixel *= (1.0f + val);
pixel.w = (vigmask) ? val.w : mask;
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
lens_vignette (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
global float *pi,
const int step,
const int gwidth)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
float scale[3];
_lens_grid_sample(pi, x, y, step, gwidth, 4, 3, scale);
pixel.xyz *= (float3)(scale[0], scale[1], scale[2]);
write_imagef (out, (int2)(x, y), pixel);
}
#define MAXKNOTS 16
kernel void md_lens_correction(read_only image2d_t in,
write_only image2d_t out,
global float *knots_dist,
global float *cor_rgb,
const int owidth,
const int oheight,
const int iwidth,
const int iheight,
const float w2,
const float h2,
const float r,
const float scale,
const int roix,
const int roiy,
const int roox,
const int rooy,
const int knots,
const int itor_mode,
const int itor_width,
const int pass_mode)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= owidth || y >= oheight) return;
const float iscale = fp32_div(1.0f, scale);
const float cx = ((float)(roox + x) - w2) * iscale;
const float cy = ((float)(rooy + y) - h2) * iscale;
const float radius = r * dt_fast_hypot(cx, cy);
const float limw = (float)iwidth - 1.0f;
const float limh = (float)iheight - 1.0f;
float output[4];
for(int c = 0; c < 4; c++)
{
const int plane = (c == 3 || pass_mode) ? 1 : c;
const float dr =
_interpolate_linear_spline(knots_dist, &cor_rgb[plane * MAXKNOTS], knots, radius);
const float xs = clamp(dr*cx + w2 - roix, 0.0f, limw);
const float ys = clamp(dr*cy + h2 - roiy, 0.0f, limh);
output[c] = interpolation_compute_single(in, itor_mode, itor_width,
xs, ys, iwidth, iheight, c);
}
float4 pixel = {output[0], output[1], output[2], output[3]};
write_ipixel(out, (int2)(x, y), pixel);
}
#undef MAXKNOTS
/* kernel for flip */
__kernel void
flip(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int owidth,
const int oheight,
const int orientation)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
// ORIENTATION_FLIP_X = 2
int ox = (orientation & 2) ? width - x - 1 : x;
// ORIENTATION_FLIP_Y = 1
int oy = (orientation & 1) ? height - y - 1 : y;
// ORIENTATION_SWAP_XY = 4
if(orientation & 4)
{
const int tmp = ox;
ox = oy;
oy = tmp;
}
if(ox >= 0 && oy >= 0 && ox < owidth && oy < oheight)
{
const float4 pixel = readpixel(in, x, y);
write_imagef(out, (int2)(ox, oy), pixel);
}
}
float
envelope(const float L)
{
const float x = clipf(L/100.0f);
// const float alpha = 2.0f;
const float beta = 0.6f;
if(x < beta)
{
// return 1.0f-fabsf(x/beta-1.0f)^2
const float tmp = fabs(x/beta-1.0f);
return 1.0f-tmp*tmp;
}
else
{
const float tmp1 = (1.0f-x)/(1.0f-beta);
const float tmp2 = tmp1*tmp1;
const float tmp3 = tmp2*tmp1;
return 3.0f*tmp2 - 2.0f*tmp3;
}
}
/* kernel for monochrome */
kernel void
monochrome_filter(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const float a,
const float b,
const float size)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
pixel.x = 100.0f*dt_fast_expf(-clipf((fsquare(pixel.y - a) + fsquare(pixel.z - b)) / (2.0f * size)));
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
monochrome(read_only image2d_t in,
read_only image2d_t base,
write_only image2d_t out,
const int width,
const int height,
const float a,
const float b,
const float size,
float highlights)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
float4 basep = Areadpixel(base, x, y);
float filter = dt_fast_expf(-clipf((fsquare(pixel.y - a) + fsquare(pixel.z - b)) / (2.0f * size)));
float tt = envelope(pixel.x);
float t = tt + (1.0f-tt)*(1.0f-highlights);
pixel.x = mix(pixel.x, pixel.x*basep.x/100.0f, t);
pixel.y = pixel.z = 0.0f;
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the plugin colorout, fast matrix + shaper path only */
kernel void
colorout (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
global float *mat,
read_only image2d_t lutr,
read_only image2d_t lutg,
read_only image2d_t lutb,
global const float (*const a)[3], const int hdr_transfer)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
float XYZ[3], rgb[3];
float4 xyz = Lab_to_XYZ(pixel);
XYZ[0] = xyz.x;
XYZ[1] = xyz.y;
XYZ[2] = xyz.z;
for(int i=0;i<3;i++)
{
rgb[i] = 0.0f;
for(int j=0;j<3;j++) rgb[i] += mat[3*i+j]*XYZ[j];
}
dt_hdr_encode(rgb, hdr_transfer);
pixel.x = lerp_lookup_unbounded0(lutr, rgb[0], a[0]);
pixel.y = lerp_lookup_unbounded0(lutg, rgb[1], a[1]);
pixel.z = lerp_lookup_unbounded0(lutb, rgb[2], a[2]);
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the plugin colorout: the LittleCMS transform baked into a 3D LUT
over the Lab range LittleCMS encodes floats in, see _bake_clut() */
kernel void
colorout_clut(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
global const float *clut,
const int level,
const int gamutcheck,
global const float *gamut,
const int hdr_transfer)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float4 Lab = readpixel(in, x, y);
const float4 coord = clip4((float4)(Lab.x / 100.0f, (Lab.y + 128.0f) / 255.0f, (Lab.z + 128.0f) / 255.0f, 0.0f));
float4 pixel = dt_lut3d_tetrahedral(coord, clut, level);
pixel.w = Lab.w;
// as _transform_lcms(): out of gamut, or negative, shows as cyan
if(gamutcheck
&& (dt_lut3d_tetrahedral(coord, gamut, level).x > 0.5f
|| pixel.x < 0.0f || pixel.y < 0.0f || pixel.z < 0.0f))
pixel = (float4)(0.0f, 1.0f, 1.0f, 0.0f);
float rgb[3] = { pixel.x, pixel.y, pixel.z };
dt_hdr_encode(rgb, hdr_transfer);
pixel.x = rgb[0];
pixel.y = rgb[1];
pixel.z = rgb[2];
write_imagef(out, (int2)(x, y), pixel);
}
/* kernel for the levels plugin */
kernel void
levels (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
read_only image2d_t lut,
const float in_low,
const float in_high,
const float in_inv_gamma)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = Areadpixel(in, x, y);
const float L = pixel.x;
const float L_in = pixel.x/100.0f;
if(L_in <= in_low)
{
pixel.x = 0.0f;
}
else if(L_in >= in_high)
{
float percentage = (L_in - in_low) / (in_high - in_low);
pixel.x = 100.0f * dtcl_pow(percentage, in_inv_gamma);
}
else
{
float percentage = (L_in - in_low) / (in_high - in_low);
pixel.x = lookup(lut, percentage);
}
if(L_in > 0.01f)
{
pixel.y *= pixel.x/L;
pixel.z *= pixel.x/L;
}
else
{
pixel.y *= pixel.x;
pixel.z *= pixel.x;
}
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the colorzones plugin */
enum
{
DT_IOP_COLORZONES_L = 0,
DT_IOP_COLORZONES_C = 1,
DT_IOP_COLORZONES_h = 2
};
kernel void
colorzones_v3 (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int channel,
read_only image2d_t table_L,
read_only image2d_t table_a,
read_only image2d_t table_b)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
const float a = pixel.y;
const float b = pixel.z;
const float h = fmod(atan2(b, a) + DT_2PI_F, DT_2PI_F) / DT_2PI_F;
const float C = dt_fast_hypot(b, a);
float select = 0.0f;
float blend = 0.0f;
switch(channel)
{
case DT_IOP_COLORZONES_L:
select = fmin(1.0f, pixel.x/100.0f);
break;
case DT_IOP_COLORZONES_C:
select = fmin(1.0f, C/128.0f);
break;
default:
case DT_IOP_COLORZONES_h:
select = h;
blend = fsquare(1.0f - C/128.0f);
break;
}
const float Lm = (blend * 0.5f + (1.0f-blend)*lookup(table_L, select)) - 0.5f;
const float hm = (blend * 0.5f + (1.0f-blend)*lookup(table_b, select)) - 0.5f;
blend *= blend; // saturation isn't as prone to artifacts:
// const float Cm = 2.0f* (blend*0.5f + (1.0f-blend)*lookup(d->lut[1], select));
const float Cm = 2.0f * lookup(table_a, select);
const float L = pixel.x * dtcl_pow(2.0f, 4.0f*Lm);
pixel.x = L;
pixel.y = dtcl_cos(DT_2PI_F*(h + hm)) * Cm * C;
pixel.z = dtcl_sin(DT_2PI_F*(h + hm)) * Cm * C;
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
colorzones (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int channel,
read_only image2d_t table_L,
read_only image2d_t table_C,
read_only image2d_t table_h)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
float4 LCh;
const float normalize_C = 1.f / (128.0f * M_SQRT2_F);
LCh = Lab_2_LCH(pixel);
float select = 0.0f;
switch(channel)
{
case DT_IOP_COLORZONES_L:
select = LCh.x * 0.01f;
break;
case DT_IOP_COLORZONES_C:
select = LCh.y * normalize_C;
break;
case DT_IOP_COLORZONES_h:
default:
select = LCh.z;
break;
}
select = clipf(select);
LCh.x *= dtcl_pow(2.0f, 4.0f * (lookup(table_L, select) - .5f));
LCh.y *= 2.f * lookup(table_C, select);
LCh.z += lookup(table_h, select) - .5f;
pixel.xyz = LCH_2_Lab(LCh).xyz;
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the zonesystem plugin */
kernel void
zonesystem (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const int size,
global float *zonemap_offset,
global float *zonemap_scale)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
const float rzscale = (float)(size-1)/100.0f;
const int rz = clamp((int)(pixel.x*rzscale), 0, size-2);
const float zs = ((rz > 0) ? (zonemap_offset[rz]/pixel.x) : 0) + zonemap_scale[rz];
pixel.xyz *= zs;
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel to fill an image with a color (for the borders plugin). */
kernel void
borders_fill (write_only image2d_t out,
const int left,
const int top,
const int width,
const int height,
const float4 color)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x < left || y < top) return;
if(x >= width + left || y >= height + top) return;
write_imagef (out, (int2)(x, y), color);
}
/* kernel for the overexposed plugin. */
typedef enum dt_clipping_preview_mode_t
{
DT_CLIPPING_PREVIEW_GAMUT = 0,
DT_CLIPPING_PREVIEW_ANYRGB = 1,
DT_CLIPPING_PREVIEW_LUMINANCE = 2,
DT_CLIPPING_PREVIEW_SATURATION = 3
} dt_clipping_preview_mode_t;
kernel void
overexposed (read_only image2d_t in,
write_only image2d_t out,
read_only image2d_t tmp,
const int width,
const int height,
const float lower,
const float upper,
const float4 lower_color,
const float4 upper_color,
constant dt_colorspaces_iccprofile_info_cl_t *profile_info,
read_only image2d_t lut,
const int use_work_profile,
dt_clipping_preview_mode_t mode)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 pixel = readpixel(in, x, y);
float4 pixel_tmp = readpixel(tmp, x, y);
if(mode == DT_CLIPPING_PREVIEW_ANYRGB)
{
if(pixel_tmp.x >= upper || pixel_tmp.y >= upper || pixel_tmp.z >= upper)
pixel.xyz = upper_color.xyz;
else if(pixel_tmp.x <= lower && pixel_tmp.y <= lower && pixel_tmp.z <= lower)
pixel.xyz = lower_color.xyz;
}
else if(mode == DT_CLIPPING_PREVIEW_GAMUT && use_work_profile)
{
const float luminance = get_rgb_matrix_luminance(pixel, profile_info, profile_info->matrix_in, lut);
if(luminance >= upper)
{
pixel.xyz = upper_color.xyz;
}
else if(luminance <= lower)
{
pixel.xyz = lower_color.xyz;
}
else
{
float4 saturation = { 0.f, 0.f, 0.f, 0.f};
saturation = pixel_tmp - (float4)luminance;
saturation = dtcl_sqrt(saturation * saturation / ((float4)(luminance * luminance) + pixel_tmp * pixel_tmp));
if(saturation.x > upper || saturation.y > upper || saturation.z > upper ||
pixel_tmp.x >= upper || pixel_tmp.y >= upper || pixel_tmp.z >= upper)
pixel.xyz = upper_color.xyz;
else if(pixel_tmp.x <= lower && pixel_tmp.y <= lower && pixel_tmp.z <= lower)
pixel.xyz = lower_color.xyz;
}
}
else if(mode == DT_CLIPPING_PREVIEW_LUMINANCE && use_work_profile)
{
const float luminance = get_rgb_matrix_luminance(pixel, profile_info, profile_info->matrix_in, lut);
if(luminance >= upper)
pixel.xyz = upper_color.xyz;
else if(luminance <= lower)
pixel.xyz = lower_color.xyz;
}
else if(mode == DT_CLIPPING_PREVIEW_SATURATION && use_work_profile)
{
const float luminance = get_rgb_matrix_luminance(pixel, profile_info, profile_info->matrix_in, lut);
if(luminance < upper && luminance > lower)
{
float4 saturation = { 0.f, 0.f, 0.f, 0.f};
saturation = pixel_tmp - (float4)luminance;
saturation = dtcl_sqrt(saturation * saturation / ((float4)(luminance * luminance) + pixel_tmp * pixel_tmp));
if(saturation.x > upper || saturation.y > upper || saturation.z > upper ||
pixel_tmp.x >= upper || pixel_tmp.y >= upper || pixel_tmp.z >= upper)
pixel.xyz = upper_color.xyz;
else if(pixel_tmp.x <= lower && pixel_tmp.y <= lower && pixel_tmp.z <= lower)
pixel.xyz = lower_color.xyz;
}
}
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the rawoverexposed plugin. */
kernel void
rawoverexposed_mark_cfa (read_only image2d_t in,
write_only image2d_t out,
global float *pi,
const int width,
const int height,
read_only image2d_t raw,
const int raw_width,
const int raw_height,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
global unsigned int *threshold,
global float4 *colors)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const int piwidth = 2*width;
global float *ppi = pi + mad24(y, piwidth, 2*x);
const int raw_x = ppi[0];
const int raw_y = ppi[1];
if(raw_x < 0 || raw_y < 0 || raw_x >= raw_width || raw_y >= raw_height) return;
const uint raw_pixel = read_imageui(raw, sampleri, (int2)(raw_x, raw_y)).x;
const int c = fcol(raw_y, raw_x, filters, xtrans);
if(raw_pixel < threshold[c]) return;
float4 pixel = fmax(0.0f, Areadpixel(in, x, y));
const float4 color = colors[c & 3];
// cfa color
pixel.xyz = color.xyz;
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
rawoverexposed_mark_solid (read_only image2d_t in,
write_only image2d_t out,
global float *pi,
const int width,
const int height,
read_only image2d_t raw,
const int raw_width,
const int raw_height,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
global unsigned int *threshold,
const float4 solid_color)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const int piwidth = 2*width;
global float *ppi = pi + mad24(y, piwidth, 2*x);
const int raw_x = ppi[0];
const int raw_y = ppi[1];
if(raw_x < 0 || raw_y < 0 || raw_x >= raw_width || raw_y >= raw_height) return;
const uint raw_pixel = read_imageui(raw, sampleri, (int2)(raw_x, raw_y)).x;
const int c = fcol(raw_y, raw_x, filters, xtrans);
if(raw_pixel < threshold[c]) return;
float4 pixel = fmax(0.0f, Areadpixel(in, x, y));
// solid color
pixel.xyz = solid_color.xyz;
write_imagef (out, (int2)(x, y), pixel);
}
kernel void
rawoverexposed_falsecolor (read_only image2d_t in,
write_only image2d_t out,
global float *pi,
const int width,
const int height,
read_only image2d_t raw,
const int raw_width,
const int raw_height,
const unsigned int filters,
global const unsigned char (*const xtrans)[6],
global unsigned int *threshold)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const int piwidth = 2*width;
global float *ppi = pi + mad24(y, piwidth, 2*x);
const int raw_x = ppi[0];
const int raw_y = ppi[1];
if(raw_x < 0 || raw_y < 0 || raw_x >= raw_width || raw_y >= raw_height) return;
const uint raw_pixel = read_imageui(raw, sampleri, (int2)(raw_x, raw_y)).x;
const int c = fcol(raw_y, raw_x, filters, xtrans);
if(raw_pixel < threshold[c]) return;
float4 pixel = fmax(0.0f, Areadpixel(in, x, y));
if(c == 2) pixel.z = 0.0f;
else if(c == 1) pixel.y = 0.0f;
else if(c == 0) pixel.x = 0.0f;
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the lowlight plugin. */
kernel void
lowlight (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const float4 XYZ_sw,
read_only image2d_t lut)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float c = 0.5f;
const float threshold = 0.01f;
float V;
float w;
float4 pixel = readpixel(in, x, y);
float4 XYZ = Lab_to_XYZ(pixel);
// calculate scotopic luminance
if (XYZ.x > threshold)
{
// normal flow
V = XYZ.y * ( 1.33f * ( 1.0f + (XYZ.y+XYZ.z)/XYZ.x) - 1.68f );
}
else
{
// low red flow, avoids "snow" on dark noisy areas
V = XYZ.y * ( 1.33f * ( 1.0f + (XYZ.y+XYZ.z)/threshold) - 1.68f );
}
// scale using empiric coefficient and fit inside limits
V = clipf(c*V);
// blending coefficient from curve
w = lookup(lut, pixel.x/100.0f);
XYZ = w * XYZ + (1.0f - w) * V * XYZ_sw;
pixel = XYZ_to_Lab(XYZ);
write_imagef (out, (int2)(x, y), pixel);
}
/* kernel for the contrast lightness saturation module */
kernel void
colisa (read_only image2d_t in,
write_only image2d_t out,
unsigned int width,
unsigned int height,
const float saturation,
read_only image2d_t ctable,
constant float *ca,
read_only image2d_t ltable,
constant float *la)
{
const unsigned int x = get_global_id(0);
const unsigned int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 i = readpixel(in, x, y);
float4 o;
o.x = lookup_unbounded(ctable, i.x/100.0f, ca);
o.x = lookup_unbounded(ltable, o.x/100.0f, la);
o.y = i.y*saturation;
o.z = i.z*saturation;
o.w = i.w;
write_imagef(out, (int2)(x, y), o);
}
/* kernel for the unbreak input profile module - gamma version */
kernel void
profilegamma (read_only image2d_t in,
write_only image2d_t out,
int width,
int height,
read_only image2d_t table,
constant float *ta)
{
const unsigned int x = get_global_id(0);
const unsigned int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 i = readpixel(in, x, y);
float4 o;
o.x = lookup_unbounded(table, i.x, ta);
o.y = lookup_unbounded(table, i.y, ta);
o.z = lookup_unbounded(table, i.z, ta);
o.w = i.w;
write_imagef(out, (int2)(x, y), o);
}
/* kernel for the unbreak input profile module - log version */
kernel void
profilegamma_log (read_only image2d_t in,
write_only image2d_t out,
int width,
int height,
const float dynamic_range,
const float shadows_range,
const float grey)
{
const unsigned int x = get_global_id(0);
const unsigned int y = get_global_id(1);
if(x >= width || y >= height) return;
float4 i = readpixel(in, x, y);
const float4 noise = pow((float4)2.0f, (float4)-16.0f);
const float4 dynamic4 = dynamic_range;
const float4 shadows4 = shadows_range;
const float4 grey4 = grey;
float4 o;
o = (i < noise) ? noise : i / grey4;
o = (dtcl_log2(o) - shadows4) / dynamic4;
o = (o < noise) ? noise : o;
i.xyz = o.xyz;
write_imagef(out, (int2)(x, y), i);
}
/* kernel for the interpolation resample helper */
kernel void
interpolation_resample (read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const global int *hmeta,
const global int *vmeta,
const global int *hlength,
const global int *vlength,
const global int *hindex,
const global int *vindex,
const global float *hkernel,
const global float *vkernel,
const int htaps,
const int vtaps,
local float *lkernel,
local int *lindex,
local float4 *buffer)
{
const int x = get_global_id(0);
const int yi = get_global_id(1);
const int ylsz = get_local_size(1);
const int xlid = get_local_id(0);
const int ylid = get_local_id(1);
const int y = yi / vtaps;
const int iy = yi % vtaps;
// Initialize resampling indices
const int xm = min(x, width - 1);
const int ym = min(y, height - 1);
const int hlidx = hmeta[xm*3]; // H(orizontal) L(ength) I(n)d(e)x
const int hkidx = hmeta[xm*3+1]; // H(orizontal) K(ernel) I(n)d(e)x
const int hiidx = hmeta[xm*3+2]; // H(orizontal) I(ndex) I(n)d(e)x
const int vlidx = vmeta[ym*3]; // V(ertical) L(ength) I(n)d(e)x
const int vkidx = vmeta[ym*3+1]; // V(ertical) K(ernel) I(n)d(e)x
const int viidx = vmeta[ym*3+2]; // V(ertical) I(ndex) I(n)d(e)x
const int hl = hlength[hlidx]; // H(orizontal) L(ength)
const int vl = vlength[vlidx]; // V(ertical) L(ength)
// generate local copy of horizontal index field and kernel
for(int n = 0; n <= htaps/ylsz; n++)
{
int k = mad24(n, ylsz, ylid);
if(k >= hl) continue;
lindex[k] = hindex[hiidx+k];
lkernel[k] = hkernel[hkidx+k];
}
barrier(CLK_LOCAL_MEM_FENCE);
// horizontal convolution kernel; store intermediate result in local buffer
if(x < width && y < height)
{
const int yvalid = iy < vl;
const int yy = yvalid ? vindex[viidx+iy] : -1;
float4 vpixel = (float4)0.0f;
for (int ix = 0; ix < hl && yvalid; ix++)
{
const int xx = lindex[ix];
float4 hpixel = readpixel(in, xx, yy);
vpixel += hpixel * lkernel[ix];
}
buffer[ylid] = yvalid ? vpixel * vkernel[vkidx+iy] : (float4)0.0f;
}
else
buffer[ylid] = (float4)0.0f;
barrier(CLK_LOCAL_MEM_FENCE);
// recursively reduce local buffer (vertical convolution kernel)
for(int offset = vtaps / 2; offset > 0; offset >>= 1)
{
if (iy < offset)
{
buffer[ylid] += buffer[ylid + offset];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
// store final result
if (iy == 0 && x < width && y < height)
{
write_ipixel(out, (int2)(x, y), buffer[ylid]);
}
}
/* the statistics of dt_color_picker_helper() in src/common/color_picker.c,
reduced on the device. Each work item reduces one tile of the box and
writes 16 floats: the sums, minima, maxima and pixel counts of the four
channels. The host adds up the tiles, so only those come back. */
#define DT_PICK_PLAIN 0
#define DT_PICK_LCH 1
#define DT_PICK_HSL 2
#define DT_PICK_JZCZHZ 3
typedef struct dt_picker_stats_t
{
float4 sum, low, high, count;
} dt_picker_stats_t;
static inline void _picker_stats_store(const dt_picker_stats_t *s, global float *stats, const int item)
{
vstore4(s->sum, 4 * item, stats);
vstore4(s->low, 4 * item + 1, stats);
vstore4(s->high, 4 * item + 2, stats);
vstore4(s->count, 4 * item + 3, stats);
}
// blur_2D_Bspline() at one scale and without clipping, as the picker's
// denoise does over the whole image, with the edges clamped to the image
static inline float4 _picker_denoised(read_only image2d_t in, const int x, const int y,
const int width, const int height)
{
const float filter[5] = { 1.0f / 16.0f, 4.0f / 16.0f, 6.0f / 16.0f, 4.0f / 16.0f, 1.0f / 16.0f };
float4 acc = (float4)0.0f;
for(int ii = 0; ii < 5; ii++)
{
const int xx = clamp(x + ii - 2, 0, width - 1);
float4 v = (float4)0.0f;
for(int jj = 0; jj < 5; jj++)
v += filter[jj] * read_imagef(in, sampleri, (int2)(xx, clamp(y + jj - 2, 0, height - 1)));
acc += filter[ii] * v;
}
return acc;
}
// rgb_to_JzCzhz() of color_picker.c: the profile's matrix to XYZ D50, then
// the Bradford adaptation to D65
static inline float4 _picker_jzczhz(const float4 rgb,
constant const dt_colorspaces_iccprofile_info_cl_t *const profile_info,
read_only image2d_t lut)
{
const float4 D50 = rgb_matrix_to_xyz(rgb, profile_info, profile_info->matrix_in, lut);
const float4 D65 = (float4)(0.9555766f * D50.x - 0.0230393f * D50.y + 0.0631636f * D50.z,
-0.0282895f * D50.x + 1.0099416f * D50.y + 0.0210077f * D50.z,
0.0122982f * D50.x - 0.0204830f * D50.y + 1.3299098f * D50.z,
0.0f);
const float4 JzAzBz = XYZ_to_JzAzBz(D65);
const float h = atan2(JzAzBz.z, JzAzBz.y) / DT_2PI_F;
return (float4)(JzAzBz.x, hypot(JzAzBz.y, JzAzBz.z), h >= 0.0f ? h : 1.0f + h, 0.0f);
}
kernel void
color_picker_stats_4ch(read_only image2d_t in,
global float *stats,
const int width,
const int height,
const int bx0,
const int by0,
const int bx1,
const int by1,
const int tile_w,
const int tile_h,
const int tiles_x,
const int tiles_y,
const int mode,
const int denoise,
constant const dt_colorspaces_iccprofile_info_cl_t *const profile_info,
read_only image2d_t lut)
{
const int tx = get_global_id(0);
const int ty = get_global_id(1);
if(tx >= tiles_x || ty >= tiles_y) return;
const int x0 = bx0 + tx * tile_w;
const int y0 = by0 + ty * tile_h;
const int x1 = min(x0 + tile_w, bx1);
const int y1 = min(y0 + tile_h, by1);
dt_picker_stats_t s = { (float4)0.0f, (float4)FLT_MAX, (float4)(-FLT_MAX), (float4)0.0f };
for(int y = y0; y < y1; y++)
for(int x = x0; x < x1; x++)
{
const float4 p = denoise ? _picker_denoised(in, x, y, width, height)
: read_imagef(in, sampleri, (int2)(x, y));
float4 pick = p;
if(mode != DT_PICK_PLAIN)
{
pick = mode == DT_PICK_LCH ? Lab_2_LCH(p)
: mode == DT_PICK_HSL ? RGB_2_HSL(p)
: _picker_jzczhz(p, profile_info, lut);
// the hue shifted by half a turn, for sensible minima and maxima,
// as _update_stats_4ch() does
pick.w = pick.z < 0.5f ? pick.z + 0.5f : pick.z - 0.5f;
}
s.sum += pick;
s.low = fmin(s.low, pick);
s.high = fmax(s.high, pick);
s.count += 1.0f;
}
_picker_stats_store(&s, stats, ty * tiles_x + tx);
}
// a Bayer or X-Trans mosaic, per CFA color
kernel void
color_picker_stats_mosaic(read_only image2d_t in,
global float *stats,
const int bx0,
const int by0,
const int bx1,
const int by1,
const int tile_w,
const int tile_h,
const int tiles_x,
const int tiles_y,
const int rx,
const int ry,
const unsigned int filters,
global const unsigned char (*const xtrans)[6])
{
const int tx = get_global_id(0);
const int ty = get_global_id(1);
if(tx >= tiles_x || ty >= tiles_y) return;
const int x0 = bx0 + tx * tile_w;
const int y0 = by0 + ty * tile_h;
const int x1 = min(x0 + tile_w, bx1);
const int y1 = min(y0 + tile_h, by1);
float sum[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float low[4] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
float high[4] = { -FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX };
float count[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
for(int y = y0; y < y1; y++)
for(int x = x0; x < x1; x++)
{
const int c = fcol(y + ry, x + rx, filters, xtrans);
const float v = read_imagef(in, sampleri, (int2)(x, y)).x;
sum[c] += v;
low[c] = fmin(low[c], v);
high[c] = fmax(high[c], v);
count[c] += 1.0f;
}
const dt_picker_stats_t s = { vload4(0, sum), vload4(0, low), vload4(0, high), vload4(0, count) };
_picker_stats_store(&s, stats, ty * tiles_x + tx);
}
/* kernel for the interpolation copy helper */
kernel void
interpolation_copy(read_only image2d_t dev_in,
write_only image2d_t dev_out,
const int owidth,
const int oheight,
const int iwidth,
const int iheight,
const int dx,
const int dy)
{
const int ocol = get_global_id(0);
const int orow = get_global_id(1);
if(ocol >= owidth || orow >= oheight) return;
float4 pix = (float4)0.0f;
const int irow = orow + dy;
const int icol = ocol + dx;
if(irow < iheight && irow >= 0 && icol < iwidth && icol >= 0)
{
pix = Areadpixel(dev_in, icol, irow);
}
write_imagef(dev_out, (int2)(ocol, orow), pix);
}
// process() of src/iop/hotpixels.c: each work item writes its own sensel,
// either copied or replaced by the largest of its four like-colored
// neighbors. mode 0 is Bayer, with the neighbors 2 sensels away; 1 is a
// monochrome sensor, with the direct neighbors; 2 is X-Trans, with the
// neighbors from offsets[(row % 6) * 6 + col % 6], as (x, y) pairs
kernel void
hotpixels(read_only image2d_t in,
write_only image2d_t out,
const int width,
const int height,
const float threshold,
const float multiplier,
const int min_neighbours,
const int mode,
global const int4 *offsets,
global unsigned int *fixed)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= width || y >= height) return;
const float pixel = Areadsingle(in, x, y);
float o = pixel;
const int margin = mode == 1 ? 1 : 2;
if(pixel > threshold && x >= margin && y >= margin && x < width - margin && y < height - margin)
{
int2 n[4];
if(mode == 2)
{
const int k = (y % 6) * 6 + x % 6;
const int4 a = offsets[2 * k];
const int4 b = offsets[2 * k + 1];
n[0] = a.xy;
n[1] = a.zw;
n[2] = b.xy;
n[3] = b.zw;
}
else
{
const int s = mode == 1 ? 1 : 2;
n[0] = (int2)(-s, 0);
n[1] = (int2)(0, -s);
n[2] = (int2)(s, 0);
n[3] = (int2)(0, s);
}
const float mid = pixel * multiplier;
int count = 0;
float maxin = 0.0f;
for(int i = 0; i < 4; i++)
{
const float other = Areadsingle(in, x + n[i].x, y + n[i].y);
if(mid > other)
{
count++;
maxin = fmax(maxin, other);
}
}
if(count >= min_neighbours)
{
o = maxin;
atomic_inc(fixed);
}
}
write_imagef(out, (int2)(x, y), o);
}
// the module histograms of dt_histogram_helper() in src/common/histogram.c,
// counted on the device so that only the bins are read back. bins holds 4
// counts per bin as on the CPU and must be zeroed by the caller. With
// use_local each work group counts into lhist (3 * bins_count) first and
// adds that to bins; without, for bin counts too large for local memory,
// every pixel goes to bins directly.
#define DT_HISTOGRAM_RGB 0
#define DT_HISTOGRAM_RGB_COMPENSATED 1
#define DT_HISTOGRAM_LAB 2
#define DT_HISTOGRAM_LCH 3
// dt_ioppr_compensate_middle_grey(): the lightness of a gray of value x
static inline float _histogram_compensate(const float x,
constant const dt_colorspaces_iccprofile_info_cl_t *const profile_info,
read_only image2d_t lut)
{
const float4 xyz = rgb_matrix_to_xyz((float4)(x, x, x, 0.0f), profile_info, profile_info->matrix_in, lut);
return XYZ_to_Lab(xyz).x * 0.01f;
}
kernel void
histogram_collect(read_only image2d_t in,
const int x0,
const int y0,
const int x1,
const int y1,
const int bins_count,
const int mode,
const int use_local,
constant const dt_colorspaces_iccprofile_info_cl_t *const profile_info,
read_only image2d_t lut,
global unsigned int *bins,
local unsigned int *lhist)
{
const int x = x0 + get_global_id(0);
const int y = y0 + get_global_id(1);
const int l = mad24((int)get_local_id(1), (int)get_local_size(0), (int)get_local_id(0));
const int lsz = mul24((int)get_local_size(0), (int)get_local_size(1));
if(use_local)
{
for(int i = l; i < 3 * bins_count; i += lsz) lhist[i] = 0;
barrier(CLK_LOCAL_MEM_FENCE);
}
if(x < x1 && y < y1)
{
const float4 p = read_imagef(in, sampleri, (int2)(x, y));
const float max_bin = bins_count - 1;
float4 b;
if(mode == DT_HISTOGRAM_RGB)
b = max_bin * p;
else if(mode == DT_HISTOGRAM_RGB_COMPENSATED)
b = max_bin * (float4)(_histogram_compensate(p.x, profile_info, lut),
_histogram_compensate(p.y, profile_info, lut),
_histogram_compensate(p.z, profile_info, lut), 0.0f);
else if(mode == DT_HISTOGRAM_LAB)
b = (float4)(max_bin / 100.0f, max_bin / 256.0f, max_bin / 256.0f, 0.0f)
* (p + (float4)(0.0f, 128.0f, 128.0f, 0.0f));
else
b = (float4)(max_bin / 100.0f, max_bin / (128.0f * M_SQRT2_F), max_bin, 0.0f) * Lab_2_LCH(p);
const int4 bin = convert_int4(clamp(b, 0.0f, max_bin));
if(use_local)
{
atomic_inc(&lhist[bin.x]);
atomic_inc(&lhist[bins_count + bin.y]);
atomic_inc(&lhist[2 * bins_count + bin.z]);
}
else
{
atomic_inc(&bins[4 * bin.x]);
atomic_inc(&bins[4 * bin.y + 1]);
atomic_inc(&bins[4 * bin.z + 2]);
}
}
if(!use_local) return;
barrier(CLK_LOCAL_MEM_FENCE);
for(int i = l; i < 3 * bins_count; i += lsz)
{
const unsigned int count = lhist[i];
if(count) atomic_add(&bins[4 * (i % bins_count) + i / bins_count], count);
}
}
// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on