Changing the center of rotation of Imagerotate - php

Imagerotate rotates the image using the given angle in degrees.
The center of rotation is the center of the image, and the rotated image may have different dimensions than the original image.
How do I change the center of rotation to coordinate x_new and y_new and avoid automatic resizing?
Example: Rotation around red dot.

First idea that comes to mind is to move the image so that its new center is at x_new, y_new rotate it and move back.
assumptions:
0 < x_new < w
0 < y_new < h
Pseudocode:
new_canter_x = MAX(x_new, w - x_new)
new_center_y = MAX(y_new, h - y_new)
create new image (plain or transparent background):
width = new_canter_x * 2
height = new_center_y * 2
copy your old image to new one to coords:
new_center_x - x_new
new_center_y - y_new
imagerotate the new image.
now you just have to cut the part that you are interested in out of it.

The right way to do it is rotate and then crop with right params of transformation.
Another way is move, rotate then move again (simpler math but more code).
$x and $y are coordinates of the red dot.
private function rotateImage($image, $x, $y, $angle)
{
$widthOrig = imagesx($image);
$heightOrig = imagesy($image);
$rotatedImage = $this->createLayer($widthOrig * 2, $heightOrig * 2);
imagecopyresampled($rotatedImage, $image, $widthOrig - $x, $heightOrig - $y, 0, 0, $widthOrig, $heightOrig, $widthOrig, $heightOrig);
$rotatedImage = imagerotate($rotatedImage, $angle, imageColorAllocateAlpha($rotatedImage, 0, 0, 0, 127));
$width = imagesx($rotatedImage);
$height = imagesy($rotatedImage);
$image = $this->createLayer();
imagecopyresampled($image, $rotatedImage, 0, 0, $width / 2 - $x, $height / 2 - $y, $widthOrig, $heightOrig, $widthOrig, $heightOrig);
return $image;
}
private function createLayer($width = 1080, $height = 1080)
{
$image = imagecreatetruecolor($width, $height);
$color = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagefill($image, 0, 0, $color);
return $image;
}

Related

PHP Image: generated image has right and bottom black border on production server, but not on my local machine

