draw multiple circles with imagemagick - php

I am trying to draw multiple circles on a background image, but im having a hard time figuring out how to do it.
Ive tried passing multiple imageMagick instances to the draw function but did not work.
So then I tried creating a new image object, and setting it to transparent.
Then i tried the drawing an image on top of that, but i cant get it to adjust the opacity at all.
Along the lines of what i am trying to do is this:
<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL | E_STRICT);
function drawImage(Imagick $im) {
// $im->setCompressionQuality(100);
$im->setImageFormat("jpg");
header("Content-Type: image/" . $im->getImageFormat());
echo $im;
exit;
}
// define circle mask
$layer = new Imagick('spc.jpg');
//now we need the height and width.
$width = $layer->getImageWidth();
$height = $layer->getImageHeight();
$x = $width/2;
$y = $height/2;
$endX = $x + 150;
$endY = $y + 150;
$circle = new ImagickDraw();
$circle->setFillColor("#FFFF00");
$circle->circle($x, $y, $endX, $endY);
//$layer->drawImage($circle, $circle2);
$circle2 = new ImagickDraw();
$circle2->setFillColor("#0000FF");
$circle2->circle(0, 0, $x, $y);
$layer->drawImage($circle, $circle2);
drawImage($layer);
?>

drawImage shoud get only 1 parameter. try this at the final lines:
$layer->drawImage($circle);
$layer->drawImage($circle2);
drawImage($layer);

I think/hope this is nearer to what you are aiming for... I have hacked around a bit because my environment is different from yours - but you should be able to see what I have done.
The main difference is that you need to set the fill colour to transparent and the stroke colour to whatever colour you want the circle outlines to be drawn in. Also, I have reused your Draw object rather than creating additional ones for each circle.
#!/usr/local/bin/php
<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL | E_STRICT);
function drawImage(Imagick $im) {
$im->setImageFormat("jpg");
$im->writeImage('out.jpg');
exit;
}
// define circle mask
$layer = new Imagick();
$layer->newImage(500,250,new ImagickPixel('white'));
$layer->borderImage('#6000ff',3,3);
//now we need the height and width.
$width = $layer->getImageWidth();
$height = $layer->getImageHeight();
$x = $width/2;
$y = $height/2;
$circle = new ImagickDraw();
$circle->setFillColor('transparent'); // transparent
$circle->setStrokeColor("#0000FF"); // blue
$circle->circle($x, $y, $x + 25, $y);
$layer->drawImage($circle);
$circle->setStrokeColor("#00FF00"); // green
$circle->circle($x, $y, $x + 50, $y);
$layer->drawImage($circle);
$circle->setStrokeColor("#FF00FF"); // purple
$circle->circle($x, $y, $x +75, $y);
$layer->drawImage($circle);
$circle->setStrokeColor("#FF0000"); // red
$circle->circle($x, $y, $x +100, $y);
$layer->drawImage($circle);
drawImage($layer);
?>

Related

Add colour overlay to non transparent part of a PNG

I am trying to accomplish having a colour wheel and it will add an overlay to a PNG image with a 50% opacity so you can still see the outlines of the image behind.
I have tried using intervention fill methods and changing it on a per-pixel basis.
private function colorImage($url, $r = null, $g = null, $b = null) {
$im = imagecreatefrompng($url);
imageAlphaBlending($im, true);
imageSaveAlpha($im, true);
if (imageistruecolor($im)) {
$sx = imagesx($im);
$sy = imagesy($im);
for ($x = 0; $x < $sx; $x++) {
for ($y = 0; $y < $sy; $y++) {
$c = imagecolorat($im, $x, $y);
$a = $c & 0xFF000000;
$newColor = $a | $r << 16 | $g << 8 | $b;
imagesetpixel($im, $x, $y, $newColor);
}
}
}
ob_start();
imagepng($im);
imagedestroy($im);
$image_data = ob_get_contents();
ob_end_clean();
$image_data_base64 = "data:image/png;base64," . base64_encode($image_data);
return $image_data_base64;
}
Before: https://imgur.com/v1xRixQ
Current: https://imgur.com/67M9iCg
Objective: https://imgur.com/RxzaxTP
You can use the Intervention Image image library to do this.
Create an instance that is an image filled with your semi-transparent RGB color, mask it off using the source image, and lay the mask on top of the original as an overlay.
use Intervention\Image\ImageManager;
// setup
$red = 255;
$green = 0;
$blue = 0;
$opacity = 0.25;
$manager = new ImageManager(['driver' => 'gd']);
// the source image
$image = $manager->make('./board.png');
// the fill color
$fill = $manager->canvas(
$image->width(),
$image->height(),
[$red, $green, $blue, $opacity]
);
// use the source image to mask off only the portion of the fill that will be
// used as the overlay
$fill->mask($image, true);
// apply the semi-transparent, masked fill to the source image
$image->insert($fill);
// render as a png
header('Content-Type: image/png');
echo $image->encode('png');
Output: https://imgur.com/oIRP8Ir

Create round png image in PHP using transparency and center a letter in it

