Create 1 bit bitmap (monochrome) in php - php

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?

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!

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.

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: ;"/>

Related

Is it possible to retrieve the alpha value of a pixel of a PNG file in the 0-255 range?

In a recent competition I was given the task to extract binary data (another PNG) from a PNG image file's alpha channel. The data was encoded in such a way that if I read the values in the alpha channel for each pixel from the top left (e.g. 80,78,71,13,10,26,10) up to a specific point then the resulting data would form another image.
Initially I tried to complete this task using PHP, but I hit a roadblock that I could not overcome. Consider the code below:
function pad($hex){
return strlen($hex) < 2 ? "0$hex" : $hex;
}
$channel = '';
$image = 'image.png';
$ir = imagecreatefrompng($image);
imagesavealpha($ir, true);
list($width, $height) = getimagesize($image);
for ($y = 0; $y < $height; $y++){
for ($x = 0; $x < $width; $x++){
$pixel = imagecolorat($ir, $x, $y);
$colors = imagecolorsforindex($ir, $pixel);
$a = pad(dechex($colors['alpha']));
$channel.= $a;
}
}
After running this, I noticed that the output did not contain the PNG magic number, and I just didn't know what went wrong. After a bit of digging I found out that $colors['alpha'] only contained values less than or equal to 127. Due to the data having been encoded with a 0-255 range in mind, I could not find a way to extract it, and ended up (successfully) solving the problem with node.js instead.
So, my question is: Is there any way I could have read the PNG file's alpha channel that would have returned the values in a 0 to 255 range as opposed to 0-127, or is this a hard-coded limitation of PHP and/or GD?
For the record, I tried to use ($colors['alpha']/127)*255 in order to try and forge the value in the incorrect range to the correct one, but to no avail.
It is a limitation of GD. According to https://bitbucket.org/libgd/gd-libgd/issues/132/history-behind-7-bit-alpha-component, in GD source it says:
gd has only 7 bits of alpha channel resolution, and 127 is
transparent, 0 opaque. A moment of convenience, a lifetime of
compatibility.
If you install ImageMagick PHP extension, you can get the alpha value between 0-255 for a pixel (let's say with x=300 and y=490) like this:
$image = new Imagick(__DIR__ . DIRECTORY_SEPARATOR . 'image.png');
$x = 300;
$y = 490;
$pixel = $image->getImagePixelColor($x, $y);
$colors = $pixel->getColor(true);
echo 'Alpha value: ' . round($colors['a'] * 255, 0);
ImageMagick: https://www.imagemagick.org
ImageMagick for PHP (called Imagick): http://php.net/manual/en/book.imagick.php
In your code:
list($width, $height) = getimagesize($image);
references a variable '$image' that was not defined in the code.
using getimagesize means $image is a filename.
so this line get's width and height from a filename.
This line broke the code when I tested it.
You already have your answer, but this is for posterity.
Considering the code, I feel:
$width=imagesx ($ir);
$height=imagesy ($ir);
Would be more logical (and actually works)

Crop/cut-out image based on existing selection/path already drawn on image (ideally via PHP)

I've scoured around the internet a fair bit and I can't seem to find any reference to what I am attempting to achieve... I fear that means I'm probably going about doing something the wrong way, but I'll pose this question here anyways in hopes that maybe I am not.
I would like to take an already generated image that has a rectangular selection already drawn on it via a specific color and a dynamic (but always rectangular) path, and crop or cut-out (and use) the inner area of that rectangular path.
Let's use an image generated by google maps as an example for this:
I thought perhaps the imagemagick library would hold a solution for this, but, I don't know if it's because I haven't quite narrowed down the exact key terms for what I am looking to do exactly, or if it's because it cannot (at least not simply) be done, but I haven't turned up any solutions.
Any solutions, advice, or smacks to the head are welcome.
[Please note that (for now) I would like to operate under the assumption that these images already exist, so any information regarding the pixel coordinates of the relative selection area on the image doesn't exist]
Your problem seems to boil down to this: How do I find a red rectangle in an image?
This is quite an open-ended problem, and could actually be quite difficult to solve. However, if the following assumptions can be made, then the task will be a lot easier:
The rectangle is drawn in pure RGB red (#ff0000).
The rectangle is aligned parallel with the image edges.
The image is saved in a lossless format like PNG.
The image contains no other pixels of this exact colour.
We know the width of the rectangle's edges.
The example you provided seems to tick all these boxes. Since it's stored as an 8-bit indexed color image, the first step would be to convert it into a true color image. This makes it easier to check the pixel values.
Then find the outermost edges of the frame, inset the coordinates by the frame width, and crop the image. Here's some code that will do this for you:
<?php
$src_img = 'er7RT.png';
$frame_color = 0xff0000;
$frame_width = 6;
// Load image and copy to true color image resource
$im = imagecreatefrompng($src_img);
$sw = imagesx($im);
$sh = imagesy($im);
$im1 = imagecreatetruecolor($sw, $sh);
imagecopy ($im1, $im, 0, 0, 0, 0, $sw, $sh);
imagedestroy($im);
// Get outer dimensions of frame.
// Assume the frame color appears nowhere else in the image.
$minx = $miny = 999999;
$maxx = $maxy = -$minx;
for ($x=0; $x<$sw; $x++) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $minx = $x; break 2; }
}
for ($x=$sw-1; $x>=0; $x--) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxx = $x; break 2; }
}
for ($y=0; $y<$sh; $y++) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $miny = $y; break 2; }
}
for ($y=$sh-1; $y>=0; $y--) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxy = $y; break 2; }
}
if ($minx>=$maxx || $miny>=$maxy) die("Couldn't locate frame");
// Subtract frame width to obtain crop region
$minx += $frame_width;
$maxx -= $frame_width;
$miny += $frame_width;
$maxy -= $frame_width;
// Create new image with cropped dimensions
$im2 = imagecreatetruecolor($maxx-$minx, $maxy-$miny);
imagecopy ($im2, $im1, 0, 0, $minx, $miny, $maxx-$minx, $maxy-$miny);
// Finish up
header("Content-Type: image/png");
imagepng($im2);
imagedestroy($im1);
imagedestroy($im2);

