Related
I think there should be some way from the distance to the primaries, or the intersection of the line that connects the point and the white point with the limits of the gamut.
I think you can approximate your Y very well with numerical methods, but it is quite hard to pin down a formula.
The max for Y is 100, so to avoid the difficulty of finding a starting point that is inside sRGB, I would probably implement a numerical approximation of a slightly different question.
I know that a Yxy of (100, x, y) is probably out of bounds, so I would decrease Y until I'm in sRGB gamut. An interval of about 0.5 - 0.2 should suffice.
The simplest and most accurate way to do this is to convert to sRGB, set the luminance to the maximum, and convert back to XYZ. When converting to sRGB you have to make sure to choose a value for Y that will give a result within the gamut for any point x,y (it must be below the minimum relative luminance coefficient, which for sRGB is ≃ 0.072 for blue). Note that this doesn't check that the point x,y is inside the gamut:
#include <Math.au3>
;XYZtosRGB
Global $RsRGB[3] = [3.2409699, -1.5373832, -0.4986108]
Global $GsRGB[3] = [-0.9692436, 1.8759675, 0.0415551]
Global $BsRGB[3] = [0.0556301, -0.203977, 1.0569715]
;sRGBtoXYZ
Global $XsRGB[3] = [0.4123908, 0.3575843, 0.1804808]
Global $YsRGB[3] = [0.212639, 0.7151687, 0.0721923]
Global $ZsRGB[3] = [0.0193308, 0.1191948, 0.9505322]
Global $Wx = 0.3127, $Wy = 0.3290
Func DP($T, $S)
return $T[0] * $S[0] + $T[1] * $S[1] + $T[2] * $S[2]
EndFunc
Func xyYtoXYZ($xyY)
Local $XYZ = $xyY
If $xyY[2] <> 0 Then
$XYZ[0] = $xyY[0] * $xyY[2] / $xyY[1]
$XYZ[2] = $xyY[2] * (1 - $xyY[0] - $xyY[1]) / $xyY[1]
Else
$XYZ[0] = 0
$XYZ[2] = 0
EndIf
$XYZ[1] = $xyY[2]
Return $XYZ
EndFunc
Func FindY($x, $y)
Local $xyY = [$x, $y, 0.07]
Local $XYZ = xyYtoXYZ($xyY)
Local $sRGBin = [DP($RsRGB, $XYZ), DP($GsRGB, $XYZ), DP($BsRGB, $XYZ)]
Local $inc = _Min(_Min(1 / $RGBin[0], 1 / $RGBin[1]), 1 / $RGBin[2])
Local $sRGBout = [$sRGBin[0] * $inc, $sRGBin[1] * $inc, $sRGBin[2] * $inc]
Return DP($YsRGB, $sRGBout)
EndFunc
I have a section of code that calculates the percent of pixels in a binary grid with value == 1 using a 50x50 sliding window:
f = #(x) numel(x(x==1))/numel(x);
I2 = nlfilter(buffer,[50 50],f);
I have heard that imfilter is a more efficient way to make focal calculations and, as such, hope to do some benchmarking. What is the imfilter() equivalent of the above nlfilter() function?
The complete code with sample data is attached
% Generate a grid of 0's to begin with.
m = zeros(400, 400, 'uint8');
% Generate 100 random "trees".
numRandom = 100;
linearIndices = randi(numel(m), 1, numRandom);
% Assign a radius value of 1-12 to each tree
m(linearIndices) = randi(12, [numel(linearIndices) 1]);
buffer = false(size(m));
for radius =1:12 % update to actual range
im_r = m==radius;
se = strel('disk',radius);
im_rb = imfilter(im_r, double(se.getnhood()));
buffer = buffer | im_rb;
end
% The imfilter approach
% The nlfilter approach
f = #(x) numel(x(x==1))/numel(x);
I2 = nlfilter(buffer,[50 50],f);
imshowpair(buffer,I2, 'montage')
For binary images (only 0s and 1s), what you have done is simple summation in a sliding window. Thus, the average filter of imfilter can be adopted here as:
h = fspecial( 'average', 50 );
I2 = imfilter( double( buffer ), h );
I need an algorithm to convert the HCL color to RGB and backward RGB to HCL keeping in mind that these color spaces have different gamuts (I need to constrain the HCL colors to those that can be reproduced in RGB color space). What is the algorithm for this (the algorithm is intended to be implemented in Wolfram Mathematica that supports natively only RGB color)? I have no experience in working with color spaces.
P.S. Some articles about HCL color:
M. Sarifuddin (2005). A new perceptually uniform color space with associated color similarity measure for content–based image and video retrieval.
Zeileis, Hornik and Murrell (2009): Escaping RGBland: Selecting Colors for Statistical Graphics // Computational Statistics & Data Analysis Volume 53, Issue 9, 1 July 2009, Pages 3259-3270
UPDATE:
As pointed out by Jonathan Jansson, in the above two articles different color spaces are described by the name "HCL": "The second article uses LCh(uv) which is the same as Luv* but described in polar coordiates where h(uv) is the angle of the u* and v* coordinate and C* is the magnitude of that vector". So in fact I need an algorithm for converting RGB to Luv* and backward.
I was just learing about the HCL colorspace too. The colorspace used in the two articles in your question seems to be different color spaces though.
The second article uses L*C*h(uv) which is the same as L*u*v* but described in polar coordiates where h(uv) is the angle of the u* and v* coordiate and C* is the magnitude of that vector.
The LCH color space in the first article seems to describe another color space than that uses a more algorithmical conversion. There is also another version of the first paper here: http://isjd.pdii.lipi.go.id/admin/jurnal/14209102121.pdf
If you meant to use the CIE L*u*v* you need to first convert sRGB to CIE XYZ and then convert to CIE L*u*v*. RGB actually refers to sRGB in most cases so there is no need to convert from RGB to sRGB.
All source code needed
Good article about how conversion to XYZ works
Nice online converter
But I can't answer your question about how to constrain the colors to the sRGB space. You could just throw away RGB colors which are outside the range 0 to 1 after conversion. Just clamping colors can give quite weird results. Try to go to the converter and enter the color RGB 0 0 255 and convert to L*a*b* (similar to L*u*v*) and then increase L* to say 70 and convert it back and the result is certainly not blue anymore.
Edit: Corrected the URL
Edit: Merged another answer into this answer
HCL is a very generic name there are a lot of ways to have a hue, a chroma, and a lightness. Chroma.js for example has something it calls HCL which is polar coord converted Lab (when you look at the actual code). Other implementations, even ones linked from that same site use Polar Luv. Since you can simply borrow the L factor and derive the hue by converting to polar coords these are both valid ways to get those three elements. It is far better to call them Polar Lab and Polar Luv, because of the confusion factor.
M. Sarifuddin (2005)'s algorithm is not Polar Luv or Polar Lab and is computationally simpler (you don't need to derive Lab or Luv space first), and may actually be better. There are some things that seem wrong in the paper. For example applying a Euclidean distance to a CIE L*C*H* colorspace. The use of a Hue means it's necessarily round, and just jamming that number into A²+B²+C² is going to give you issues. The same is true to apply a hue-based colorspace to D94 or D00 as these are distance algorithms with built in corrections specific to Lab colorspace. Unless I'm missing something there, I'd disregard figures 6-8. And I question the rejection tolerances in the graphics. You could set a lower threshold and do better, and the numbers between color spaces are not normalized. In any event, despite a few seeming flaws in the paper, the algorithm described is worth a shot. You might want to do Euclidean on RGB if it doesn't really matter much. But, if you're shopping around for color distance algorithms, here you go.
Here is HCL as given by M. Sarifuddin implemented in Java. Having read the paper repeatedly I cannot avoid the conclusion that it scales the distance by a factor of between 0.16 and 180.16 with regard to the change in hue in the distance_hcl routine. This is such a profound factor that it almost cannot be right at all. And makes the color matching suck. I have the paper's line commented out and use a line with only the Al factor. Scaling Luminescence by constant ~1.4 factor isn't going to make it unusable. With neither scale factor it ends up being identical to cycldistance.
http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip is corrected and improved version of the paper.
static final public double Y0 = 100;
static final public double gamma = 3;
static final public double Al = 1.4456;
static final public double Ach_inc = 0.16;
public void rgb2hcl(double[] returnarray, int r, int g, int b) {
double min = Math.min(Math.min(r, g), b);
double max = Math.max(Math.max(r, g), b);
if (max == 0) {
returnarray[0] = 0;
returnarray[1] = 0;
returnarray[2] = 0;
return;
}
double alpha = (min / max) / Y0;
double Q = Math.exp(alpha * gamma);
double rg = r - g;
double gb = g - b;
double br = b - r;
double L = ((Q * max) + ((1 - Q) * min)) / 2;
double C = Q * (Math.abs(rg) + Math.abs(gb) + Math.abs(br)) / 3;
double H = Math.toDegrees(Math.atan2(gb, rg));
/*
//the formulae given in paper, don't work.
if (rg >= 0 && gb >= 0) {
H = 2 * H / 3;
} else if (rg >= 0 && gb < 0) {
H = 4 * H / 3;
} else if (rg < 0 && gb >= 0) {
H = 180 + 4 * H / 3;
} else if (rg < 0 && gb < 0) {
H = 2 * H / 3 - 180;
} // 180 causes the parts to overlap (green == red) and it oddly crumples up bits of the hue for no good reason. 2/3H and 4/3H expanding and contracting quandrants.
*/
if (rg < 0) {
if (gb >= 0) H = 90 + H;
else { H = H - 90; }
} //works
returnarray[0] = H;
returnarray[1] = C;
returnarray[2] = L;
}
public double cycldistance(double[] hcl1, double[] hcl2) {
double dL = hcl1[2] - hcl2[2];
double dH = Math.abs(hcl1[0] - hcl2[0]);
double C1 = hcl1[1];
double C2 = hcl2[1];
return Math.sqrt(dL*dL + C1*C1 + C2*C2 - 2*C1*C2*Math.cos(Math.toRadians(dH)));
}
public double distance_hcl(double[] hcl1, double[] hcl2) {
double c1 = hcl1[1];
double c2 = hcl2[1];
double Dh = Math.abs(hcl1[0] - hcl2[0]);
if (Dh > 180) Dh = 360 - Dh;
double Ach = Dh + Ach_inc;
double AlDl = Al * Math.abs(hcl1[2] - hcl2[2]);
return Math.sqrt(AlDl * AlDl + (c1 * c1 + c2 * c2 - 2 * c1 * c2 * Math.cos(Math.toRadians(Dh))));
//return Math.sqrt(AlDl * AlDl + Ach * (c1 * c1 + c2 * c2 - 2 * c1 * c2 * Math.cos(Math.toRadians(Dh))));
}
I'm familiar with quite a few color spaces, but this one is new to me. Alas, Mathematica's ColorConvert doesn't know it either.
I found an rgb2hcl routine here, but no routine going the other way.
A more comprehensive colorspace conversion package can be found here. It seems to be able to do conversions to and from all kinds of color spaces. Look for the file colorspace.c in colorspace_1.1-0.tar.gz\colorspace_1.1-0.tar\colorspace\src. Note that HCL is known as PolarLUV in this package.
As mentioned in other answers, there are a lot of ways to implement an HCL colorspace and map that into RGB.
HSLuv ended up being what I used, and has MIT-licensed implementations in C, C#, Go, Java, PHP, and several other languages. It's similar to CIELUV LCh but fully maps to RGB. The implementations are available on GitHub.
Here's a short graphic from the website describing the HSLuv color space, with the implementation output in the right two panels:
I was looking to interpolate colors on the web and found HCL to be the most fitting color space, I couldn't find any library making the conversion straightforward and performant so I wrote my own.
There's many constants at play, and some of them vary significantly depending on where you source them from.
My target being the web, I figured I'd be better off matching the chromium source code. Here's a minimized snippet written in Typescript, the sRGB XYZ matrix is precomputed and all constants are inlined.
const rgb255 = (v: number) => (v < 255 ? (v > 0 ? v : 0) : 255);
const b1 = (v: number) => (v > 0.0031308 ? v ** (1 / 2.4) * 269.025 - 14.025 : v * 3294.6);
const b2 = (v: number) => (v > 0.2068965 ? v ** 3 : (v - 4 / 29) * (108 / 841));
const a1 = (v: number) => (v > 10.314724 ? ((v + 14.025) / 269.025) ** 2.4 : v / 3294.6);
const a2 = (v: number) => (v > 0.0088564 ? v ** (1 / 3) : v / (108 / 841) + 4 / 29);
function fromHCL(h: number, c: number, l: number): RGB {
const y = b2((l = (l + 16) / 116));
const x = b2(l + (c / 500) * Math.cos((h *= Math.PI / 180)));
const z = b2(l - (c / 200) * Math.sin(h));
return [
rgb255(b1(x * 3.021973625 - y * 1.617392459 - z * 0.404875592)),
rgb255(b1(x * -0.943766287 + y * 1.916279586 + z * 0.027607165)),
rgb255(b1(x * 0.069407491 - y * 0.22898585 + z * 1.159737864)),
];
}
function toHCL(r: number, g: number, b: number) {
const y = a2((r = a1(r)) * 0.222488403 + (g = a1(g)) * 0.716873169 + (b = a1(b)) * 0.06060791);
const l = 500 * (a2(r * 0.452247074 + g * 0.399439023 + b * 0.148375274) - y);
const q = 200 * (y - a2(r * 0.016863605 + g * 0.117638439 + b * 0.865350722));
const h = Math.atan2(q, l) * (180 / Math.PI);
return [h < 0 ? h + 360 : h, Math.sqrt(l * l + q * q), 116 * y - 16];
}
Here's a playground for the above snippet.
It includes d3's interpolateHCL and the browser native css transition for comparaison.
https://svelte.dev/repl/0a40a8348f8841d0b7007c58e4d9b54c
Here's a gist to do the conversion to and from any web color format and interpolate it in the HCL color space.
https://gist.github.com/pushkine/c8ba98294233d32ab71b7e19a0ebdbb9
I think
if (rg < 0) {
if (gb >= 0) H = 90 + H;
else { H = H - 90; }
} //works
is not really necessary because of atan2(,) instead of atan(/) from paper (but dont now anything about java atan2(,) especially
Hey guys, I got this error message when I tried to trigger the function below. Can anybody help me out? Thanks!
>> changeYuv('tilt.yuv',352,288,1:40,40);
??? Index exceeds matrix dimensions.
Error in ==> changeYuv at 32
j=histogram(imgYuv(:,:,1,k+1));
>> [x,y,z,a]=size(imgYuv)
x =
288
y =
352
z =
3
a =
40
The source code:
function [imgYuv, S]= changeYuv(fileName, width, height, idxFrame, nFrames)
% load RGB movie [0, 255] from YUV 4:2:0 file
fileId = fopen(fileName, 'r');
subSampleMat = [1, 1; 1, 1];
nrFrame = length(idxFrame);
for f = 1 : 1 : nrFrame
% search fileId position
sizeFrame = 1.5 * width * height;
fseek(fileId, (idxFrame(f) - 1) * sizeFrame, 'bof');
% read Y component
buf = fread(fileId, width * height, 'uchar');
imgYuv(:, :, 1,f) = reshape(buf, width, height).';
% read U component
buf = fread(fileId, width / 2 * height / 2, 'uchar');
imgYuv(:, :, 2,f) = kron(reshape(buf, width / 2, height / 2).', subSampleMat); % reshape and upsample
% read V component
buf = fread(fileId, width / 2 * height / 2, 'uchar');
imgYuv(:, :, 3,f) = kron(reshape(buf, width / 2, height / 2).', subSampleMat); % reshape and upsample
%histogram difference of Y component
for k=1:(nFrames-1)
h=histogram(imgYuv(:,:,1,k));
j=histogram(imgYuv(:,:,1,k+1));
X=abs(h-j)/256;
S(k)=sum(X);
end
end
fclose(fileId);
On every iteration of the outer loop, you appear to be growing imgYuv by one in the 4th dimension, starting from empty. But your inner loop always loops from 1 to nFrames-1. Therefore, it would seem to me like you're trying to access beyond the extent of imgYuv.
On an unrelated note, growing an array like this is typically very slow. You're much better off initialising imgYuv before you start, i.e. imgYuv = zeros([height,width,3,nFrames]).
I'm currently writing a program to generate really enormous (65536x65536 pixels and above) Mandelbrot images, and I'd like to devise a spectrum and coloring scheme that does them justice. The wikipedia featured mandelbrot image seems like an excellent example, especially how the palette remains varied at all zoom levels of the sequence. I'm not sure if it's rotating the palette or doing some other trick to achieve this, though.
I'm familiar with the smooth coloring algorithm for the mandelbrot set, so I can avoid banding, but I still need a way to assign colors to output values from this algorithm.
The images I'm generating are pyramidal (eg, a series of images, each of which has half the dimensions of the previous one), so I can use a rotating palette of some sort, as long as the change in the palette between subsequent zoom levels isn't too obvious.
This is the smooth color algorithm:
Lets say you start with the complex number z0 and iterate n times until it escapes. Let the end point be zn.
A smooth value would be
nsmooth := n + 1 - Math.log(Math.log(zn.abs()))/Math.log(2)
This only works for mandelbrot, if you want to compute a smooth function for julia sets, then use
Complex z = new Complex(x,y);
double smoothcolor = Math.exp(-z.abs());
for(i=0;i<max_iter && z.abs() < 30;i++) {
z = f(z);
smoothcolor += Math.exp(-z.abs());
}
Then smoothcolor is in the interval (0,max_iter).
Divide smoothcolor with max_iter to get a value between 0 and 1.
To get a smooth color from the value:
This can be called, for example (in Java):
Color.HSBtoRGB(0.95f + 10 * smoothcolor ,0.6f,1.0f);
since the first value in HSB color parameters is used to define the color from the color circle.
Use the smooth coloring algorithm to calculate all of the values within the viewport, then map your palette from the lowest to highest value. Thus, as you zoom in and the higher values are no longer visible, the palette will scale down as well. With the same constants for n and B you will end up with a range of 0.0 to 1.0 for a fully zoomed out set, but at deeper zooms the dynamic range will shrink, to say 0.0 to 0.1 at 200% zoom, 0.0 to 0.0001 at 20000% zoom, etc.
Here is a typical inner loop for a naive Mandelbrot generator. To get a smooth colour you want to pass in the real and complex "lengths" and the iteration you bailed out at. I've included the Mandelbrot code so you can see which vars to use to calculate the colour.
for (ix = 0; ix < panelMain.Width; ix++)
{
cx = cxMin + (double )ix * pixelWidth;
// init this go
zx = 0.0;
zy = 0.0;
zx2 = 0.0;
zy2 = 0.0;
for (i = 0; i < iterationMax && ((zx2 + zy2) < er2); i++)
{
zy = zx * zy * 2.0 + cy;
zx = zx2 - zy2 + cx;
zx2 = zx * zx;
zy2 = zy * zy;
}
if (i == iterationMax)
{
// interior, part of set, black
// set colour to black
g.FillRectangle(sbBlack, ix, iy, 1, 1);
}
else
{
// outside, set colour proportional to time/distance it took to converge
// set colour not black
SolidBrush sbNeato = new SolidBrush(MapColor(i, zx2, zy2));
g.FillRectangle(sbNeato, ix, iy, 1, 1);
}
and MapColor below: (see this link to get the ColorFromHSV function)
private Color MapColor(int i, double r, double c)
{
double di=(double )i;
double zn;
double hue;
zn = Math.Sqrt(r + c);
hue = di + 1.0 - Math.Log(Math.Log(Math.Abs(zn))) / Math.Log(2.0); // 2 is escape radius
hue = 0.95 + 20.0 * hue; // adjust to make it prettier
// the hsv function expects values from 0 to 360
while (hue > 360.0)
hue -= 360.0;
while (hue < 0.0)
hue += 360.0;
return ColorFromHSV(hue, 0.8, 1.0);
}
MapColour is "smoothing" the bailout values from 0 to 1 which then can be used to map a colour without horrible banding. Playing with MapColour and/or the hsv function lets you alter what colours are used.
Seems simple to do by trial and error. Assume you can define HSV1 and HSV2 (hue, saturation, value) of the endpoint colors you wish to use (black and white; blue and yellow; dark red and light green; etc.), and assume you have an algorithm to assign a value P between 0.0 and 1.0 to each of your pixels. Then that pixel's color becomes
(H2 - H1) * P + H1 = HP
(S2 - S1) * P + S1 = SP
(V2 - V1) * P + V1 = VP
With that done, just observe the results and see how you like them. If the algorithm to assign P is continuous, then the gradient should be smooth as well.
My eventual solution was to create a nice looking (and fairly large) palette and store it as a constant array in the source, then interpolate between indexes in it using the smooth coloring algorithm. The palette wraps (and is designed to be continuous), but this doesn't appear to matter much.
What's going on with the color mapping in that image is that it's using a 'log transfer function' on the index (according to documentation). How exactly it's doing it I still haven't figured out yet. The program that produced it uses a palette of 400 colors, so index ranges [0,399), wrapping around if needed. I've managed to get pretty close to matching it's behavior. I use an index range of [0,1) and map it like so:
double value = Math.log(0.021 * (iteration + delta + 60)) + 0.72;
value = value - Math.floor(value);
It's kind of odd that I have to use these special constants in there to get my results to match, since I doubt they do any of that. But whatever works in the end, right?
here you can find a version with javascript
usage :
var rgbcol = [] ;
var rgbcol = MapColor ( Iteration , Zy2,Zx2 ) ;
point ( ctx , iX, iY ,rgbcol[0],rgbcol[1],rgbcol[2] );
function
/*
* The Mandelbrot Set, in HTML5 canvas and javascript.
* https://github.com/cslarsen/mandelbrot-js
*
* Copyright (C) 2012 Christian Stigen Larsen
*/
/*
* Convert hue-saturation-value/luminosity to RGB.
*
* Input ranges:
* H = [0, 360] (integer degrees)
* S = [0.0, 1.0] (float)
* V = [0.0, 1.0] (float)
*/
function hsv_to_rgb(h, s, v)
{
if ( v > 1.0 ) v = 1.0;
var hp = h/60.0;
var c = v * s;
var x = c*(1 - Math.abs((hp % 2) - 1));
var rgb = [0,0,0];
if ( 0<=hp && hp<1 ) rgb = [c, x, 0];
if ( 1<=hp && hp<2 ) rgb = [x, c, 0];
if ( 2<=hp && hp<3 ) rgb = [0, c, x];
if ( 3<=hp && hp<4 ) rgb = [0, x, c];
if ( 4<=hp && hp<5 ) rgb = [x, 0, c];
if ( 5<=hp && hp<6 ) rgb = [c, 0, x];
var m = v - c;
rgb[0] += m;
rgb[1] += m;
rgb[2] += m;
rgb[0] *= 255;
rgb[1] *= 255;
rgb[2] *= 255;
rgb[0] = parseInt ( rgb[0] );
rgb[1] = parseInt ( rgb[1] );
rgb[2] = parseInt ( rgb[2] );
return rgb;
}
// http://stackoverflow.com/questions/369438/smooth-spectrum-for-mandelbrot-set-rendering
// alex russel : http://stackoverflow.com/users/2146829/alex-russell
function MapColor(i,r,c)
{
var di= i;
var zn;
var hue;
zn = Math.sqrt(r + c);
hue = di + 1.0 - Math.log(Math.log(Math.abs(zn))) / Math.log(2.0); // 2 is escape radius
hue = 0.95 + 20.0 * hue; // adjust to make it prettier
// the hsv function expects values from 0 to 360
while (hue > 360.0)
hue -= 360.0;
while (hue < 0.0)
hue += 360.0;
return hsv_to_rgb(hue, 0.8, 1.0);
}