I'd like to create profile images for my website. The image should be rounded, a solid background color and a single big letter exactly at the center.
So far I've been able to create a square png image and a letter in it but it's not centered. Due to the fact that different letters have different width I get some letters slightly off-center.
I'm going to implement"round" png images in PHP using transparency.
This is the complete script and should work out of the box. You need Arial.tff if you want to use a custom font but it's not strictly needed.
There is a lookup table with some colors. The single letter get hashed (yeah, pretty stupid I know, but this way it's more flexible as I could pass any string I like) and a color is picked.
<?php
$text = "a";
$posx = 40;
$posy = 150;
$size = 120;
$displaytext = strtoupper($text);
$image = imagecreatetruecolor(200, 200);
// pick a background at random
$binhash = md5($text, true);
$numhash = unpack('N2', $binhash);
$index = abs($numhash[1]) % 16;
$palette = array(
array(0xde,0x5a,0xa4),
array(0xae,0xc6,0xcf),
array(0x96,0x6f,0xd6),
array(0xff,0xb3,0x47),
array(0xff,0x69,0x61),
array(0x77,0xdd,0x77),
array(0x03,0xc0,0x3c),
array(0xcf,0xcf,0xc4),
array(0xc2,0x3b,0x22),
array(0xfd,0xfd,0x96),
array(0x83,0x69,0x53),
array(0x77,0x9e,0xcb),
array(0xb1,0x9c,0xd9),
array(0xb3,0x9e,0xb5),
array(0xf4,0x9a,0xc2),
array(0xff,0xd1,0xdc)
);
$r = $palette[$index][0];
$g = $palette[$index][1];
$b = $palette[$index][2];
$image = imageCreate(200, 200);
imageColorAllocate($image, $r, $g, $b);
$color = imageColorAllocate($image, 255, 255, 255);
$font = 'arial.ttf';
$box = imagettfbbox(200, 0, "ARIAL", $displaytext);
$width = abs($box[2] - $box[0]);
$height = abs($box[5] - $box[1]);
$image = imageCreate(200, 200);
imageColorAllocate($image, $r, $g, $b);
$color = imageColorAllocate($image, 255, 255, 255);
$font = 'arial.ttf';
imagettftext($image, 200, 0, 0 + (200/2 - floor($width/2)), 200, $color, "ARIAL", $displaytext);
header("content-type: image/png");
imagepng($image);
imagedestroy($image);
?>
How can I modify such code in order to:
Have round corner. Shall I create a transparent background and a solid disc at the center?
Have the letter centered, no matter which letter it is
This can help you for centering the letter
<?php
$im = new Imagick(); /* Create a new Imagick object */
$draw = new ImagickDraw(); /* Create an ImagickDraw object */
$draw->setFont('/path/to/font.ttf'); /* Set the font */
var_dump($im->queryFontMetrics($draw, "K")); /* Dump the font metrics*/
?>
Using fontmetrics you can calculate the width and height of a specific letter/text with specific font and you can access the properties.
Hope that will help you.

Watermarking: Display the same text on multiple parts of the image

I want to add a text to a image. The text should be displayed in multiple areas of the image (not just one).
For example I want to watermark with a text stack. Stack should be displayed in the image at least 8 times in different areas in the image.
I just learned about imagestring() and imagettftext(), but these two only displays my text on a single spot.
Image is not fixed size, so i cannot specify exact and multiple location in advance. It should work on all sizes of images
<?php
/*
image.php
*/
header("Content-type: image/jpeg");
$imgPath = 'olximage.jpg';
$image = imagecreatefromjpeg($imgPath);
$color = imagecolorallocate($image, 255, 255, 255);
$string = "stack overflow";
$fontSize = 3;
$x = 15;
$y = 185;
imagestring($image, $fontSize, $x, $y, $string, $color);
$x = 15;
$y = 175;
imagestring($image, $fontSize, $x, $y, $string, $color);
imagejpeg($image);
?>
Thanks in advance
For example:
<?php
/*
image.php
*/
header("Content-type: image/jpeg");
$imgPath = 'olximage.jpg';
$image = imagecreatefromjpeg($imgPath);
$color = imagecolorallocate($image, 255, 255, 255);
$string = "stack overflow";
$fontSize = 3;
$imageHeight = imagesy($image);
$distanceY = 10;
$maxImageStrings = max(8, $imageHeight / $distanceY);
$x = 15;
for ($i = 0; $i < $maxImageStrings; $i++) {
$y = $i * $distanceY;
imagestring($image, $fontSize, $x, $y, $string, $color);
}
imagejpeg($image);
You can finetune calculations for your needs.
I'm using Imagick extension for same. If you want to go with this then follow detail:
PHP:
// Create objects
$image = new Imagick('image.png');
$watermark = new Imagick();
// Watermark text
$text = 'Copyright';
// Create a new drawing palette
$draw = new ImagickDraw();
$watermark->newImage(140, 80, new ImagickPixel('none'));
// Set font properties
$draw->setFont('Arial');
$draw->setFillColor('grey');
$draw->setFillOpacity(.5);
// Position text at the top left of the watermark
$draw->setGravity(Imagick::GRAVITY_NORTHWEST);
// Draw text on the watermark
$watermark->annotateImage($draw, 10, 10, 0, $text);
// Position text at the bottom right of the watermark
$draw->setGravity(Imagick::GRAVITY_SOUTHEAST);
// Draw text on the watermark
$watermark->annotateImage($draw, 5, 15, 0, $text);
// Repeatedly overlay watermark on image
for ($w = 0; $w < $image->getImageWidth(); $w += 140) {
for ($h = 0; $h < $image->getImageHeight(); $h += 80) {
$image->compositeImage($watermark, Imagick::COMPOSITE_OVER, $w, $h);
}
}
// Set output image format
$image->setImageFormat('png');
// Output the new image
header('Content-type: image/png');
echo $image;
although there are plenty of command-line examples to be found on the ImageMagick website, so that is where we shall begin.

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.

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