PHP Imagick how to best fit text annotation

I'm adding annotation text to a newPseudoImage which works fine but I'd like to make the text scale to fit the image size.
Any ideas how I might do this?
$im = new Imagick();
$draw = new ImagickDraw();
$draw->setFillColor($color);
$draw->setFont($font);
$draw->setFontSize(($width, $height) / 100) * 15);
$draw->setGravity(Imagick::GRAVITY_CENTER);
$im->newPseudoImage($width, $height, "canvas:{$bg}");
$im->annotateImage($draw, 0, 0, 0, $text);
$draw->clear();
$draw->destroy();
$im->setImageFormat('gif');
header("Content-Type: image/gif");
echo $im;
I think you could use the imageftbbox function to help you out.
This will give you the bounding box for a text string, with the given ttf font, size, and angle.
You could create a loop to increase or decrease the font size as long as the text is not fitting the image properly.
<?php
$bbox = imageftbbox(12, 0, 'Arial.ttf', 'This is a test');
$width_of_text = $bbox[2] - $bbox[0];
You could look at the $width_of_text and adjust the font size as long as the font isn't scaled to your liking. Keep in mind, as you increase the font, the width and height will grow.
Depending on what you are trying to scale it to that may help.
I'm facing the same issue and although I've not tried this yet as I'm away from my machine, I'm going to give this a go.
Using the query font metrics function of the class I will be able to get the calculated width of the text and then compare it with the specified width of its container. I'll make adjustments to the font size and repeat until its near enough. You could get it quite accurate this way but bare in mind possible performance issues if you have multiple text items in the image.
On the other hand, if you weren't concerned about styling the text as much you could use caption.
This is a slightly naive solution (I could have used binary search to find the proper font size) , but it works for me.
In my example I want to place text on a box in the image, so I calculate the proper font size with imageftbbox.
$size = $MAX_FONT_SIZE;
while (true){
$bbox = imageftbbox($size, 0, $font, $text );
$width_of_text = $bbox[2] - $bbox[0];
if ($width_of_text > $MAX_TEXT_WIDTH) {
$size -= 1;
}
else {
break;
}
}
$height_of_text = ($bbox[3] - $bbox[1]);
$draw->setFontSize( $size );
$image->annotateImage($draw, $TEXT_WIDTH_CENTER - $width_of_text/2, $TEXT_HEIGHT_CENTER - $height_of_text/2, 0, $text);

