How to make any "extra space" white when using imagecopy in PHP - php

I am using imagecopy to crop a PNG image to a user specification.
Currently, if the crop area is bigger than the image, any "extra space" becomes black, but I would like it to be white.
Have searched around a bunch, I have discovered that you can use imagefill or imagefilledrectangle to make the background white, however if this is done before imagecopy, then it has no effect and if it is done after imagecopy, it also makes any black parts of the image white.
My code currently looks like this and suffers from black parts of the original image being turned white as well as the extra space:
// Input and output files
$infile = "[Image]";
$outfile = "[Output path for image]"
// Make the image
$orig =imagecreatefromjpeg($infile);
$width = imagesx($orig);
$height = imagesy($orig);
$new = imagecreatetruecolor($width, $height);
// Crop the image
imagecopy($new, $orig, 0, 0, -100, 100, $width, $height);
// Try and make the extra space white
$white = imagecolorallocate($new, 255,255,255);
imagefill($new, 0, 0, $white);
// Save the file
imagepng($new, $outfile);
How can I make that extra space white without affecting the original image? I have no control over what image users might upload, so I can't really pick a transparent color as that color might be part of their original image.
EDIT: This scenario arises when a user chooses a crop size outside of the original image dimensions, something that I do want to be a valid option. The crop is to force a square image, but if the user uploads, say, a landscape rectangle and wants all of their image in the final crop, then the crop will be outside of the image on the top and bottom (which is where I want it to be white instead of black)

This happens because you are supplying 'invalid' values to imagecopy() (that is, the crop coordinates are outside the bounds of the source image). GD simply fills in the out of bounds area with black pixels. It would be lovely if it instead used transparent (or any colour) pixels but unfortunately that's not an option.
I don't completely understand what you are trying to do (your source doesn't seem to match your stated goal) but a possible solution involves restricting the crop to the bounds of the image:
$src = imagecreatefromjpeg('JPEG FILE'); // 100x100 image in my test.
$src_w = imagesx($src);
$src_h = imagesy($src);
$user_crop = [
'x' => -50,
'y' => -50,
'width' => 150,
'height' => 150
];
if ($user_crop['x'] < 0) {
$user_crop['x'] = 0;
}
if ($user_crop['y'] < 0) {
$user_crop['y'] = 0;
}
if ($user_crop['x'] + $user_crop['width'] > $src_w) {
$user_crop['width'] = $src_w - $user_crop['x'];
}
if ($user_crop['y'] + $user_crop['height'] > $src_h) {
$user_crop['height'] = $src_h - $user_crop['y'];
}
$dest = imagecreatetruecolor($src_w, $src_h);
imagefill($dest, 0, 0, 0x00ffffff); // opaque white.
imagecopy(
$dest,
$src,
$user_crop['x'],
$user_crop['y'],
$user_crop['x'],
$user_crop['y'],
$user_crop['width'],
$user_crop['height']
);
header('Content-type: image/png;');
imagepng($dest);
imagedestroy($src);
imagedestroy($dest);
exit;
Note that I've made a few assumptions in this code about placement of the cropped image.

Related

PHP imagerotate is cropping image

