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

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

Related

How to cut a part of the image and paste it to another using PHP? [duplicate]

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

Php IMG_FILTER_GRAYSCALE converting transparent pixels to black

I am trying to get my PNGs converted into grayscale and it almost works properly with this code:
$image = imagecreatefromstring(file_get_contents($this->image_dest."".$this->file_name));
imagefilter($image, IMG_FILTER_GRAYSCALE);
imagepng($image, $this->image_dest."".$this->file_name);
The problem is, when the image has some transparency, the transparent pixels are being rendered as black. I see there are others who have had the same question in part of their question, but it was never answered about this issue specifically.
I hope someone can help out with this!
If it helps, I was previously using this snippet to convert to greyscale, but it has the same problem with the transparent pixels in pngs being converted to black and I am not sure
how to detect the transparency and convert it using the imagecolorat function.
//Creates the 256 color palette
for ($c=0;$c<256;$c++){
$palette[$c] = imagecolorallocate($new,$c,$c,$c);
}
//Creates yiq function
function yiq($r,$g,$b){
return (($r*0.299)+($g*0.587)+($b*0.114));
}
//Reads the origonal colors pixel by pixel
for ($y=0;$y<$h;$y++) {
for ($x=0;$x<$w;$x++) {
$rgb = imagecolorat($new,$x,$y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
//This is where we actually use yiq to modify our rbg values, and then convert them to our grayscale palette
$gs = yiq($r,$g,$b);
imagesetpixel($new,$x,$y,$palette[$gs]);
}
}
Okay, this was mostly borrowed.. Don't quite remember where, but it should work:
//$im is your image with the transparent background
$width = imagesx($im);
$height = imagesy($im);
//Make your white background to overlay the original image on ($im)
$bg = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($bg, 255, 255, 255);
//Fill it with white
imagefill($bg, 0, 0, $white);
//Merge the two together
imagecopyresampled($bg, $im, 0, 0, 0, 0, $width, $height, $width, $height);
//Convert to gray-scale
imagefilter($bg, IMG_FILTER_GRAYSCALE);
Hope that helps!

Replace transparent color in PHP

I have set of images. In this images exist green alpha color. I need replace that transparent color to another color (i want replace to white alpha color).
Its my code:
$img = imagecreatefrompng($path . $file);
$white_color_transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
for ($y = 0; $y < imagesy($img); $y++) {
for ($x = 0; $x < imagesx($img); $x++) {
$rgb = imagecolorat($img, $x, $y);
$pixel_color = imagecolorsforindex($img, $rgb);
if ($pixel_color['alpha'] != 0 && $pixel_color['alpha'] != 127){
imagesetpixel($img, $x, $y, $white_color_transparent);
}
}
}
imagealphablending($img, false);
imagesavealpha($img, true);
imagepng($img, $path . $file);
And my result color its the same than id source image, when i add before
imagesetpixel($img, $x, $y, $white_color_transparent);
that line:
imagesetpixel($img, $x, $y, $white_color);
i get only white color without transparency.
Without seeing your image, I'm not sure there's an easy way to do it.
EDIT 2:
One thing I noticed from my first edit is that it didn't work for an image with transparency and had no opaque layer behind it.
Here is my attempt to deal with an image that has some transparency. As you can see by the example. It didn't work out perfectly.
<?php
//These colors here are the rgba values of the green transparency I used in my test image. You will need to know this to reverse the rgb blending.
$red = 28;
$green = 198;
$blue = 72;
$alpha = .48;
$img = imagecreatefrompng('test.png');
//Adding in the image blending that Zombaya brought up. Need this so it overwrites the pixel instead of blending it
imagealphablending($img, false);
for ($y = 0; $y < imagesy($img); $y++) {
for ($x = 0; $x < imagesx($img); $x++) {
$rgb = imagecolorat($img, $x, $y);
$pixel_color = imagecolorsforindex($img, $rgb);
//If pixel color matches our alpha layer color, we change it white transparent
//Not sure how accurate the rounding for the alpha conversion is. Maybe the reason for the green edges in this example:
if($pixel_color['red'] == $red &&
$pixel_color['green'] == $green &&
$pixel_color['blue'] == $blue &&
$pixel_color['alpha'] == round(127 - ($alpha * 127))){
$color = imagecolorallocatealpha($img, 255, 255, 255, 127);
} else {
//This is the algorithm from the link. But reordered to get the old color before the transparent color went over it.
$oldR = ($pixel_color['red'] - $alpha * $red) / (1 - $alpha);
$oldG = ($pixel_color['green'] - $alpha * $green ) / (1 - $alpha);
$oldB = ($pixel_color['blue'] - $alpha * $blue) / (1 - $alpha);
$color = imagecolorallocatealpha($img, $oldR, $oldG, $oldB, $pixel_color['alpha']);
}
imagesetpixel($img, $x, $y, $color);
}
}
imagesavealpha($img, true);
imagepng($img, 'test_result.png');
-> Original without transparency
-> Starting image with transparency
-> Resulting image. Notice the green edges. And I may have got lucky with using similar opacity for the red and green. The white part is transparent
EDIT:
I thought about this some more and I came across this link: How to calculate an RGB colour by specifying an alpha blending amount?
This helped me understand how to fix your problem. Although this is dependent on if you have just a solid color that is transparent over the image. If it is a gradient it is more difficult.
<?php
//These colors here are the rgba values of the green transparency I used in my test image. You will need to know this to reverse the rgb blending.
$red = 44;
$green = 215;
$blue = 56;
$alpha = .45;
$img = imagecreatefrompng('test2.png');
for ($y = 0; $y < imagesy($img); $y++) {
for ($x = 0; $x < imagesx($img); $x++) {
$rgb = imagecolorat($img, $x, $y);
$pixel_color = imagecolorsforindex($img, $rgb);
//This is the algorithm from the link. But reordered to get the old color before the transparent color went over it.
$oldR = ($pixel_color['red'] - $alpha * $red) / (1 - $alpha);
$oldG = ($pixel_color['green'] - $alpha * $green ) / (1 - $alpha);
$oldB = ($pixel_color['blue'] - $alpha * $blue) / (1 - $alpha);
$color = imagecolorallocate($img, $oldR, $oldG, $oldB);
imagesetpixel($img, $x, $y, $color);
}
}
imagesavealpha($img, true);
imagepng($img, 'test_result.png');
- Starting Image
- After Image
Here is an explanation of the rgb calculation used in the code above. The link shows the basic formula:
out = alpha * new + (1 - alpha) * old
out is the resulting color after mixing the original with the new color.
So we need to rearrange the formula to get the old color, which yields this:
old = (out - alpha * new) / (1 - alpha)
OLD ANSWER:
Why this line doesn't work:
$white_color_transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
127 means fully transparent, so that means it is invisible. Which is why the result image is the same as the starting image.
Why this line creates a fully white image:
$white_color = imagecolorallocatealpha($img, 255, 255, 255, 0);
or
$white_color = imagecolorallocate($img, 255, 255, 255);
This basically just places a white pixel over the selected pixel (0 means no transparency), which is why the selected pixels become fully white.
Now if you do the following:
$white_color_semi_transparent = imagecolorallocatealpha($img, 255, 255, 255, 40);
Instead of replace the transparent green with a transparent white, you know get a lighter green instead of a semi transparent white.
See my test example:
- Starting Image
- Resulting Image with color 255,255,255,40
So this tells us two things:
First is that colors are basically being applied on top of the existing pixel's color, hence the lighter transparent green instead of a transparent white.
Second, notice that the brown part is not affected. The brown part was actually an opaque red color beneath the green opacity layer. What this says is that the image doesn't take into account layers like photoshop and basically reads the brown color as brown and not a red color behind a green transparent color. And that makes sense since PNG files don't come with layer info.
I'm not sure how you'd solve your issue. It would depend on your image. I think it's possible to get the coloring you want, but it obviously the more complex your image is the more difficult it will be. For example, my two overlapped squares with two colors versus say a photo.
I used Gohn67's image as something to work width.
The trick is to turn off alpha blending before trying to transform your image. This way you can use imagesetpixel() to set the pixel to the exact value you want.
As from the manual on imagealphablending:
In non-blending mode, the drawing color is copied literally with its
alpha channel information, replacing the destination pixel.
This resulted in this code:
$img = imagecreatefrompng($path.$file);
imagealphablending($img, false); // Turn off blending
$white_color_transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
for ($y = 0; $y < imagesy($img); $y++) {
for ($x = 0; $x < imagesx($img); $x++) {
$rgb = imagecolorat($img, $x, $y);
$pixel_color = imagecolorsforindex($img, $rgb);
if ($pixel_color['alpha'] != 0 && $pixel_color['alpha'] != 127){
imagesetpixel($img, $x, $y, $white_color_transparent);
}
}
}
imagesavealpha($img, true);
imagepng($img, $path.$file);
Original image
Resulting image
(The background is actually transparent, but impossible to see here)
I would like to say that right now, it replaces all transparent colors with this totally transparent white. If you would prefer to replace all colored transparency with a transparent white with the same alpha-value, you would do something like this:
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, 255, 255, 255, $pixel_color['alpha']));
And to select only the green transparent pixels, I would calculate the hue of each pixel and check if it falls in a specified range.

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 GD2: how to maintain alpha channel transparency and correct gamma

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.

Categories