PHP FPDF With Transparent PNG's Very Slow

I'm using FPDF with an extension I found online called TransFPDF that allows for transparent PNG's to be put into the PDF that I am dynamically generating with PHP. The problem I am having however is that the PDF takes a long time to generate (the script takes about 30 seconds to run when I am embedding about 6 characters, where each characters is a transparent PNG. This time also includes text and the background but I checked times and those only take about a second or two and are not slowing down the code).
I have found that the main slow point is the following function:
// needs GD 2.x extension
// pixel-wise operation, not very fast
function ImagePngWithAlpha($file,$x,$y,$w=0,$h=0,$link='')
{
$tmp_alpha = tempnam('.', 'mska');
$this->tmpFiles[] = $tmp_alpha;
$tmp_plain = tempnam('.', 'mskp');
$this->tmpFiles[] = $tmp_plain;
list($wpx, $hpx) = getimagesize($file);
$img = imagecreatefrompng($file);
$alpha_img = imagecreate( $wpx, $hpx );
// generate gray scale pallete
for($c=0;$c<256;$c++) ImageColorAllocate($alpha_img, $c, $c, $c);
// extract alpha channel
$xpx=0;
while ($xpx<$wpx){
$ypx = 0;
while ($ypx<$hpx){
$color_index = imagecolorat($img, $xpx, $ypx);
$alpha = 255-($color_index>>24)*255/127; // GD alpha component: 7 bit only, 0..127!
imagesetpixel($alpha_img, $xpx, $ypx, $alpha);
++$ypx;
}
++$xpx;
}
imagepng($alpha_img, $tmp_alpha);
imagedestroy($alpha_img);
// extract image without alpha channel
$plain_img = imagecreatetruecolor ( $wpx, $hpx );
imagecopy ($plain_img, $img, 0, 0, 0, 0, $wpx, $hpx );
imagepng($plain_img, $tmp_plain);
imagedestroy($plain_img);
//first embed mask image (w, h, x, will be ignored)
$maskImg = $this->Image($tmp_alpha, 0,0,0,0, 'PNG', '', true);
//embed image, masked with previously embedded mask
$this->Image($tmp_plain,$x,$y,$w,$h,'PNG',$link, false, $maskImg);
}
Does anyone have any ideas of how I could speed this up? I can't seem to get it to go faster than about 4 seconds per character which really adds up fast (a character probably is about 1000x2000 pixels, and yes I know this is a lot, but yes it is neccessary for a printable PDF).
Thank you.

Setting the grayscale value of a pixel in php

I have a jpeg image resource loaded into a variable in php. Given a grayscale value, like 6, how can I set a single, particular pixel to that grayscale value? My client has made it exceedingly clear that there is a big difference between grayscale and rgb.
Is this even doable in php with the GD library? If so, how?
Note: The script does indeed grayscale the entire image, but using an obscure algorithm. My script obtains the RGB for each pixel, and obtains the grayscale value corresponding to the algorithm. It just needs to now transform that pixel to that particular grayscale value.
You will probably find the the imagesetpixel function handy. The following is an example of how you could use it:
// $image = <GD image resource>
for($x = 0; $x < $width; x++)
{
for($y = 0; $y < $height; $y++)
{
$value = specialFunction($rgb_value);
// Depending on what the above function returns, the call below
// might have to be changed
$color = imagecolorallocate($image, $value, $value, $value);
imagesetpixel($image, $x, $y, $color);
}
}
Do you have access to the terminal or something along those lines to install Image Magick. If so, then you can could use the following
convert image.jpg -colorspace Gray image-bw.jpg
I'm not sure how to do it with the PHP GD Library sorry.

Categories