ImageMagick Trim Whitespace with offset - php

I am Trimming whitespace from my images using ImageMagick. Everything is working great and successfully trimming. But now I want to leave an offset of about 30px and cut extra whitespace from images. I mean to say my current result is
Now you can see that above image is completely trimmed but I want some offset like
I want to leave 30px on every side and trimming remaining whitespace. I am trimming with Fuzz with following code
$image = new Imagick('capfile.jpg');
$image->trimImage(25000);
I don't want to add borders or crop. I just want to trim with offset of 30px to main image itself because many of my images also have some light background colors which is trimmed using Fuzz so adding borders is not an option.

You can get the trim-box that ImageMagick would trim to like this at the command line:
convert -fuzz 10% -format %# cap.jpg info:
259x171+19+21
Then you can modify the width/height and offset as you wish before using the modified numbers to do a crop - i.e. subtract 30 from the x and y offset and add 60 to the width and height.
Edited by emcconville
How can I do this in php?
Here's a PHP alternative...
$img = new Imagick('/tmp/Zpuq9.jpg');
// Get Quantum to calculate 40%
$quantumInfo = $img->getQuantumRange();
// -fuzz 40%
$img->trimImage($quantumInfo['quantumRangeLong'] * 0.4);
// -format %#
$img->setOption('format','%#');
// info:
$img->setImageFilename('info:');
/*
* For this example, let's use PHP memory protocol
* to capture std out (from `info:').
*/
$fd = fopen('php://memory','rwb');
$img->writeImageFile($fd);
fseek($fd, 0);
$info = fread($fd, 1024);
fclose($fd);
var_dump($info);
//=> string(13) "258x170+19+22"

I know this kinda of silly but this worked for me and it is workaround. I couldn't get this so I figured out completely different approach.
1) Sometimes we are uploading photos that are visualisations like bedroom interior so we don't need to trim whitespace in photo because it is not existing. That is why we need to check if photo is bright or dark with get_avg_luminance function found in this thread https://stackoverflow.com/a/5959461/2760717
2) Now we have to write this
$src_image = new Imagick($src_file);
$dimmensions = $src_image->getImageGeometry();
$height = $dimmensions['height'];
$luminance = get_avg_luminance($src_file);
// If image is mostly bright then add white border with 5% of entire height
if ($luminance > 170) {
$quantumInfo = $src_image->getQuantumRange();
if ($src_image->trimImage($quantumInfo['quantumRangeLong']*0.1)) {
// add white border with 5% of offset
$src_image->borderImage('#ffffff', 0, ($height *0.05));
}
}
It is adding border to image and work ok for me :)

Related

Automatically resize image on the shorter side regardless of orientation in imagemagick

Im struggling to resize an image regardless of orientation using imagemagick, is the program that terrible that a basic detection method is not even included? Do I have to now go and check the shortest side using getimagesize and comparing them or what?
-resize 125^
only works one way, the minute i flip the image 90 degrees the shorter side becomes smaller than 125.. ie its not resizing the shorter side on both orientations. Im trying to use less code if that makes a difference.
here is a function ive just completed to force an exact pixel size - I cant guarantee it 100% but ive tested it with many options and got perfect results so far, it gives the closest result imo. First it resizes the SMALLEST difference between the source image and specified sizes by calculating ratios. Then trims off excess pixels. I have compensated for odd numbers, negative values, etc. I have had good results so far. Please let me know if ive missed something or if it breaks somehow:
PHP:
// set source/export paths and pixel sizes for final sizes
$src="path/to/source.jpg";
$exp="path/to/output.jpg";
$crop_w=300;
$crop_h=200;
$size = getimagesize("$src");
//check image sizes
if( ($size[0] < $crop_w) || ($size[1] < $crop_h) ){
echo 'Image not big enough to crop';
exit();
}
//get differential ratios of image vs crop sizes -
//smaller ratio must be resized
$ratio_w = $size[0]/$crop_w;
$ratio_h = $size[1]/$crop_h;
//square or landscape - shave sides
if($ratio_w >= $ratio_h){
//resize width / shave top&bottom
exec("convert $src -resize x".$crop_h." $exp ");
$size = getimagesize("$exp");
$diff=abs($crop_w-$size[1]);
//dividing 1 by 2 will leave a zero on round down - just force resize
if($diff < 2){
// skip shave - diff too small
exec('convert $exp -resize '.$crop_w.'X! $exp ');
}
else{
//divide difference by 2 for shave amount
$shave = round($diff/2,0,PHP_ROUND_HALF_DOWN); //halve & round difference down to avoid cropping smaller
exec('convert '.$exp.' -shave '.$shave.'x0 '.$exp.' '); //shave sides
//odd $diff leave a rounded down pixel - force height resize
if($diff%2 !==0){//if $diff was not divisible by two then 1 pixel is left from round down
exec('convert '.$exp.' -resize '.$crop_w.'x! '.$exp.' ');
}
}
}
//portrait - shave height
else{
//resize width / shave top&bottom
exec("convert $src -resize ".$crop_w."x $exp ");
$size = getimagesize("$exp");
$diff=abs($crop_h-$size[1]);
//dividing 1 by 2 will leave a zero on round down - just force resize
if($diff < 2){
exec('convert $exp -resize x'.$crop_h.'! $exp ');
}
else{
//divide difference by 2 for shave amount
$shave = round($diff/2,0,PHP_ROUND_HALF_DOWN); //halve & round difference down to avoid cropping smaller
exec('convert '.$exp.' -shave 0x'.$shave.' '.$exp.' '); //shave sides
//odd $diff leave a rounded down pixel - force height resize
if($diff%2 !==0){//if $diff was not divisible by two then 1 pixel is left from round down
exec('convert '.$exp.' -resize x'.$crop_h.'! '.$exp.' ');
}
}
}
Feel free to use / make comments. Php 5.4<, Imagemagick 6.8.8.1, Windows xampp.

