A transformation seems to be applied when painting colors in p5.js with an alpha value lower than 255:
for (const color of [[1,2,3,255],[1,2,3,4],[10,11,12,13],[10,20,30,40],[50,100,200,40],[50,100,200,0],[50,100,200,1]]) {
clear();
background(color);
loadPixels();
print(pixels.slice(0, 4).join(','));
}
Input/Expected Output Actual Output (Firefox)
1,2,3,255 1,2,3,255 ✅
1,2,3,4 0,0,0,4
10,11,12,13 0,0,0,13
10,20,30,40 6,19,25,40
50,100,200,40 51,102,204,40
50,100,200,0 0,0,0,0
50,100,200,1 0,0,255,1
The alpha value is preserved, but the RGB information is lost, especially on low alpha values.
This makes visualizations impossible where, for example, 2D shapes are first drawn and then the visibility in certain areas is animated by changing the alpha values.
Can these transformations be turned off or are they predictable in any way?
Update: The behavior is not specific to p5.js:
const ctx = new OffscreenCanvas(1, 1).getContext('2d');
for (const [r,g,b,a] of [[1,2,3,255],[1,2,3,4],[10,11,12,13],[10,20,30,40],[50,100,200,40],[50,100,200,0],[50,100,200,1]]) {
ctx.clearRect(0, 0, 1, 1);
ctx.fillStyle = `rgba(${r},${g},${b},${a/255})`;
ctx.fillRect(0, 0, 1, 1);
console.log(ctx.getImageData(0, 0, 1, 1).data.join(','));
}
I could be way off here...but it looks like internally that in the background method if _isErasing is true then blendMode is called. By default this will apply a linear interpolation of colours.
See https://github.com/processing/p5.js/blob/9cd186349cdb55c5faf28befff9c0d4a390e02ed/src/core/p5.Renderer2D.js#L45
See https://p5js.org/reference/#/p5/blendMode
BLEND - linear interpolation of colours: C = A*factor + B. This is the
default blending mode.
So, if you set the blend mode to REPLACE I think it should work.
REPLACE - the pixels entirely replace the others and don't utilize
alpha (transparency) values.
i.e.
blendMode(REPLACE);
for (const color of [[1,2,3,255],[1,2,3,4],[10,11,12,13],[10,20,30,40],[50,100,200,40],[50,100,200,0],[50,100,200,1]]) {
clear();
background(color);
loadPixels();
print(pixels.slice(0, 4).join(','));
}
Internally, the HTML Canvas stores colors in a different way that cannot preserve RGB values when fully transparent. When writing and reading pixel data, conversions take place that are lossy due to the representation by 8-bit numbers.
Take for example this row from the test above:
Input/Expected Output Actual Output
10,20,30,40 6,19,25,40
IN (conventional alpha)
R
G
B
A
values
10
20
30
40 (= 15.6%)
Interpretation: When painting, add 15.6% of (10,20,30) to the 15.6% darkened (r,g,b) background.
Canvas-internal (premultiplied alpha)
R
G
B
A
R
G
B
A
calculation
10 * 0.156
20 * 0.156
30 * 0.156
40 (= 15.6%)
values
1.56
3.12
4.7
40
values (8-bit)
1
3
4
40
Interpretation: When painting, add (1,3,4) to the 15.6% darkened (r,g,b) background.
Premultiplied alpha allows faster painting and supports additive colors, that is, adding color values without darkening the background.
OUT (conventional alpha)
R
G
B
A
calculation
1 / 0.156
3 / 0.156
4 / 0.156
40
values
6.41
19.23
25.64
40
values (8-bit)
6
19
25
40
So the results are predictable, but due to the different internal representation, the transformation cannot be turned off.
The HTML specification explicitly mentions this in section 4.12.5.1.15 Pixel manipulation:
Due to the lossy nature of converting between color spaces and converting to and from premultiplied alpha color values, pixels that have just been set using putImageData(), and are not completely opaque, might be returned to an equivalent getImageData() as different values.
see also 4.12.5.7 Premultiplied alpha and the 2D rendering context
I am trying to make sense of the following presentation, see page 27:
Could someone please describe the command line tools available in libjxl that can help me work with existing palettes ?
I tried a naive:
% convert -size 512x512 -depth 8 xc:white PNG8:white8.png
% convert -size 512x512 -depth 8 xc:white PNG24:white24.png
which gives me the exected:
% file white8.png white24.png
white8.png: PNG image data, 512 x 512, 8-bit colormap, non-interlaced
white24.png: PNG image data, 512 x 512, 8-bit/color RGB, non-interlaced
But then:
% cjxl -d 0 white8.png white8.jxl
% cjxl -d 0 white24.png white24.jxl
Gives:
% md5sum white8.jxl white24.jxl
68c88befec21604eab33f5e691a2a667 white8.jxl
68c88befec21604eab33f5e691a2a667 white24.jxl
where
% jxlinfo white8.jxl
dimensions: 512x512
have_container: 0
uses_original_profile: 1
bits_per_sample: 8
have_preview: 0
have_animation: 0
intrinsic xsize: 512
intrinsic ysize: 512
orientation: 1 (Normal)
num_color_channels: 3
num_extra_channels: 0
color profile:
format: JPEG XL encoded color profile
color_space: 0 (RGB color)
white_point: 1 (D65)
primaries: 1 (sRGB)
transfer_function: gamma: 0.454550
rendering_intent: 0 (Perceptual)
frame:
still frame, unnamed
I also tried:
% cjxl -d 0 --palette=1024 white24.png palette.jxl
which also gives:
% md5sum palette.jxl
68c88befec21604eab33f5e691a2a667 palette.jxl
The libjxl encoder either takes a JPEG bitstream as input (for the special case of lossless JPEG recompression), or pixels. It does not make any difference if those pixels are given via a PPM file, a PNG8 file, a PNG24 file, an RGB memory buffer, or any other way, if the pixels are the same, the result will be the same.
In your example, you have an image that is just solid white, so it will be encoded the same way regardless of how you pass it to cjxl.
Now if those pixels happen to use only few colors, as will be the case for PNG8 since there can be at most 256 colors in that case, the encoder (at a default effort setting) will detect this and use the jxl Palette transform to represent the image more compactly. In jxl, palettes can have arbitrary sizes, there is no limit to 256 colors. The --palette option in cjxl can be used to set the maximum number of colors for which it will still use the Palette transform — if the input image has more colors than that, it will not use Palette.
The use of Palette is considered an internal encoding tool in jxl, not part of the externally exposed image metadata. It can be used by the encoder to effectively recompress PNG8 files, but by no means will it necessarily always use that encoding tool when the input is PNG8, and it might also use Palette when the input has more than 256 colors. The Palette transform of jxl is quite versatile, it can also be applied to individual channels, to more or less than 3 channels, and palette entries can be not only specific colors but also so-called "delta palette entries" which are not a color but signed pixel values that get added to the predicted pixel value.
As explained by Jon Sneyers just above the palette is an internal encoding tool. I was confused by this, as I could not see any difference in the output of the jxlinfo command line.
So I ran the following experience on my side to convince myself:
$ cjxl -d 0 --palette=257 palette.png palette.257.jxl
$ cjxl -d 0 --palette=256 palette.png palette.256.jxl
$ cjxl -d 0 --palette=255 palette.png palette.255.jxl
Lead to:
% md5sum palette.*.jxl
e925521cbb976dce2646354ea3deee3b palette.255.jxl
8d241b94d67aeb2706a1aad7aed55cc7 palette.256.jxl
8d241b94d67aeb2706a1aad7aed55cc7 palette.257.jxl
Where:
% du -sb palette.*.jxl
89616 palette.255.jxl
45627 palette.256.jxl
45627 palette.257.jxl
In all case jxlinfo reveals:
% jxlinfo palette.255.jxl
dimensions: 256x256
have_container: 0
uses_original_profile: 1
bits_per_sample: 8
have_preview: 0
have_animation: 0
intrinsic xsize: 256
intrinsic ysize: 256
orientation: 1 (Normal)
num_color_channels: 3
num_extra_channels: 0
color profile:
format: JPEG XL encoded color profile
color_space: 0 (RGB color)
white_point: 1 (D65)
primaries: 1 (sRGB)
transfer_function: 13 (sRGB)
rendering_intent: 0 (Perceptual)
frame:
still frame, unnamed
With:
% pnginfo palette.png
palette.png...
Image Width: 256 Image Length: 256
Bitdepth (Bits/Sample): 8
Channels (Samples/Pixel): 1
Pixel depth (Pixel Depth): 8
Colour Type (Photometric Interpretation): PALETTED COLOUR (0 colours, 0 transparent)
Image filter: Single row per byte filter
Interlacing: No interlacing
Compression Scheme: Deflate method 8, 32k window
Resolution: 0, 0 (unit unknown)
FillOrder: msb-to-lsb
Byte Order: Network (Big Endian)
Number of text strings: 0
I'm using Go and compiling it to web assembly.
I'm trying to render a bunch of rectangles next to eachother with a random colour, but they keep rendering as just gray.
My render function looks something like this:
for row,_ := range rows {
for col,_ := range row {
ctx.Set("fillStyle", fmt.Sprintf("#%06x", rand.Int()))
ctx.Call("fillRect", 20, 20 + (col * width), maxHeight - (row*height))
}
}
With which it renders a big block (all rectangles are next to eachother) but just all in gray, instead of doing them in different colours.
Is this enough code in the example to help further? If not I can post it to a gist, as I'm new to WASM I'm unsure which parts could really be relevant - but those 2 functions are the only ones doing something with rendering as far as I can tell.
The problem is that you use this expression to construct the fill style:
fmt.Sprintf("#%06x", rand.Int())
rand.Int() returns a non-negative pseudo-random int. Size of int is 64 bits if GOOS=js and GOARCH=wasm. What this means is that the random int number will be random 8 bytes (first bit being always 0 due to being non-negative).
If you format such a number with the %06x verb, like almost all the time it will be more than just 6 hex digits. The width 6 means to be at least 6, and the flag 0 means to pad with zeros if less. But if it's longer, it is not truncated.
And if you set an invalid color to canvas.fillStyle, it will disregard it and the last set valid fill style will remain active. And I'm guessing it was a gray color you used before the loop.
Fix is easy, just make sure the random number has no more than 3 bytes, or in other words, 6 hex digits. Use a simple bitmask:
ctx.Set("fillStyle", fmt.Sprintf("#%06x", rand.Int()&0xffffff))
Or use rand.Intn() instead of rand.Int():
ctx.Set("fillStyle", fmt.Sprintf("#%06x", rand.Int(0x1000000)))
Also context.fillRect() expects 4 arguments: x, y, width and height, so it should be something like this:
ctx.Call("fillRect", 20+(col*width), maxHeight-(row*height), width, height)
I have a question regarding a PNG file that I am trying to read (I have attached it in this question)
The file size 328750 bytes
Width 660
Height 330
Color type - truecolor
Bit depth - 24 bits
So here's my question. If it's true color, I assume it's RGB, which is 24 bits. But you do the math, the number doesn't add up. 660 (width) * 330 (height) * 3 bytes (from 24 bits) = 653400 bytes, which is double the actual file size.
Why is that?
I tried to read the IDAT chunk, pretending that each pixel is 3 bytes, and I tried to check the colour and it doesn't match what is displayed.
PNG is a compressed image format, so the IDAT chunk(s) contain a zlib-compressed representation of the RGB pixels. Probably the easiest way for you to access the pixel data is to use a converter such as ImageMagick or GraphicsMagick to decompress the image into the Netpbm "PPM" format.
magick image.png image.ppm
or
gm convert image.png image.ppm
Then read the "image.ppm" in the same way you tried to read the PNG. Just skip over the short header, which in the case of your image is
P 6 \n 6 6 0 3 3 0 \n 2 5 5 \n
where "P6" is the magic number, 660 and 330 are the dimensions, and 255 is the image depth (maximum value for R,G,and B is 255, or 0xff). The remainder of the file is just the R,G,B values you were expecting.
A quesion about RGB color and finding the simplest, tiniest, php conversion code for manipulating the lightness/darkness of a given RGB hue.
Imagine a variable $colorA containning a valid six char RGB color, like F7A100 which we want to make a bit lighter and/or darker:
$color = B1B100; // original RGB color manually set.
Then, at any page have that color bit darker/lighter on the fly:
$colorX = someFunction($color, +10); // original color 10 steps lighter
$colorY = someFunction($color, -25); // original color 25 steps darker
What would be YOUR way of solving this? Keep the RGB as is or first change it to HSL? Hints and suggestions are welcome. Your sample/code is welcome too.
This really focuses to the TINIES / SIMPLES / SHORTEST possible code to just make the same hue bit darker/lighter.
I deliberately do not suggest my code, as I want to keep possibilities open in here.
The absolutely simplest solution is to add some constant (like 1) to each part of the color representation: [R, G, B]. This is due to the fact that max values of all [R, G, B] represent white, while min values - black. In pseudo-code (assuming 255 is max, sorry, I don't know PHP):
lighter(R, G, B) = [
min(255, R + 1),
min(255, G + 1),
min(255, B + 1)
]
You must keep in mind though that this transformation is way too simplistic and the proper implementation would be to convert to HSL/HSB, increase H and transform back to RGB.
For slight alteration of brightness you can convert the hexadecimal values to decimal, manipulate them and convert back to hexadecimal like this:
function alterBrightness($color, $amount) {
$rgb = hexdec($color); // convert color to decimal value
//extract color values:
$red = $rgb >> 16;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
//manipulate and convert back to hexadecimal
return dechex(($red + $amount) << 16 | ($green + $amount) << 8 | ($blue + $amount));
}
echo alterColor('eeeeee', -10); //outputs e4e4e4
Beware that this code does not handle overflow for one color - if one color value becomes less than 0 or more than 255 you will get an invalid color value. This should be easy enough to add.
For drastic changes in brightness, convert to HSL and manipulate the lightness.
Using the functions from the Drupal code, this can be done like this:
$hsl = _color_rgb2hsl(_color_unpack('eeeeee'));
$hsl[2] -= 10;
$rgb = _color_pack(_color_hsl2rgb($hsl));
echo $rgb; //outputs e4e4e4