I am writing a script that takes an arrow image and rotates it by a set number of degrees. Using the code below, when the angle is a multiple of 90 the image rotates and displays as expected.
The source image looks like this (74 x 74):
Images after rotating by 90:
Images after rotating by any other number (not a multiple of 90) eg 45:
As can be seen in the image, the tip of the arrow has been cropped out of the image. Could anyone please tell me why this is happening? Again, multiples of 90 are fine, it's just any other number where the unusual cropping occurs.
$props = ['w' => 74, 'h' => 74];
$angle = 360 - $_GET['angle'];
$final_img = imagecreatetruecolor($props['w'], $props['h']);
imagesavealpha($final_img, true);
$transColor = imagecolorallocatealpha($final_img, 0, 0, 0, 127);
imagefill($final_img, 0, 0, $transColor);
$rotate = imagecreatefrompng('arrow.png');
$src = imagerotate($rotate, $angle, $transColor); //rotated my image
$src_x = ImageSX($src); //find out new x width
$src_y = ImageSY($src); //find out new y height
$src_widthx = $src_x/2 - $props['w']/2; // divide each by 2 and then subtract desired end width from wider rotated width
$src_heighty = $src_y/2 - $props['h']/2; // and again for height
imagecopy($final_img, $src, 0, 0, $src_widthx, $src_heighty, $props['w'], $props['h']);
header('Content-Type: image/png');
imagepng($final_img);
When you rotate a square of nXm pixels by lets say 45 degrees you will get the diagonals(which are bigger than n or m and equal sqrt(n^2+m^2)) of the image be the new rotated image width and height.
The function crops the rotated image using the original dimensions of the image, namely n and m.
A way to fix the problem would be by crating a bigger blank image with the appropriate size, sqrt(width_original_image^2+height_original_image^2), and than copy the original image to the new image using imagecopy. After that you can use imagerotate on the new image
I installed and used the ImageMagick PHP library and the rotations show uncropped, no matter the degree of rotation.

PHP imagecopyresampled() produces image border on one side

I have a resampling script that centers resampled images on a square canvas.
For horizontally-oriented images that are centered vertically on a white background a line is added along the BOTTOM edge of the resampled image.
For vertically-oriented images that are centered horizontally the line appears on the RIGHT edge of the resampled image.
$thumb = imagecreatetruecolor($th_width, $th_height);
imagecopyresampled($thumb, $source, $th_x, $th_y, 0, 0, $th_width, $th_height, $src_width, $src_height);
$bgcolor = imagecolorallocate($thumb, 255, 255, 255);
imagefill($thumb, 0, 0, $bgcolor);
The line is there regardless of background fill, it just shows up most on white.
What causes this? No amount of adjusting parameter values will get rid of it (they just offset the resampled image on the background or distort it).
I am providing my own workaround for this issue.
It appears the image border artifact is a COMBINATION result of 'imagecopyresampled()' AND centering offsets. If I just resample the image as-is THEN center and fill the image, the border is avoided. Here is the workaround:
1) RESAMPLE your image as-is (same aspect ratio) then SAVE to retain changes:
$thumb = imagecreatetruecolor($res_width, $res_height);
imagecopyresampled($thumb, $source, 0, 0, 0, 0, $res_width, $res_height, $src_width, $src_height);
// SAVE new image - set quality to lossless since reusing it:
imagejpeg($thumb, "resampled/output_temp.jpg", 100);
2) Retrieve the temporary image:
$file = "resampled/output_temp.jpg";
$image = file_get_contents($file);
$source = imagecreatefromstring($image);
// Get the RESAMPLED image dimensions
list($src_width, $src_height) = getimagesize($file);
3) NOW apply CENTERING and FILL:
$thumb = imagecreatetruecolor($th_width, $th_height);
// COPY the image
imagecopy($thumb, $source, $th_x, $th_y, 0, 0, $src_width, $src_height);
$bgcolor = imagecolorallocate($thumb, 255, 255, 255);
// Replace default black background - this must be placed AFTER imagecopy()
imagefill($thumb, 0, 0, $bgcolor);
// Save image (uses default quality setting 75)
imagejpeg($thumb, "resampled/" . $newfile);
// Optionally free up memory:
imagedestroy($thumb);
The result is a clean image.
For correcting incomplete imagefill() on the background when centering vertically-oriented images see this post:
imagecopyresampled() results in split color background imagefill()

imagecopyresampled() results in split color background imagefill()