This app is written by me, I've tested it on my local machine, and the image generated by my app is great without bug on my local development server machine.
My app is an API, the user use it to generate tiled image. It mostly uses PHP Image GD library.
The issue: Generated image has one-pixel-width right and bottom black borders, but it only happen on production server, it doesn't on my local server. The border is only generated when the image is a transparent one (in my case: 'outline', 'invert', 'black' image type. Look at the code below). But sometimes the border is there mostly and sometimes it's not.
I'm very sure that there is nothing wrong with my code and my app is working flawlessly. I have tested on both environments with the same image type, same dimension, supplied with the same configuration for my app... and still, the production server generates image that has the border.
Here is a piece of code of my app to look suspiciously at:
$src = $img->filePath;
$src_outline = $img->filePathComplements['outline'];
$src_invert = $img->filePathComplements['invert'];
$src_black = $img->filePathComplements['black'];
$info_text = is_array($img->info) ? join($img->info, ', ') : (is_string($img->info) ? $img->info : '');
$w = $img->widthOriginal;
$h = $img->heightOriginal;
$x = $img->fit->x + $this->packer->getPageMarginLeft() + $this->packer->getMarginLeft() +
$this->packer->getVerticalBorderWidth() + $this->packer->getVerticalBordefOffset();
$y = $img->fit->y + $this->packer->getPageMarginTop() + $this->packer->getMarginTop();
$info_y = $y + $h + $this->packer->getImageInfoMargin();
// Create main and complement images
$image_main = imagecreatefrompng($src);
$image_outline = imagecreatefrompng($src_outline);
$image_invert = imagecreatefrompng($src_invert);
$image_black = imagecreatefrompng($src_black);
list($w_px_original, $h_px_original) = getimagesize($src);
$image_main_resampled = Image::imageCreateTrueColorTransparent($w, $h);
$image_outline_resampled = Image::imageCreateTrueColorTransparent($w, $h);
$image_invert_resampled = Image::imageCreateTrueColorTransparent($w, $h);
$image_black_resampled = Image::imageCreateTrueColorTransparent($w, $h);
// Resample images from original dimension to DPI-based dimension
imagecopyresampled($image_main_resampled, $image_main, 0, 0, 0, 0, $w, $h, $w_px_original, $h_px_original);
imagecopyresampled($image_outline_resampled, $image_outline, 0, 0, 0, 0, $w, $h, $w_px_original, $h_px_original);
imagecopyresampled($image_invert_resampled, $image_invert, 0, 0, 0, 0, $w, $h, $w_px_original, $h_px_original);
imagecopyresampled($image_black_resampled, $image_black, 0, 0, 0, 0, $w, $h, $w_px_original, $h_px_original);
// Add image to all containers
// Parameters are: Destination image, source image, destination starting coordinates (x, y),
// source starting coordinates (x, y), source dimension (width, height).
imagecopy($container_main, $image_main_resampled, $x, $y, 0, 0, $w, $h);
imagecopy($container_outline, $image_outline_resampled, $x, $y, 0, 0, $w, $h);
imagecopy($container_invert, $image_invert_resampled, $x, $y, 0, 0, $w, $h);
imagecopy($container_black, $image_black_resampled, $x, $y, 0, 0, $w, $h);
// Add info to main and outline images
$info = Image::imageDrawTextBordered($w, $info_h, INFO_FONT_SIZE, INFO_BORDER_SIZE, $info_text);
imagecopy($container_main, $info, $x, $info_y, 0, 0, $w, $info_h);
imagecopy($container_outline, $info, $x, $info_y, 0, 0, $w, $info_h);
And Image::imageCreateTrueColorTransparent() is:
/**
* Creates and returns image resource of a true color transparent image.
* #param $width
* #param $height
* #return resource
*/
public static function imageCreateTrueColorTransparent($width, $height) {
$im = imagecreatetruecolor($width, $height);
imagealphablending($im, false);
imagesavealpha($im, true);
$transparent = imagecolorallocatealpha($im, 0, 0, 0, 127);
imagefill($im, 0, 0, $transparent);
return $im;
}
The example of result from my local machine (click to view in original size):
The example of result from the production server (click to view in original size):
I've been doing some research here on Stackoverflow, and I got this two threads who said that the issue was generated by imagecopyresampled() function. Still, I'm not so sure about this, since my app is working flawlessly on my local machine. This is the list of the discussion threads:
imagecopyresampled issue – black border right and bottom …
PHP imagecopyresampled() produces image border on one side
Any help would be appreciated, please elaborate if you know what's causing this and/or you've ever experienced this. Thank you in advance.
This function resizes an image regardless of its format or the presence of alpha channel/transparency.
To avoid the problem due to resampling, a padded version of the original image is created so that the column of pixels farthest to the right and the row of pixels below have sufficient data to display the colors correctly.
The size of the padding is calculated independently for the two axes based on the difference in size between the original image and the resized image.
For example, an image of 256x128 pixels which must be resized to 16x16 pixels requires the following padding:
256 / 16 = 16 columns on the right
128 / 16 = 8 rows on the bottom
this is because the color of each pixel of the resized image will be calculated on a rectangle of 16x8 pixels of the original image (in the simplest case of a bilinear filtering).
/** Resize an image resource.
*
* #param resource $src_image Original image resource.
* #param int $dest_x Destination image x position.
* #param int $dest_y Destination image y position.
* #param int $dest_width Destination width.
* #param int $dest_height Destination height.
* #param int $src_width Source width (can be less than full-width to get a subregion).
* #param int $src_height Source height (can be less than full-height to get a subregion).
* #return false|resource Resized image as resource, false on error.
*/
function resize_resource($src_image, $dest_x, $dest_y, $dest_width, $dest_height, $src_width, $src_height) {
$img_width = imagesx($src_image);
$img_height = imagesy($src_image);
// Create a padded version of source image cloning rows/columns of pixels from last row/column
// to ensure full coverage of right and bottom borders after rescaling.
// Compute padding sizes.
$pad_width = (int)ceil($img_width / $dest_width);
$pad_height = (int)ceil($img_height / $dest_height);
$padded = imagecreatetruecolor($img_width + $pad_width, $img_height + $pad_height);
if ($padded === false) return false;
imagealphablending($padded, false);
$transparent = imagecolorallocatealpha($padded, 0, 0, 0, 127);
imagefill($padded, 0, 0, $transparent);
imagecopy($padded, $src_image, 0, 0, 0, 0, $img_width, $img_height);
// Clone last column.
for ($i = 0; $i < $pad_width; ++$i)
imagecopy($padded, $src_image, $i + $img_width, 0, $img_width - 1, 0, 1, $img_height);
// Clone last row.
for ($i = 0; $i < $pad_height; ++$i)
imagecopy($padded, $src_image, 0, $i + $img_height, 0, $img_height - 1, $img_width, 1);
// Fill remaining padding area on bottom-right with color of bottom-right original image pixel.
$pad_pixel = imagecolorat($padded, $img_width - 1, $img_height - 1);
$pad_color = imagecolorallocatealpha($padded, ($pad_pixel >> 16) & 0xFF,
($pad_pixel >> 8) & 0xFF, $pad_pixel & 0xFF, ($pad_pixel >> 24) & 0x7F);
imagefilledrectangle($padded, $img_width, $img_height,
$img_width + $pad_width - 1, $img_height + $pad_height - 1, $pad_color);
// Create new rescaled image.
$new = imagecreatetruecolor($dest_width, $dest_height);
if ($new === false) return false;
imagealphablending($new, false);
$transparent = imagecolorallocatealpha($new, 0, 0, 0, 127);
imagefill($new, 0, 0, $transparent);
imagecopyresampled($new, $padded, 0, 0, $dest_x, $dest_y, $dest_width, $dest_height, $src_width, $src_height);
return $new;
}
NOTE: for a correct transparency display in the final image it is necessary to correctly use the following code just before writing it to disk:
$transparent = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagecolortransparent ($img, $transparent);
in the case of indexed-color images, or the following code:
imagesavealpha($img, true);
in the case of images with alpha channel. Where $img is the resized image resource returned by the function above.
This method allows you to resample even small images without creating an offset with respect to the original image.

