veluca93 / fpnge

Demo of a fast PNG encoder.
Apache License 2.0
88 stars 8 forks source link

Appears to not handle 16-bit RGB images #14

Closed nznobody closed 2 years ago

nznobody commented 2 years ago

Output image is 8bit from 16bit image input according to file and ImageMagick compare.

Steps to reproduce:

git clone <this repo>
./build.sh
# Check inputs
file 8b.png 
# 8b.png: PNG image data, 1920 x 1200, 8-bit/color RGB, non-interlaced
file 12b.png
# 12b.png: PNG image data, 1920 x 1200, 16-bit/color RGB, non-interlaced

# Convert images
./build/fpnge 8b.png 8b-out.png 100
./build/fpnge 12b.png 12b-out.png 100

# Compare:
compare -metric AE 8b.png 8b-out.png compare.png  # gives 0
compare -metric AE 12b.png 12b-out.png compare.png  # Gives a very very large number (2.304e+06)

file 12b-out.png 
# 12b-out.png: PNG image data, 1920 x 1200, 8-bit/color RGB, non-interlaced

Note: the 12b-out.png claims to be 8-bit/color. Interestingly opencv reads this still as 16-bit, but wrongly.

Extra Info:

CPU: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz OS: Debian GNU/Linux 11 (bullseye) (in docker) Compiler: gcc (Debian 10.2.1-6) 10.2.1 20210110 g++ (Debian 10.2.1-6) 10.2.1 20210110

Extra extra

I stumbled on this via the python binding here. Below is an example out showing the errors:

blank_image = np.random.randint(0, high=np.iinfo(np.uint16).max, size=(1,2,3), dtype=np.uint16)
image_bytes = fpnge.binding.encode_view(blank_image.data, blank_image.shape[1], blank_image.shape[0], blank_image.shape[2], blank_image.dtype.itemsize * 8)
image_check: cv2.Mat = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH)
assert image_check.dtype == blank_image.dtype
assert image_check.shape == blank_image.shape
assert (image_check == blank_image).all()
# If you print these:
pprint.pprint(image_check)
array([[[51929, 35260,  2880],
        [ 6872, 23392, 15276]]], dtype=uint16)
pprint.pprint(blank_image)
array([[[16395, 48265, 55754],
        [44091, 24667, 55322]]], dtype=uint16)

Images: 8b 12b

veluca93 commented 2 years ago

This seems to be a problem of fpnge_main, which the python bindings don't use. Nonetheless, fixed :)

nznobody commented 2 years ago

Thanks for fixing up fpnge_main. I am not convinced that this is however the issue. Can you take a quick look at this and determine whey the test test/test_fpnge.py::test_uint16_numpy_random is failing?

Specifically if I put this into the PNG encoder (a height=1, width=2, channels=3 array):

[[[  903, 58312,  4628],
[17640, 58450, 32469]]]

I get this out:

[[[ 5138, 51427, 34563],
[54654, 21220, 59460]]]

Note: It works fine with all zeros and all max_uint16.

veluca93 commented 2 years ago

@animetosho could you check that? I'd hazard it's an issue with the python bindings, but I am not sure -- possibly endianness mismatch?

animetosho commented 2 years ago

possibly endianness mismatch?

Without checking and from @nznobody's output, it looks exactly like that.

Converted to hex:

Input
0387 E3C8 1214
44E8 E452 7ED5

Output
1412 C8E3 8703
D57E 52E4 E844

The endianess is flipped, and the channels are as well.
Never had looked into 16-bit PNGs, but since it uses big-endian everywhere, I wouldn't be surprised if this flows onto each sample.