I have a script that takes an image and when resampled centers the short dimension (width or height) on a square coloured background.
This works fine for images with a longer WIDTH but for some reason any image with a longer HEIGHT the result is a split background fill - the correct colour on the left but the default black on the right. If I play with the x-axis offset the background fill only extends to the right as far as the image placement.
The calculated values are as expected for the vertical images so I cannot figure out what is happening here. Note that 'imagecopy()' produces the exact same behaviour.
original image is 155 x 400px
adjusted source dimensions for square aspect ratio = 400 x 400px
resulting thumbnail to be 250 x 250px
Here is the code with static values for one example:
$thumb = imagecreatetruecolor(250, 250);
imagecopyresampled($thumb, $source, 77, 0, 0, 0, 250, 250, 400, 400);
$blue = imagecolorallocate($thumb, 0xDE, 0xE6, 0xF9);
imagefill($thumb, 0, 0, $blue);
Using the same image rotated 90 degrees (400 x 155 px) so it is longer horizontally DOES apply the full background fill:
imagecopyresampled($thumb, $source, 0, 77, 0, 0, 250, 250, 400, 400);
For the vertical image, my coordinate values (77, 0) place the image on the imagecreatetruecolor() canvas centered exactly where I want it but changing any of the other imagecopyresampled() values stretch or squeeze the resampled image or crop it.
Am I overlooking something simple? View the screenshots here:
http://i.stack.imgur.com/5CxHU.jpg (vertical issue) and
http://i.stack.imgur.com/wvhzP.jpg (OK horizontally)
This vertical issue must have something to do with PHP's resampling/imagefill algorithm (?) but here is a workaround that now works for centering all of my vertical images within my square canvas:
1) You need to first pad your image placeholder so the background fill will extend to the right edge in the resampled image by extending the thumbnail height with your x-axis offset (sounds odd but it works)...we will trim this off later:
$thadj_height = $th_height + $th_x;
$thumb = imagecreatetruecolor($th_width, $thadj_height);
2) Resample as usual with the background fill (note that the fill is applied AFTER the resampling statement, odd but just works that way)...remember that $thumb has more height than what $th_width, $th_height will occupy:
imagecopyresampled($thumb, $source, $th_x, $th_y, 0, 0, $th_width, $th_height, $src_width, $src_height);
imagefill($thumb, 0, 0, $bgcolor);
3) Temporarily save the image output so a new function can be applied to it next - set quality to lossless since we'll be reusing it:
imagejpeg($thumb, "resampled/output_temp.jpg", 100);
imagedestroy($thumb);
4) Retrieve the temporary file and grab the new dimensions (overwrite the previous variables):
$file = "resampled/output_temp.jpg";
$image = file_get_contents($file);
$source = imagecreatefromstring($image);
list($src_width, $src_height) = getimagesize($file);
5) Create a new image placeholder, square as originally intended in my case:
$thumb = imagecreatetruecolor($th_width, $th_height);
6) Now copy the temporary padded thumbnail into the square placeholder which will result in cropping off the padding:
imagecopy($thumb, $source, 0, 0, 0, 0, $src_width, $src_height);
header('Content-Type: image/jpeg');
echo imagejpeg($thumb);
imagedestroy($thumb);
Again, none of this is necessary for centering my horizontal images on a square canvas but this is a workaround that will work to eliminate the split fill background.

PHP GD image merge changing my image to black