Adding text to images with PHP

I'm running into some issues with adding custom text to an image, what I am try to do is have 3 lines of text that is centered. However the centered text is on the right side of the image, not in the center of the image. This is what I have so far with text added, but I need to have the text aligned center with the line shown on the image.
// Variables
$img = LoadJpeg('img/custom-image.jpg');
$orig_width = imagesx($img);
$orig_height = imagesy($img);
$width = 2500;
$font_path = 'font/ArialBlack.ttf';
$text_line_1 = $_GET['series'];
$text_line_2 = $_GET['custom'];
$text_line_3 = $_GET['model'];
// Calc the new height
$height = (($orig_height * $width) / $orig_width);
// Create new image to display
$new_image = imagecreatetruecolor($width, $height);
// Create some colors
$white = imagecolorallocate($new_image, 255, 255, 255);
// Create new blank image with changed dimensions
imagecopyresized($new_image, $img,0, 0, 0, 0, $width, $height, $orig_width, $orig_height);
// Add text to image
imagettftext($new_image, 13,0, 2150,72, $white, $font_path, $text_line_1);
imagettftext($new_image, 13,0, 2150,92, $white, $font_path, $text_line_2);
imagettftext($new_image, 13,0, 2150,112, $white, $font_path, $text_line_3);
// Print image
imagejpeg($new_image);
imagejpeg($img);
imagedestroy($img);
I'm also wanting to make the width variable from the url, I can do but I'm not sure how to resize the text to match the resized image. Any assistance would be greatly appreciated.
Current Placement of Text
What I am wanting
You want to use imagettfbbox() for this to calculate the horizontal offset:
$bbox = imagettfbbox(13, 0, $font_path, $text_line_1);
$xOffset = ($bbox[2] - $bbox[0]) / 2;
Now just subtract the offset from your desired position and you are good to go.

How to account for font swash with PHP and GD

