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.
Related
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.
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;
}
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);
I was intrigued by this discussion of image scaling and subsequently found out that the PHP code I'm using to create thumbnails from uploaded images suffers from the same problem. I decided to try the PHP fix posted near the bottom (converting gamma from 2.2 to 1.0, resizing the image, converting gamma back from 1.0 to 2.2). This works to solve the issue noted in the article, however this modification to the code has the unfortunate side effect of knocking out PNG alpha channel transparency.
Here is the code I have with the gamma correction in place.
<?php
$image = imagecreatefrompng($source_file);
$resized_image = imagecreatetruecolor($new_width, $new_height);
imagealphablending($resized_image, false);
imagesavealpha($resized_image, true);
imagegammacorrect($image, 2.2, 1.0);
imagecopyresampled($resized_image, $image, 0, 0, 0, 0, $new_width, $new_height, $image_width, $image_height);
imagegammacorrect($resized_image, 1.0, 2.2);
imagepng($resized_image, $dest_file);
?>
Anyone know how to resize the image, employing the gamma correction trick, while maintaining the alpha channel transparency of the original image?
Edit
sample images:
original file - PNG with alpha channel transparency
resized file with both imagegammacorrect() function calls commented out
resized file with both imagegammacorrect() function calls in place
You can see that the transparency is fine until you attempt to correct the gamma. (easiest way to see the transparency is working below is to inspect the paragraph tag wrapped around the images and add a background: black; to its style attribute via FireBug or similar.)
original image http://ender.hosting.emarketsouth.com/images/test-image.png
no gamma correction http://ender.hosting.emarketsouth.com/images/test-image-resized-no-gamma.png
gamma corrected - no transparency http://ender.hosting.emarketsouth.com/images/test-image-resized.png
Here's some code that does work. Basically, it separates out the alpha channel, resizes the image using gamma correct, resizes the alpha channel without gamma correct, then copies over the alpha channel to the resized image that was done with gamma correct.
My guess is that the imagegammacorrect() function has a bug. Perhaps gamma only applies to RGB and GD tries to do the same calculation to the alpha channel as well? Color theory is not my forte.
Anyway, here's the code. Unfortunately I could not find a better way to separate out the channels than to loop over the pixels one-by-one. Oh well...
<?php
// Load image
$image = imagecreatefrompng('test-image.png');
// Create destination
$resized_image = imagecreatetruecolor(100, 100);
imagealphablending($resized_image, false); // Overwrite alpha
imagesavealpha($resized_image, true);
// Create a separate alpha channel
$alpha_image = imagecreatetruecolor(200, 200);
imagealphablending($alpha_image, false); // Overwrite alpha
imagesavealpha($alpha_image, true);
for ($x = 0; $x < 200; $x++) {
for ($y = 0; $y < 200; $y++) {
$alpha = (imagecolorat($image, $x, $y) >> 24) & 0xFF;
$color = imagecolorallocatealpha($alpha_image, 0, 0, 0, $alpha);
imagesetpixel($alpha_image, $x, $y, $color);
}
}
// Resize image to destination, using gamma correction
imagegammacorrect($image, 2.2, 1.0);
imagecopyresampled($resized_image, $image, 0, 0, 0, 0, 100, 100, 200, 200);
imagegammacorrect($resized_image, 1.0, 2.2);
// Resize alpha channel
$alpha_resized_image = imagecreatetruecolor(200, 200);
imagealphablending($alpha_resized_image, false);
imagesavealpha($alpha_resized_image, true);
imagecopyresampled($alpha_resized_image, $alpha_image, 0, 0, 0, 0, 100, 100, 200, 200);
// Copy alpha channel back to resized image
for ($x = 0; $x < 100; $x++) {
for ($y = 0; $y < 100; $y++) {
$alpha = (imagecolorat($alpha_resized_image, $x, $y) >> 24) & 0xFF;
$rgb = imagecolorat($resized_image, $x, $y);
$r = ($rgb >> 16 ) & 0xFF;
$g = ($rgb >> 8 ) & 0xFF;
$b = $rgb & 0xFF;
$color = imagecolorallocatealpha($resized_image, $r, $g, $b, $alpha);
imagesetpixel($resized_image, $x, $y, $color);
}
}
imagepng($resized_image, 'test-image-scaled.png');
?>
Replace hard-coded values with variables of course... And here's the result I get using your image and my code:
(source: jejik.com)
There is a problem with imagecopyresampled() and transparency. Take a look at this comment on php.net for a possible solution.
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);