I have a very frustrating situation. I am using PHP GD for the first time, and it's been a bit of a rollercoaster relationship. Basically, I am trying to merge 2 images, a square one (with a height/width of x) onto a rectangle (with a width of x and a height of y).
The square needs to be centered vertically. But this isn't the issue - I've managed to position it correctly.
Whats happening is, my rectangle is white. My square has a white background, so when the images are merged, it should just look like my asset on a white rectangluar background.
When I merge the image though, GD is for some reason changing my background white rectangle to black - so you can see the white square in the middle, with black "bars" on top and bottom. Can anyone help?
Code is:
//create copy of original image to correct size
imagecopyresized($dst_image, $src_image, 0,0,0,0,$x_width,$x_height,$orig_img_x_width,$orig_img_x_height);
imagejpeg($dst_image, "resized_copy.jpg", 100);
$img = imagecreatetruecolor(1333, 2000);
$white = imagecolorallocate($img, 255, 255, 255);
imagefill ( $img, 0, 0, $white );
imagefilledrectangle($img,0,0,1333,2000, $white);
imagejpeg($img, "rectangle.jpg", 100);
//merge images
$dest2 = imagecreatefromjpeg("rectangle.jpg");
$src2 = imagecreatefromjpeg('resized_copy.jpg');
imagecopymerge($dest2, $src2, 0, 0, 0, -333.5, $x_width, $x_height, 100);
imagejpeg($dest2, "final_image.jpg", 100);
I've tried using imagecopy instead of imagecopymerge, but I get the same result. I'm sure there is a simple explanation, but I cant seem to find it trawling through the php manual.
I've read your question a few times but I'm not convinced I understand exactly what you are trying to achieve so I've made a few assumptions in producing the below code.
For the sake of simplicity I've created a 'square.jpg' test image file like so:
(Note that I've used small image sizes here so I can show them inline.)
// read in the square test image.
$square = imagecreatefromjpeg('square.jpg');
$square_x = imagesx($square); // 100px
$square_y = imagesy($square); // 100px
// create the rectangular image to merge with.
$rectangle = imagecreatetruecolor(100, 200);
$rectangle_x = imagesx($rectangle); // 100px
$rectangle_y = imagesy($rectangle); // 200px
// note that this isn't white, but rather a lovely shade of blue to better
// show the image on the white SO background!
$white = imagecolorallocate($rectangle, 128, 128, 255);
imagefill($rectangle, 0, 0, $white);
// merge the images.
imagecopymerge(
$rectangle,
$square,
0,
($rectangle_y / 2) - ($square_y / 2), // to vertically centre the square.
0,
0,
$square_x,
$square_y,
75 // Just to show the merge clearly; change back to 100 for your usage.
);
imagejpeg($rectangle, 'final_image.jpg', 100);
imagedestroy($rectangle);
imagedestroy($square);
This gives me the following image in final_image.jpg:

Rendering a coloured background with transparent foreground in PHP

I am rendering audio waveforms directly from MP3's on the fly as they are uploaded to the server. My upload script currently saves both the original mp3 and renders the waveform to a png.
Currently I render the background first to a rectangle. This code produces either a transparent or coloured background dependant upon the value of $background:
I am trying to create a transparent png overlay in PHP.
if (!$img) {
// create original image width based on amount of detail
// each waveform to be processed with be $height high, but will be condensed
// and resized later (if specified)
$img = imagecreatetruecolor($data_size / DETAIL, $height * sizeof($wavs_to_process));
// fill background of image
if ($background == "") {
// transparent background specified
imagesavealpha($img, true);
$transparentColor = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagefill($img, 0, 0, $transparentColor);
} else {
list($br, $bg, $bb) = html2rgb($background);
imagefilledrectangle($img, 0, 0, (int) ($data_size / DETAIL), $height * sizeof($wavs_to_process), imagecolorallocate($img, $br, $bg, $bb));
}
}
I then loop through the data points of a dynamically resampled MP3 and for each point I draw a line onto this background which renders the waveform image.
I use this code to produce the waveform image:
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5 && !$draw_flat))
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline(
$img,
// x1
(int) ($data_point / DETAIL),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height * $wav - $v,
// x2
(int) ($data_point / DETAIL),
// y2: same as y1, but from the bottom of the image
$height * $wav - ($height - $v),
imagecolorallocate($img, $r, $g, $b)
);
} else {
// skip this one due to lack of detail
fseek($handle, $ratio + $byte, SEEK_CUR);
}
}
This works perfectly, however I need to create the waveform as a transparent overlay in order to place it over a div with a CSS gradient. So, I need to render the background as #ffffff and the actual waveform itself needs to be transparent. In essence I need the transparency inverted on the produced png's.
I have tried using:
imagecolorallocatealpha($img, 0, 0, 0, 127)
On the waveform rendering portion but always seem to just end up with a coloured rectangle with no visible waveform and I'm not sure where I am going wrong.
Any help would be greatly appreciated.
Please try the following. Disable blending mode­Docs for the image:
imagealphablending($img, FALSE);
This will enable that you can set pixels with alpha information directly. To do so, you need to allocate the color with the alpha­Docs set to 100% transparent as well:
imagecolorallocatealpha($img ,$r, $g, $b, $alpha = 127);
BTW, you can allocate the color once and then re-use it, so you don't need to call the imagecolorallocatealpha function that often.
Let me know if this works.

Categories