Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create 1 bit bitmap (monochrome) in php

Tags:

php

bitmap

bit

I'm looking for the possibility of write a 1 bit bitmap from a string with this content:

$str = "001011000111110000";

Zero is white and One is black. The BMP file will be 18 x 1 px.

I don't want a 24bit BMP, but a real 1bit BMP.

Does anyone know the header and the conversion method in PHP?

like image 233
htmlandrea Avatar asked Jun 23 '12 07:06

htmlandrea


3 Answers

For creating a monochromatic bitmap image without gd or imagemagick you can do so with pack for converting machine byte order to little endian byte order and some functions for handling string, for reference and more details you can check the Wikipedia page bitmap file format or this script on 3v4l.

For this example I will be using a more complex input, this just for better explain how each line should be aligned when creating a bitmap;

<?php
$pixelDataArray = array(
    "11101010111",
    "10101010101",
    "11101110111",
    "10001010100",
    "10001010100",
);

First to convert the input into a pixel array or bitmap data, each line on the pixel array should be dword/32bit/4bytes aligned.

$pixelWidth = strlen($pixelDataArray[0]);
$pixelHeight = count($pixelDataArray);

$dwordAlignment = 32 - ($pixelWidth % 32);
if ($dwordAlignment == 32) {
    $dwordAlignment = 0;
}
$dwordAlignedLength = $pixelWidth + $dwordAlignment;

Now we can proper align the string then convert it to a array of 1 byte integers and after to a binary string.

$pixelArray = '';
foreach (array_reverse($pixelDataArray) as $row) {
    $dwordAlignedPixelRow = str_pad($row, $dwordAlignedLength, '0', STR_PAD_RIGHT);
    $integerPixelRow = array_map('bindec', str_split($dwordAlignedPixelRow, 8));
    $pixelArray .= implode('', array_map('chr', $integerPixelRow));
}
$pixelArraySize = \strlen($pixelArray);

Then lets build the color table

$colorTable = pack(
    'CCCxCCCx',
    //blue, green, red
    255,  255,   255, // 0 color
    0,    0,     0    // 1 color
);
$colorTableSize = \strlen($colorTable);

Now the bitmap information header, for better support BITMAPINFOHEADER (40 bytes header) will be used.

$dibHeaderSize = 40;
$colorPlanes = 1;
$bitPerPixel = 1;
$compressionMethod = 0; //BI_RGB/NONE
$horizontal_pixel_per_meter = 2835;
$vertical_pixel_per_meter = 2835;
$colorInPalette = 2;
$importantColors = 0;
$dibHeader = \pack('VVVvvVVVVVV', $dibHeaderSize, $pixelWidth, $pixelHeight, $colorPlanes, $bitPerPixel, $compressionMethod, $pixelArraySize, $horizontal_pixel_per_meter, $vertical_pixel_per_meter, $colorInPalette, $importantColors);

The last part is the file header

$bmpFileHeaderSize = 14;
$pixelArrayOffset = $bmpFileHeaderSize + $dibHeaderSize + $colorTableSize;
$fileSize = $pixelArrayOffset + $pixelArraySize;
$bmpFileHeader = pack('CCVxxxxV', \ord('B'), \ord('M'), $fileSize, $pixelArrayOffset);

Now just concatenate all into a single string and it is ready for use.

$bmpFile = $bmpFileHeader . $dibHeader . $colorTable . $pixelArray;
$bmpBase64File = base64_encode($bmpFile);
?>
<img src="data:image/bitmap;base64, <?= $bmpBase64File ?>" style="image-rendering: crisp-edges;width: 100px;"/>

<img src="data:image/bitmap;base64, Qk1SAAAAAAAAAD4AAAAoAAAACwAAAAUAAAABAAEAAAAAABQAAAATCwAAEwsAAAIAAAAAAAAA////AAAAAACKgAAAioAAAO7gAACqoAAA6uAAAA==" style="image-rendering: crisp-edges;width: 100px;height: ;"/>
like image 29
Gregorio bonfante Avatar answered Nov 11 '22 23:11

Gregorio bonfante


That's NOT a strange request.

I completely agree with the purpose of the question, in fact I have to manage some 1bit monochrome images.

The answer is:

  • GD is not well documented in PHP website.
  • When you want to create an image from scratch you have to use imagecreate() or imagecreatetruecolor()
  • It seems that both of the just mentioned methods (functions) cannot create 1bit images from scratch.
  • I solved by creating an external image, 1bit monochrome png, loading it with imagecreatefrompng().

In addition: I've just downloaded the official library open source code from hereOfficial Bitbucket Repository

What I've found in gd.h?:

The definition of the upper mentioned functions:

/* Functions to manipulate images. */

/* Creates a palette-based image (up to 256 colors). */
BGD_DECLARE(gdImagePtr) gdImageCreate (int sx, int sy);

/* An alternate name for the above (2.0). */
 \#define gdImageCreatePalette gdImageCreate

/* Creates a truecolor image (millions of colors). */
BGD_DECLARE(gdImagePtr) gdImageCreateTrueColor (int sx, int sy);

So the "official" solution is: create a 2 colour palette image with imagecreate() (that wraps the gdImageCreate() GD function).

The "alternative" solution is to create an external image, 1bit monochrome png, and it with imagecreatefrompng() as I said above.

like image 145
trincio Avatar answered Nov 11 '22 23:11

trincio


That's a little bit of a strange request :)

So, what you'd want to use here is php-gd, for a start. Generally this is included when installing php on any OS with decent repo's, but just incase it isn't for you, you can get the installation instructions here;

http://www.php.net/manual/en/image.setup.php

First, we'll need to figure out exactly how big the image will need to be in width; height will obviously always be one.

So;

$str = $_GET['str'];
$img_width = strlen($str);

strlen will tell us how many characters are in the $str string, and since we're giving one pixel per character, the amount of characters will give us the required width.

For ease of access, split the string into an array - with each element for each separate pixel.

$color_array = str_split($str);

Now, let's set up a "pointer", for which pixel we're drawing to. It's php so you don't NEED to initalise this, but it's nice to be tidy.

$current_px = (int) 0;

And now you can initialise GD and start making the image;

$im = imagecreatetruecolor($img_width, 1);
// Initialise colours;
$black = imagecolorallocate($im, 0, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
// Now, start running through the array
foreach ($color_array as $y)
{
  if ($y == 1)
  {
    imagesetpixel ( $im, $current_px , 1 , $black );
  }
  $current_px++; // Don't need to "draw" a white pixel for 0. Just draw nothing and add to the counter.
}

This will draw your image, then all you need do is display it;

header('Content-type: image/png');
imagepng($im);
imagedestroy($im);

Note that the $white declaration isn't needed at all - I just left it in to give you an idea of how you declare different colours with gd.

You'll probably need to debug this a bit before using it - it's been a long time since I've used GD. Anyway, hope this helps!

like image 5
Eoghan Avatar answered Nov 12 '22 00:11

Eoghan