I have the following code to print text on an image. I am also adding a debug box around the text. However, I noticed the text on the left lies outside of the box that PHP gives me with imagettfbbox.
This looks like an issue with the font swash. Is there anyway to account for this? Can I figure out the distance between the start of the swash and the actual position imagettfbbox gives to me?
I don't think this is an issue with the font, as I tried it with a few script style fonts and the results were similar.
<?php
$font = 'scriptin.ttf';
$text = 'Ipsum';
$size = 30;
$image = imagecreatetruecolor(200, 200);
$fontColour = imagecolorallocate($image, hexdec('11'), hexdec('11'), hexdec('11'));
$bgColour = imagecolorallocate($image, hexdec('CC'), hexdec('CC'), hexdec('CC'));
imagefilledrectangle($image, 0, 0, 200, 200, $bgColour);
$dimensions = imagettfbbox($size, 0, $font, $text);
imagefilledrectangle(
$image,
$dimensions[0] + 40,
$dimensions[7] + 50,
$dimensions[2] + 40,
$dimensions[3] + 50,
imagecolorallocate($image, mt_rand(1, 180), mt_rand(1, 180), mt_rand(1, 180))
);
imagettftext(
$image,
$size,
0,
40,
50,
$fontColour,
$font,
$text
);
header('Content-Type: image/png');
imagepng($image);
The code and font is available here: https://github.com/AydinHassan/image-swash-example
If you point a VHOST at the repository, you can just hit swash.php
Edit: This appears to be fixed in PHP 7.0.12 (bug #53504) so the code below shouldn't be required.
Based on a comment in the PHP manual I've written the following function to calculate and return the difference between where GD thinks the left side of the bounding box is and where the leftmost pixel is found:
function xadjust($size, $angle, $fontfile, $text)
{
$bbox = imagettfbbox($size, $angle, $fontfile, $text);
$width = $bbox[4] - $bbox[6]; // upper right x - upper left x;
$height = $bbox[1] - $bbox[7]; // lower left y - upper left y;
// create an image with height and width doubled to fit any 'swash'.
$im = imagecreatetruecolor($width * 2, $height * 2);
// set background color to opaque black.
imagefill($im, 0, 0, 0x00000000);
// draw the text in opaque white.
imagettftext(
$im,
$size,
0,
$width / 2,
$height,
0x00ffffff,
$fontfile,
$text
);
// set the min-width to its possible maximum.
$min_x = $width * 2;
for ($x = 0; $x < $width * 2; $x++) {
// each x-pixel (horizontal)
for ($y = 0; $y < $height * 2; $y++) {
// each y-pixel (vertical)
if (imagecolorat($im, $x, $y) > 0) {
// non-black pixel found!
$min_x = min($x, $min_x);
}
}
}
imagedestroy($im);
// return the difference between where GD thinks the bounding box is and
// where we found the leftmost non-black pixel.
return (($width / 2) - $min_x) - abs($bbox[0]);
}
This can be integrated to your script fairly easily:
$font = 'scriptin.ttf';
$text = 'Ipsum';
$size = 30;
$image = imagecreatetruecolor(200, 200);
$fontColour = imagecolorallocate($image, hexdec('11'), hexdec('11'), hexdec('11'));
$bgColour = imagecolorallocate($image, hexdec('CC'), hexdec('CC'), hexdec('CC'));
imagefilledrectangle($image, 0, 0, 200, 200, $bgColour);
$xadjust = xadjust($size, 0, $font, $text); // 1. get the adjust value.
$dimensions = imagettfbbox($size, 0, $font, $text);
imagefilledrectangle(
$image,
$dimensions[0] + 40 - $xadjust, // 2. move the left-side of the box to the left.
$dimensions[7] + 50,
$dimensions[2] + 40 - $xadjust, // 3. move the right-side of the box to the left.
$dimensions[3] + 50,
imagecolorallocate($image, mt_rand(1, 180), mt_rand(1, 180), mt_rand(1, 180))
);
imagettftext(
$image,
$size,
0,
40,
50,
$fontColour,
$font,
$text
);
header('Content-Type: image/png');
imagepng($image);
This gives me the following output:
I've run it with a few other fonts and sizes and it seems to be accurate to within 1 pixel.

Cropping an image into Hexagon shape in a web page

I have an image in a web page. I want to convert the four-sided image into a six-sided one. i.e. Crop the edges and convert the image into an hexagon shape.
How can I do it using PHP ImageMagick/GD on my server side. I am using XAMPP server to build a sample web page. Or is there a better way to do it using Javascript/CSS by making use of img style attributes.
It'll be less painful in CSS (you don't even need JS on this).
see this Fiddle http://jsfiddle.net/kizu/bhGn4/
Since I needed to use multiple sizes and caching I needed it in php (well, server-side:)) :
// doge.jpg is a squared pic
$raw = imagecreatefromjpeg('doge.jpg');
/* Simple math here
A_____F
/ \
B/ \E
\ /
C\_____/D
*/
$w = imagesx($raw);
$points = array(
.25 * $w, .067 * $w, // A
0, .5 * $w, // B
.25 * $w, .933 * $w, // C
.75 * $w, .933 * $w, // D
$w, .5 * $w, // E
.75 * $w, .067 * $w // F
);
// Create the mask
$mask = imagecreatetruecolor($w, $w);
imagefilledpolygon($mask, $points, 6, imagecolorallocate($mask, 255, 0, 0));
// Create the new image with a transparent bg
$image = imagecreatetruecolor($w, $w);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
imagealphablending($image, false);
imagesavealpha($image, true);
imagefill($image, 0, 0, $transparent);
// Iterate over the mask's pixels, only copy them when its red.
// Note that you could have semi-transparent colors by simply using the mask's
// red channel as the original color's alpha.
for($x = 0; $x < $w; $x++) {
for ($y=0; $y < $w; $y++) {
$m = imagecolorsforindex($mask, imagecolorat($mask, $x, $y));
if($m['red']) {
$color = imagecolorsforindex($raw, imagecolorat($raw, $x, $y));
imagesetpixel($image, $x, $y, imagecolorallocatealpha($image,
$color['red'], $color['green'],
$color['blue'], $color['alpha']));
}
}
}
// Display the result
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
You should get something similar to this:
You can use HTML5 canvas to mask the edges and then read out the image using a Data URI.
Also be aware that for this technique to work, you'll have to proxy the image to your domain, since Canvas marks its contents dirty if an image is loaded from a foreign domain.
UPDATE: I've added a jsfiddle that demonstrates this technique.

