php / php-src

The PHP Interpreter
https://www.php.net
Other
38.05k stars 7.73k forks source link

Bug on colors when loading bitmap file #11442

Open hoit opened 1 year ago

hoit commented 1 year ago

Description

Hello,

On some bitmap files, i saw that the color of pixels got with imagecreatefrombmp/imagecolorat/imagecolorsforindex are not the correct one.

To reproduce the issue, just load the image in attachment :

$bmp = imagecreatefrombmp($bitmapFilePath);

then read the color of the pixel in 0,0 :

$rgb = imagecolorat($bmp, 0, 0);
$px = imagecolorsforindex($bmp, $rgb);

You will have the color 0, 248, 0(green) but the color should be 255,0,255 (magenta).

The bitmap file is correctly displayed on my OS (ubuntu) and in gimp software.

bg1.zip

Here is my php version :

PHP 8.1.2-1ubuntu2.11 (cli) (built: Feb 22 2023 22:56:18) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.2-1ubuntu2.11, Copyright (c), by Zend Technologies

PHP Version

8.1.2

Operating System

Ubuntu 22.04.2 LTS

devnexen commented 1 year ago

I tried your use case calling directly the gd's C api and get the same data. @kocsismate any idea ?

kocsismate commented 1 year ago

Sorry, I have no idea :/ I never really used gd.

nielsdos commented 1 year ago

This is because the palette is read at the wrong offset. The DIB header is 124 bytes, but the reading starts at byte 54. This is because V4 & V5 headers are treated as V3 headers, so therefore not all bytes are consumed and thus the offset is wrong.

This patch fixes it:

diff --git a/ext/gd/libgd/gd_bmp.c b/ext/gd/libgd/gd_bmp.c
index 00903d5ff8..b7cce4a04e 100644
--- a/ext/gd/libgd/gd_bmp.c
+++ b/ext/gd/libgd/gd_bmp.c
@@ -551,6 +551,11 @@ static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info)
        if (bmp_read_windows_v3_info(infile, info)) {
            return 1;
        }
+       if (info->len == BMP_WINDOWS_V4 || info->len == BMP_WINDOWS_V5) {
+           /* Because we only handle V3, we must skip to the end of the header we would've read if we handled V4 & V5. */
+           /* TODO: also skip the extra bit masks if the right condition is fulfilled... */
+           gdSeek(infile, 14 + info->len /* can't overflow */);
+       }
        break;
    case BMP_OS2_V1:
        if (bmp_read_os2_v1_info(infile, info)) {

EDIT: to clarify, the 14 is the fixed size of the BMP header. EDIT: and I guess we need to error-check the return value of gdSeek too :p but this serves as a PoC

nielsdos commented 1 year ago

Idk what to do here though. I don't really want to spend time upstreaming it to libgd because it's pretty silent over there. Last time I fixed a libgd issue I spend quite some time setting up a testcase and test environment and that PR is still open :shrug:

devnexen commented 1 year ago

Yes I know the feeling 🙂, I had more than once pending PR for 1 or 2 years before they got merged.