how much is 1% fuzz in imagemagick? - math

I'm working on a script and Imagemagick, to search for specific colors on images.
I'm thinking about using fuzz in the search, but can't find any information about how much for example 1%fuzz is?
if I'm running:
convert "images.png" -fill black +opaque #440000 -fuzz 1% \( +clone -evaluate set 0 \) -metric AE -compare -format "%[distortion]" info:>/tmp/rip.hex.data
.. and searching for color #440000, and add 1% fuzz on that, then, what's the 1% of?
1% of 256?
1% of the image's total colors?
1% of...?

In ImageMagick, fuzz is computed as the root mean squared difference between two colors.
fuzz = rmse = sqrt( (rdiff^2 + gdiff^2 + bdiff^2)/3 )
where r,g,b are in quantum range for your compile -- typically Q16 or 16-bits, so 0 to 65535.
So fuzz in percent is 100*fuzz/65535
So 1% fuzz would be .01*65535 = 655.35 gray levels out of 65535 as measured by the rmse formula.

It's 1% of the quantum range. So if you installed ImageMagick Q16, it would be 216 * 0.01. Or about 65536 * 0.01 = 655.36.

Related

Scaling a color based on a target contrast ratio

I am trying to create a Sass function that receives a foreground color and background color and calculates the contrast ratio. From there (and the part I'm stuck on) is that it would simply return the foreground color if it meets the target contrast ratio, but if it doesn't it would lighten or darken the foreground color to meet the target contrast ratio.
For example, if the background supplied was #000 and the foreground supplied was #444 (a contrast ratio of 2.15), this function would lighten the foreground to #757575 and return that color.
I've got everything working except for the part where I need to reverse the contrast calculation. My initial thought was to approach it with what percentage it was away from target and simply lighten/darken (depending on which color was originally darker) by 100 minus the percent difference. This approach, in hindsight, was a little naive and I'm afraid some more advanced math will be involved.
Here is what I created so far (and here is a simplified fiddle):
#function wcag-color($bg, $fg, $size: 16px, $level: "aa"){
#if ( $level == "aa" ){
$wcag_contrast_ratio: 4.5; //For text smaller than 18px
#if ( $size >= 19 ){
$wcag_contrast_ratio: 3; //For text larger than 19px
}
}
#if ( $level == "aaa" ){
$wcag_contrast_ratio: 7; //For text smaller than 18px
#if ( $size >= 19 ){
$wcag_contrast_ratio: 4.5; //For text larger than 19px
}
}
$actual_contrast_ratio: contrast($bg, $fg); //This function returns the contrast between the two colors.
#if ( $actual_contrast_ratio > $wcag_contrast_ratio ){
#return $fg; //Foreground color is acceptable
}
//Scale the lightness of the foreground to meet requested WCAG contrast ratio
$difference: 100 - $actual_contrast_ratio / $wcag_contrast_ratio * 100; //There is more to it than this...
//Edit: here are a few new lines to ponder. This assumes BG is darker than FG (would need to add a condition to compare luminance of each).
$acceptable_luminance: luminance($bg)*$wcag_contrast_ratio; //What the luminance of the FG must be to comply
$difference: ($acceptable_luminance - luminance($fg)); //How far away the FG luminance actually is (not sure if this helps anything...)
#return scale-color($fg, $lightness: $difference); //Unfortunately luminance is not the same as lightness.
}
Notice the commented line "There is more to it than this..." – that is where I need to reverse my contrast formula, but I'd love if there was a simpler formula to use since I already know what the target contrast ratio is.
I've been thinking about this for a few days an I'm stumped. I'd prefer to avoid a guess-and-check method by looping through 1% lightened/darkened colors and testing each individually for their contrast ratio– that would work, but I'm sure there is a more optimal solution.
This was my reference for my initial functions (contrast and luminance) and was very helpful: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
Note: I am not using Compass or any other Sass libraries.
Edit: Here is a simplified fiddle for reference: https://www.sassmeister.com/gist/445836123feb42885a0cf7f4709261ff
So given #000000 and #444444, you can calculate the contrast ratio (2.15 in this case). The math is pretty straightforward, albeit a little hairy. (See the "relative luminance" definition.)
Now you want to go backwards? If you have #000000 and want a ratio of 4.5, starting with #444444, what should the color be? Is that what
I need to reverse my contrast formula
means?
It's a little complicated because you're solving for 3 variables, the red, green, and blue components, plus the luminance formula doesn't treat the red, green and blue equally. It's using 21.25% red, 71.5% green, and 7.25% blue.
Plus, the luminance formula isn't a simple linear formula so you can't just take a percentage short of luminance and bump the color value by that same percentage.
For example, in your case, the ratio was 2.15 but you need it to be 4.5. 2.15 is 108% short of 4.5, the desired value.
However, if you look at your original RGB values #444444 and you calculated it needed to be #757575 (in order to have a 4.5 ratio), then if you treat those RGB values as simple numbers (and convert to decimal), then #444444 (4473924) is 72% short of #757575 (7697781).
So you have a disconnect that your ratio is short by 108% but your RGB values are short by 72%. Thus you can't do a simple linear equation.
(The numbers aren't quite exact since #757575 gives you a 4.56 ratio, not an exact 4.5 ratio. If you use #747474, you get a 4.49 ratio, which is just a smidge too small for WCAG compliance but is closer to 4.5 than 4.56. However, #444444 is 71% short of #747474, so it's still not the same as 2.15 being 108% short of 4.5, so the basic concept still applies.)
Just for fun, I looked at the values of 0x11111 through 0x666666, incrementing by 0x111111, and calculated the contrast ratio. There weren't enough points on the graph so I added a color halfway between 0x111111 and 0x222222, then halfway between 0x222222 and 0x333333, etc.
RGB contrast % from 4.5 % from 0x747474
111111 1.11 305.41% 582.35%
191919 1.19 278.15% 364.00%
222222 1.32 240.91% 241.18%
2a2a2a 1.46 208.22% 176.19%
333333 1.66 171.08% 127.45%
3b3b3b 1.87 140.64% 96.61%
444444 2.16 108.33% 70.59%
4c4c4c 2.45 83.67% 52.63%
555555 2.82 59.57% 36.47%
5d5d5d 3.19 41.07% 24.73%
666666 3.66 22.95% 13.73%
6e6e6e 4.12 9.22% 5.45%
As you can see, the lines interset at the 3rd data point then converge toward each other. I'm sure there's a formula in there so you could take the contrast percentage, do some (probably logarithmic) function on it and get the percentage needed to change the color.
This would be a fascinating math problem that I currently don't have time to play with.
Update Jan 18, 2019
I got it to work going backwards, but it doesn't handle edge cases such as when making a dark color darker but you've already reached the max (or a light color lighter but you reached the max). But maybe you can play with it.
Test case
#ee0add for the light color (magenta-ish)
#445566 for the dark color (dark gray)
contrast ratio 2.09
When computing the "relative luminance" of a color, it has a conditional statement.
if X <= 0.03928 then
X = X/12.92
else
X = ((X+0.055)/1.055) ^ 2.4
Before X is used in that condition, it's divided by 255 to normalize the value between 0 and 1. So if you take the conditional value, 0.03928, and multiply by 255, you get 10.0164. Since RGB values must be integers, that means an RGB component of 10 (0x0A) or less will go through the "if" and anything 11 (0x0B) or bigger will go through the "else". So in my test case values, I wanted one of the color parts to be 10 (0x0A) (#EE0ADD).
The relative luminance for #ee0add is 0.23614683378171950172526363525113 (0.236)
The relative luminance for #445566 is 0.0868525191131797135799815832377 (0.0868)
The "contrast ratio" is
(0.236 + .05) / (0.0868 + .05) = 2.09
(You can verify this ratio on https://webaim.org/resources/contrastchecker/?fcolor=EE0ADD&bcolor=445566)
If we want a ratio of 4.5, and we want #ee0add to not change, then we have to adjust #445566. That means you need to solve for:
4.5 = (0.236 + .05) / (XX + .05)
So the second luminance value (XX) needs to be 0.01358818528482655593894747450025 (0.0136)
The original second luminance value was 0.0868525191131797135799815832377 (0.0868), so to get 0.01358818528482655593894747450025 (0.0136), we need to multiply the original by 0.15645125119651910313960717062698 (0.0136 / 0.0868 = 0.156) (or 15.6% of the original value)
If we apply that 15.6% to each of the R, G, and B relative luminance values, and then work through the conditional statement above backwards, you can get the RGB values.
Original luminance for #445566
r = 0x44 = 68
g = 0x55 = 85
b = 0x66 = 102
r1 = 68 / 255 = 0.26666666666666666666666666666667
g1 = 85 / 255 = 0.33333333333333333333333333333333
b1 = 102 / 255 = 0.4
r2 = ((.267 + .055) / 1.055)^^2.4 = 0.05780543019106721120703816752337
g2 = ((.333 + .055) / 1.055)^^2.4 = 0.09084171118340767766490119106965
b2 = ((.400 + .055) / 1.055)^^2.4 = 0.13286832155381791428570549818868
l = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2
= 0.0868525191131797135799815832377
Working backwards, take 15.6% of the r2, g2, and b2 values
r2 = 0.05780543019106721120703816752337 * 15.6% = 0.00904373187934550551617004082875
g2 = 0.09084171118340767766490119106965 * 15.6% = 0.01421229937547695322310904970549
b2 = 0.13286832155381791428570549818868 * 15.6% = 0.02078741515147623990363062978804
Now undo that mess with ^^2.4 and the other stuff
to undo X^^2.4 you have to do the inverse, X^^(1/2.4) or X^^(0.4167)
then multiply by 1.055
then subtract 0.055
then multiply by 255
pow( 0.00904373187934550551617004082875, 1/2.4) = 0.14075965680504652191078668676178
pow( 0.01421229937547695322310904970549, 1/2.4) = 0.16993264267137740728089791717873
pow( 0.02078741515147623990363062978804, 1/2.4) = 0.19910562853770829265100914759565
multiply by 1.055
0.14075965680504652191078668676178 * 1.055 = 0.14850143792932408061587995453368
0.16993264267137740728089791717873 * 1.055 = 0.17927893801830316468134730262356
0.19910562853770829265100914759565 * 1.055 = 0.21005643810728224874681465071341
subtract 0.055
0.14850143792932408061587995453368 - 0.055 = 0.09350143792932408061587995453368
0.17927893801830316468134730262356 - 0.055 = 0.12427893801830316468134730262356
0.21005643810728224874681465071341 - 0.055 = 0.15505643810728224874681465071341
multiply by 255
0.09350143792932408061587995453368 * 255 = 23.842866671977640557049388406088 = 24 = 0x18
0.12427893801830316468134730262356 * 255 = 31.691129194667306993743562169008 = 32 = 0x20
0.15505643810728224874681465071341 * 255 = 39.53939171735697343043773593192 = 40 = 0x28
So the darker color is #182028. There are probably some rounding errors but if you check the original foreground color, #ee0add with the new color, #182028, you get a contrast ratio of 4.48. Just shy of 4.5, but like I said, probably some rounding errors.
https://webaim.org/resources/contrastchecker/?fcolor=EE0ADD&bcolor=182028
I tried doing the same thing with #ee0add, keeping #445566 the same, but when going backwards and getting to the last step where you multiply by 255, I got numbers greater than 255, which are not valid RGB components (they can only go up to 0xFF). If I stopped the number at 255 then took the difference and added it to the smallest color value, I got a decent color but the ratio was 5.04, overshooting 4.5. I can post that math too if you want.

Calculating how "colorful" a color is

I like to create a ratio between 0-1 of how "colorful" a color is, by colorful i mean:
Black colors have ratio 0 (no light)
White colors have ratio 0 (no saturation)
A fully saturated / lighted color has ratio 1
i tried by converting the color to HSV colorspace and calculating the ratio as:
ratio = (color.value / 100) * (color.saturation / 100)
This works somewhat, but it feels like the curve is wrong, for example a
HSV(hue=0, saturation=80, value=80)
only gives a ratio of 0.64 but looks very close to the fully saturated / lighted color.
Maybe i need to somehow create a "ease-out" on the values? i thought about taking human perception of colors into account aswell (using LAB or YUV colorspaces) but i dont think that is needed here, but i might be wrong.
The definition of saturation from HSV is not what you want, because it does not compensate for the lightness of the color.
From the Wikipedia article on Colorfulness:
[Perceived saturation] is the proportion of pure chromatic color in the total color sensation.
where Sab is the saturation, L* the lightness and C*ab is the chroma of the color.
This definition uses the L* and C*ab components of the CIELAB colorspace. My guess is that for your application you could use Lab colorspace instead. Then your code would look like this:
function perceived_saturation(L, a, b)
return 100 * sqrt(a*a + b*b) / sqrt(L*L + a*a + b*b)
For your example color RGB = (204, 41, 41), this returns a perceived saturation of about 86%, when converting the color via the path RGB &rightarrow; sRGB &rightarrow; Lab.
As a simple approximation, you could use max(r,g,b)/255 - min(r,g,b)/255. It has the properties you seek, where anything on the gray spectrum between black and white has a colorfulness of 0, and only fully lit colors will have a colorfulness of 1. A fully saturated but dark color will be in between, e.g. (128, 0, 0) will have a colorfulness of ~0.5.

How to create a histogram with varying bin widths

I have been unsuccessful with other using hist plot.
A simple problem would be using the following data:
age range - frequency - central band width - bin width - height (respectively)
1-4 - 30 - 2.5 - 3 - 10
5-6 - 20 - 5.5 - 1 - 20
7-17 - 30 - 12 - 10 - 3
With age along the X axis, with a linear scale, so the bin width for 1-4 would be 3, with height 10, bin width for 5-6 would be 1 with height of 20, and 7-17 would be 10 and the height would be 3.
How would can I place these data into a Word/notepad document .dat file?
And how can I then use them to set up a histogram in gnuplot?
I would use the following data file format (use only white spaces to delimit fields):
"age range" "frequency" "central band width" "bin width" "height"
1-4 30 2.5 3 10
5-6 20 5.5 1 20
7-17 30 12 10 3
To plot with variable boxwidth, use the boxes plotting style. That allows you to use the value from a column as width.
With xtic(1) you use the entry in the first column as xticlabel.
So a rather simple plotting script looks as follows:
set style fill solid noborder
set yrange [0:*]
set offset 1,1,1,0
plot 'file.txt' using 3:5:4:xtic(1) with boxes notitle
The result with version 4.6.3 and the pngcairo terminal is:
I managed a fairly nice example of variable width boxes last night. I was plotting latency histogram data produced by the FIO storage performance test package. With my compile options I have 1856 bins, that go as follows:
1 ns wide from 0-128 ns (128 bins)
2 ns wide from 128-256 ns (64 bins)
4 ns wide from 256-512 ns (64 bins)
8 ns wide from 512-1024 ns (64 bins)
etc...
My latency values at plot time are in microseconds (FIO provides nanoseconds, but I wanted microseconds for historical reasons). I did not have the opportunity to include the bin widths in my data. So I did this:
f(x) = (2**(int(log(x*1000)/log(2))-6))/1100
plot "temp" u 1:2:(f(column(1))) with boxes fs transparent solid 0.7 noborder title "$legend"$base_plot
The f(x) definition returns the box width for a given latency - it works as follows:
First, x*1000 gets me back to nanoseconds.
log(x*1000)/log(2) takes the base 2 logarithm of the nanosecond count.
The int() just gives me the integer part of that. Note that now for, say, 128 ns, I'd have 7.
The -6 gets me to the base 2 log of the bin width.
The 2 ** gets me to the bin width.
The /1000 returns me from nanoseconds to microseconds.
Then I just use f(latency) in the plot command as the box width.
This works - it seems to work perfectly as far as I can tell. It would not give the right result for x < 64 ns, but I don't have any data that small, so it works out. A conditional expression could be used to patch it up for that part of the range.
I think the key observations here are that a) you don't have to have the width as literal data - if you can calculate it from the data you do have, you're golden, and b) column(n) is an alternative to $n as a way of expressing column values in the plot command. In my case I have all this in a bash script, and bash intercepted the $1.

