# Reading png rgb channels, Julia vs Python

So here is a very simple `8x8` `png` image:

Opening it in `GIMP`:

So this pixel has `RGBA(233, 173, 4, 244)` components.

Using `Python`:

``````import imageio
r = img[:, :, 0]
g = img[:, :, 1]
b = img[:, :, 2]
a = img[:, :, 3]

print((r[2, 2], g[2, 2], b[2, 2], a[2, 2]))
``````

gives `(233, 173, 4, 244)`, so far so good.

Now using `Julia`:

``````using ColorTypes
using ImageMagick
using PNGFiles

for mod ∈ (ImageMagick, PNGFiles)
img = open("img.png") do io
kw = mod == PNGFiles ? (;expand_paletted=true) : ()
end

int255(c) = round.(Int, 255c)
r, g, b, a = int255(red.(img)), int255(green.(img)), int255(blue.(img)), int255(alpha.(img))

println((r[3, 3], g[3, 3], b[3, 3], a[3, 3]))
end
``````

gives

``````(245, 215, 34, 244)
(245, 214, 39, 244)
``````

Only the alpha channel is identical. `G` and `B` channels have different values using `PNGFiles` or `ImageMagick`. And the `RGB` values are far from what is displayed in `GIMP` or by using `Python`.

What am I missing here ?

is this a linear vs srgb issue?

It looks like Python is returning raw srgb values from the file but Julia is converting them to linear values.
You want linear values if you’re doing any math on them. For instance, averaging or adding two values together in srgb space will return a wrong result.

1 Like

that said, the conversion does lose considerable precision unless Julia is using more than 8 bits.

1 Like

Wait, isn’t it the other way around? (Disclaimer: I am very much not an expert)

From the excellent Color Spaces – Bartosz Ciechanowski, we see that
`(intensity value) = (((encoded value) + 0.055)/1.055)2.4`

So if the encoded value is `245/255` then the actual intensity is indeed about `232/255`. So seems like Julia’s color and image packages are giving the encoded values, and Python’s is giving the actual linear intensity?

(I think one way to double check this: do `using Colors; Gray(.5)` in an editor that displays color output, and you’ll see what looks to the human eye like mid-gray—meaning the argument to `Gray` must be the encoded value, not linear value, because the gray that humans see as halfway between dark and light actually has ~18% intensity.)

2 Likes

Thanks for pointing towards srgb vs linear scale:

Using

``````  # ...
linear(srgb) = begin
# en.wikipedia.org/wiki/SRGB#Transformation
lin = similar(srgb)
for I in eachindex(srgb)
lin[I] = if (v = srgb[I])  > .04045
((v + .055) / 1.055)^2.4
else
v / 12.92
end
end
lin
end
lin255(x) = round.(Int, 255linear(x))
raw255(x) = round.(Int, 255x)
r, g, b, a = lin255(red.(img)), lin255(green.(img)), lin255(blue.(img)), raw255(alpha.(img))
# ...
``````

now gives the same `R`, `G`, `B`, `A` matrices as `Python` when using `ImageMagick` but different results for `PNGFiles` (rounding issues ?) so I’m more confident trusting the data output from `ImageMagick`, since it matches the implement of `imagio` (underlying `pillow` library).

Hi, I’m one of the authors of PNGFiles.jl. Could you try importing the image using `gamma=1.0` and share the image in question?

The image in small but in the original post (right click, save as) or here is the direct url: https://aws1.discourse-cdn.com/business5/uploads/julialang/original/3X/5/c/5c5cdaa477dcae516c7183c3e710ef65a0bb6ab0.png.

With `gamma=nothing`: `(233, 171, 5, 244)`
With `gamma=1.` or `gamma=.45455`: `(208, 107, 0, 244)`

Furthermore:

``````\$ convert img.png txt:- | grep '2,2:'
\$ julia -E '44461 / 65535 * 255'
173.0
``````

@drvi, I have assembled a simple example at GitHub - t-bltg/PngPixel.jl.

Using the `libpng_api` from `PNGFiles`, I can get the correct RGB values.

However I still don’t understand why using `PNGFiles.load(...)` gives the wrong values.

@t-bltg Thanks for investigating! Can you try the following branch of PNGFiles?

``````] add PNGFiles#paletted_images_fix

julia> using PNGFiles

julia> map(x->(Int(x.r.i), Int(x.g.i), Int(x.b.i), Int(x.alpha.i)), PNGFiles.load("test.png"))[3,3]
(233, 173, 4, 244)
``````

Odd, I get different results:

``````(@v1.7) pkg> add PNGFiles#paletted_images_fix
Updating git-repo `https://github.com/JuliaIO/PNGFiles.jl.git`
Updating registry at `~/.julia/registries/General.toml`
Resolving package versions...
Updating `~/.julia/environments/v1.7/Project.toml`
[f57f5aa1] ~ PNGFiles v0.3.12 ⇒ v0.3.12 `https://github.com/JuliaIO/PNGFiles.jl.git#paletted_images_fix`
Updating `~/.julia/environments/v1.7/Manifest.toml`
[f57f5aa1] ~ PNGFiles v0.3.12 ⇒ v0.3.12 `https://github.com/JuliaIO/PNGFiles.jl.git#paletted_images_fix`
Precompiling project...
1 dependency successfully precompiled in 4 seconds (44 already precompiled)

julia> using PNGFiles

julia> map(x->(Int(x.r.i), Int(x.g.i), Int(x.b.i), Int(x.alpha.i)), PNGFiles.load("img.png"))[3,3]
(245, 214, 39, 244)
``````

Hmm, would you mind opening an issue on the github repo? You can just link to this thread and preferably include the outputs of the following:

Just to be sure we’re talking about the same exact picture:

``````julia> (x->(Int(x.r.i), Int(x.g.i), Int(x.b.i), Int(x.alpha.i)))(PNGFiles.load(Downloads.download("https://aws1.discourse-cdn.com/business5/uploads/julialang/original/3X/5/c/5c5cdaa477dcae516c7183c3e710ef65a0bb6ab0.png"))[3,3])
# expected: (233, 173, 4, 244)
``````

and package versions

``````] st
``````

and system info

``````julia> versioninfo()
``````

Thanks!

1 Like

Doh I always forget which is approximately ^1/2.2 and which is approximately ^2.2.

Which is dumb. All I have to remember is that SRGB values are higher than linear at the low end and vice versa at the high end.

1 Like