Document imagebmp() bit depth
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;
}
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.
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.
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.
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, all good. I think I'll merge that PR in a while if there will be no further feedback.