Open
Description
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;
}