PHP/GD, how to copy a circle from one image to another?

Is there a reasonably straightforward way to copy a circular area from one image resource to another? Something like imagecopymerge except with circles or ovals etc?
If possible, I want to avoid having to use pre-created image files (any oval shape should be possible), and if there's transparency colours involved they should naturally leave the rest of the image alone.
Reason I'm asking, I have a few classes that allow to apply image operations inside a "selected area" of an image, which works by first deleting that area from a copy of the image, then overlaying the copy back on the original. But what if you want to select a rectangle, and then inside that deselect a circle, and have the operations only affect the area that's left?
You could try this:
Start with original image - $img
Copy that image to a png - $copy
Create a mask png image of the area you want in the circle/ellipse (a 'magicpink' image with a black shape on it, with black set to the colour of alpha transparency) - $mask
Copy $mask over the top of $copy maintaining the Alpha transparency
Change what you need to on $copy
Copy $copy back over $img maintaining the Alpha transparency
// 1. Start with the original image
$img = imagecreatefromjpeg("./original.jpg");
$img_magicpink = imagecolorallocatealpha($img, 255, 0, 255, 127);
//imagecolortransparent($img, $img_magicpink);
// (Get its dimensions for copying)
list($w, $h) = getimagesize("./original.jpg");
// 2. Create the first copy
$copy = imagecreatefromjpeg("./original.jpg");
imagealphablending($copy, true);
$copy_magicpink = imagecolorallocate($copy, 255, 0, 255);
imagecolortransparent($copy, $copy_magicpink);
// 3. Create the mask
$mask = imagecreatetruecolor($w, $h);
imagealphablending($mask, true);
// 3-1. Set the masking colours
$mask_black = imagecolorallocate($mask, 0, 0, 0);
$mask_magicpink = imagecolorallocate($mask, 255, 0, 255);
imagecolortransparent($mask, $mask_black);
imagefill($mask, 0, 0, $mask_magicpink);
// 3-2. Draw the circle for the mask
$circle_x = $w/2;
$circle_y = $h/2;
$circle_w = 150;
$circle_h = 150;
imagefilledellipse($mask, $circle_x, $circle_y, $circle_w, $circle_h, $mask_black);
// 4. Copy the mask over the top of the copied image, and apply the mask as an alpha layer
imagecopymerge($copy, $mask, 0, 0, 0, 0, $w, $h, 100);
// 5. Do what you need to do to the image area
// My example is turning the original image gray and leaving the masked area as colour
$x = imagesx($img);
$y = imagesy($img);
$gray = imagecreatetruecolor($x, $y);
imagecolorallocate($gray, 0, 0, 0);
for ($i = 0; $i > 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
//for gray mode $r = $g = $b
$color = max(array($r, $g, $b));
$gray_color = imagecolorexact($img, $color, $color, $color);
imagesetpixel($gray, $i, $j, $gray_color);
}
}
// 6. Merge the copy with the origianl - maintaining alpha
imagecopymergegray($gray, $copy, 0, 0, 0, 0, $w, $h, 100);
imagealphablending($gray, true);
imagecolortransparent($gray, $mask_magicpink);
header('Content-Type: image/png');
imagepng($gray);
imagedestroy($gray);

Categories