php imagecolorat returns 0. Why?

Sometimes imagecolorat() returns 0 with some pixels in a PNG I'm analyzing. Why is that?
I looked the pixel and the color really is #111111. So it should return 1118481. Right?
I tried using imagealphablending($img,true) but I still get 0.
Thanks!
$img = #imagecreatefrompng($png);
if(!$img){
throw new Exception("Error loading PNG.");
}
var_dump(imagecolorat($img, 37, 625));
Result:
int 0
If image is loaded correctly and you get 0, then it means color is pure black (RGB => 0x000000 => 0)
Because that is the color of that pixel: black (red = 0, green = 0, blue = 0)
If you try it for most other pixels you will get 16777215 (0xFFFFFF), which is white (red = 255, green = 255, blue = 255).
Or the color is black, or I think the image is not loading correctly. At least not the .png you put as an example. Maybe the # is hiding the error of the image not loading. Its url is redirecting to another, and in that case you should use something like CURL to find the real image.

Create 1 bit bitmap (monochrome) in 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: ;"/>

imagemagick skew or distort an image

Well how could I change the before image to the after image by using imagemagick?
Is it the -skew command or the -distort, and how can I use it preferably in typo3 and php?
Any help is appreciated!
Using Imagemagick with php and the command line:
// Working on the original image size of 400 x 300
$cmd = "before.jpg -matte -virtual-pixel transparent".
" +distort Perspective \"0,0 0,0 400,0 400,22 400,300 400,320 0,300 0,300 \" ";
exec("convert $cmd perspective.png");
Note:
1/ This is for later versions of Imagemagick - the perspective operator has change.
2/ You need to use +distort not -distort as the image is larger than the initial image boundrys.
Examples of Imagemagick with php usage on my site http://www.rubblewebs.co.uk/imagemagick/operator.php
I think what you're looking for is the Imagick::shearImage function. This creates a checkerboard square and distorts it into a parallelogram (save this as a PHP file and open in your browser to see):
<?php
$im = new Imagick();
$im->newPseudoImage(300, 300, "pattern:checkerboard");
$im->setImageFormat('png');
$im->shearImage("transparent", 0, 10);
header("Content-Type: image/png");
echo $im;
?>
Within a larger script, to shear an image named myimg.png and save it as myimg-sheared.png, you can use:
$im = new Imagick("myimg.png");
$im->shearImage("transparent", 0, 10);
$im->writeImage("myimg_sheared.png");
If shearImage isn't versatile enough, you can try the Imagick::DISTORTION_PERSPECTIVE method via the Imagick::distortImage function.
Perspective distortion should give you what you want. Example:
convert original.png -matte -virtual-pixel white +distort Perspective '0,0,0,0 0,100,0,100 100,100,90,110 100,0,90,5' distorted.png
In TYPO3 you could apply it by (ab)using the SCALE object of the GIFBUILDER. Example:
temp.example = IMAGE
temp.example {
file = GIFBUILDER
file {
format = jpg
quality = 100
maxWidth = 9999
maxHeight = 9999
XY = [10.w],[10.h]
10 = IMAGE
10.file = fileadmin/original.png
20 = SCALE
20 {
params = -matte -virtual-pixel white +distort Perspective '0,0,0,0 0,100,0,100 100,100,90,110 100,0,90,5'
}
}
}

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);

Categories