Scaling a window while keeping ratios the same

I have a resizable window, and a graph which consists of 11 lines of different values ranging from 0 to 1000. What is the math I would use to compute this?
I want to have the data ranging from 0 to 1000 be so that it equals 0-1000 pixels on screen. But if I resize my window to say 640 / 480, the graph will adjust only will be less detailed.
This is a simple proportion: if 640 pixels bar represents value of 1000, value of Y will represent 640 * Y / 1000 pixels bar.
You did not specify a programming language.
In HTML you can size anything as %, so you calculate size as % of maximum - i.e. 550 (out of 1000 max) = 55%
If you use % size it will automatically adjust with screen/window size.
If you specify your programming language of choice we may be able to help you more

rgb values to 0 to 1 scale

I'm trying to calculate some RGB colors (0 - 255) for a 0 to 1 scale. Does anyone knows a online converter or it exists a math formula?
Lets say that I want to convert 125 RGB with (0 to 255 scale) to a 0 to 1 scale.
It's simply a case of dividing your RGB value, call it x by 255:
If x = 95 then your value is 95/255 = 0.373 (to 3 d.p.)
a = x / 255
or
x = a * 255
where x is the RGB value and a is your desired result.
if you want to continue using 0-255 can create a function for this as the following example.
function setFillColor2(...)
local object,r,b,g=...
object:setFillColor( r/255, b/255,g/255)
end
circle1 = display.newCircle(150,250, 50 )
setFillColor2(circle1,23,255,12)
The simplest way if to divide by either 255 for 0-1 or 256 for 0 - a bit less than 1. The latter sometimes has advantages. You need to do the divisions in fixed or floating point, not in integer arithmetic, of course.
However in fact the human response to an rgb channel value on 0-255 is not linear, neither is the device's response. Often you need to do gamma correction. It all gets very involved very quickly, and it doesn't usually matter much for low end graphics. For high end graphics, however, you often want to be out of rgb colourspace altogether, and then you need non-linear conversion functions to finally flush the rgb pixels to the end image.
use like this
color: rgb(140 / 255, 180 / 255, 205 / 255),
divide 255 to each actual value , its always falls between 0-1 scale
Actual RGB Color : 140 180 205
0-1 scale code - rgb(140 / 255, 180 / 255, 205 / 255)

Resources