doc-en icon indicating copy to clipboard operation
doc-en copied to clipboard

Document imagebmp() bit depth

Open mmellon opened this issue 1 year ago • 5 comments

Description

We have a business case where we need PHP to return a 1-bit depth BMP image to the client. This is because the client is outputting the image to a thermal receipt printer which can only handle 1-bit or 4-bit depth images. The imagebmp() function does not currently have a parameter to specify the color depth; it probably should. The documentation did not specify what the color depth of the generated image was by default.

Ideally, it would be nice to have a function with the following signature:

imagebmp(GdImage $image, resource|string|null $file = null, bool $compressed = true, int $colorDepth = 24): bool

We worked around this issue by writing a user-defined function:

// The PHP GD extension cannot output a 1-bit depth BMP with its built-in functions, so you have to DIY.
// See https://en.wikipedia.org/wiki/BMP_file_format
function output1BitBMP($gdImage) {
    $width = imagesx($gdImage);
    $height = imagesy($gdImage);

    // Create a 1-bit BMP header
    $fileHeaderSize = 14;
    $infoHeaderSize = 40;
    $paletteSize = 8; // 2 colors * 4 bytes per color
    $rowSize = ceil($width / 8);
    $rowSizePadded = ($rowSize + 3) & ~3; // Rows are padded to multiples of 4 bytes
    $imageSize = $rowSizePadded * $height;
    $fileSize = $fileHeaderSize + $infoHeaderSize + $paletteSize + $imageSize;

    // BMP file header
    $fileHeader = pack('vVvvV', 0x4D42, $fileSize, 0, 0, $fileHeaderSize + $infoHeaderSize + $paletteSize);

    // BMP info header
    $infoHeader = pack('V3v2V6', $infoHeaderSize, $width, $height, 1, 1, 0, $imageSize, 0, 0, 2, 0);

    // BMP color palette (black and white)
    $palette = pack('V2', 0x00000000, 0x00FFFFFF);

    // BMP pixel data
    $pixelData = '';
    for ($y = $height - 1; $y >= 0; $y--) {
        $row = '';
        for ($x = 0; $x < $width; $x += 8) {
            $byte = 0;
            for ($bit = 0; $bit < 8; $bit++) {
                if ($x + $bit < $width) {
                    $color = imagecolorat($gdImage, $x + $bit, $y);
                    $gray = (imagecolorsforindex($gdImage, $color)['red'] + imagecolorsforindex($gdImage, $color)['green'] + imagecolorsforindex($gdImage, $color)['blue']) / 3;
                    $byte |= ($gray < 128 ? 1 : 0) << (7 - $bit);
                }
            }
            $row .= chr($byte);
        }
        // Pad the row to a multiple of 4 bytes
        while (strlen($row) % 4 !== 0) {
            $row .= chr(0);
        }
        $pixelData .= $row;
    }

    // Output the BMP
    header('Content-Type: image/bmp');
    echo $fileHeader . $infoHeader . $palette . $pixelData;
}

mmellon avatar Dec 17 '24 15:12 mmellon

On behalf of my employer, as a corporate officer with contract binding authority, I release the code in my preceding comment into the public domain.

mmellon avatar Dec 17 '24 15:12 mmellon

Makes generally sense to me (and thanks for the code :) I'm not sure whether the color depth should even be specified by the user; what happens if there are more colors in the palette (or in truecolor images).

Anyhow, that would need first be addressed upstream, so please file an ticket there.

cmb69 avatar Dec 17 '24 18:12 cmb69

Forwarded to https://github.com/libgd/libgd/issues/911.

The imagebmp() function does not currently have a parameter to specify the color depth; it probably should. The documentation did not specify what the color depth of the generated image was by default.

libgd uses a simple solution: truecolor images are stored as 24bit, palette images are stored as 8bit BMPs. I agree that this should be documented.

cmb69 avatar Dec 23 '24 12:12 cmb69

I saw you opened the feature request upstream. Sorry, I've been out of the office for a trip. Is there anything else I can do to help with this?

mmellon avatar Jan 21 '25 16:01 mmellon

@mmellon, all good. I think I'll merge that PR in a while if there will be no further feedback.

cmb69 avatar Jan 21 '25 16:01 cmb69