Reputation: 31290
What is the best way to crop or mask an image into a circular shape, using either ImageMagick or GD libraries? (Note, solution exists on "other" Q&A sites, but not StackOverflow)
Upvotes: 18
Views: 24339
Reputation: 84
For those who are using intervention/image
v3 in Laravel since the mask
method has been removed, we can use a custom class to do the job.
(Note:- Raw php developers can also use this class provided that GD extension is enabled)
<?php
namespace App\Helpers\Image;
class CircleImage
{
public $img;
public $width;
public $height;
public $minSize;
public function __construct($img = null)
{
if (!empty($img)) {
$this->img = imagecreatefromstring($img);
$this->width = imagesx($this->img); // Image original width
$this->height = imagesy($this->img); // Image original height
// Get the minimum size
// This will help cut the image in a circular shape
$this->minSize = min($this->width, $this->height);
}
}
public function make(): string
{
$radius = $this->minSize / 2;
// First we crop the image from center
$cropped = imagecrop($this->img, [
// Calculation summary:
// here width/2 gives us the center point, suppose our image width is 200,
// Then the center point is 100, now we want our cropping to start
// at negative $radius from 100 and end at positive $radius from 100,
// So that it crops from the center part into a square with the
// Diameter same as minSize (Same for height)
"x" => $this->width / 2 - $radius,
"y" => $this->height / 2 - $radius,
"width" => $this->minSize, // Diameter
"height" => $this->minSize, // Diameter
]);
if ($cropped !== false) { // in case a new image object was returned
imagedestroy($this->img); // we destroy the original image
$this->img = $cropped; // and assign the cropped image
} else {
throw new \Exception("Failed to crop the image!", 500);
}
// Now create the circular mask
$mask = imagecreatetruecolor($this->minSize, $this->minSize);
$black = imagecolorallocate($mask, 0, 0, 0);
$magenta = imagecolorallocate($mask, 255, 0, 255);
// Fill with magenta color
imagefill($mask, 0, 0, $magenta);
// Create a black circle in the center
imagefilledellipse(
$mask,
$radius,
$radius,
$this->minSize,
$this->minSize,
$black
);
// Now make the black circle part transparent
imagecolortransparent($mask, $black);
// Merge the two images so that only the transparent
// part shows the image and other parts become magenta solid
imagecopymerge(
$this->img,
$mask,
0,
0,
0,
0,
$this->minSize,
$this->minSize,
100
);
// Now make the magenta part transparent
// It now only keeps the center transparent(prev=black) part
// of the original image visible
imagecolortransparent($this->img, $magenta);
// Destroy the mask
imagedestroy($mask);
return $this->render();
}
public function render(): string
{
// Get the string content
// Of the generated image & return
ob_start();
imagepng($this->img);
$imagedata = ob_get_clean();
return $imagedata;
}
}
###Usage:-
// First we scale the image to a smaller size, otherwise the circular crop may time out and get the string value
$image = InterventionImage::read($image)->scale(300, null)->encodeByMediaType(type: "image/png", quality: 90)->toString();
// Now simply Crop the image as a circle with our helper class
$circleImage = new CircleImage($image);
$image = $circleImage->make();
// It returns the png string image content, either convert to base64 or simply put to the storage Like below:-
// Storage: Storage::put("picture/avatar.png", $image);
// base64_encode($image);
Upvotes: 0
Reputation: 760
Here is solution based on masks with GD
called imageRemoveOuterCircle
:
// From https://stackoverflow.com/a/23215738/2590508
function hexColorAllocate($im,$hex){
$hex = ltrim($hex,'#');
$r = hexdec(substr($hex,0,2));
$g = hexdec(substr($hex,2,2));
$b = hexdec(substr($hex,4,2));
return imagecolorallocate($im, $r, $g, $b);
}
function imageRemoveOuterCircle(&$image,$width=null,$height=null){
// 2 arbitrary colors for transparency ; can be redefined if needed
$transparentColor1="8d5ca4";
$transparentColor2="8d5ca5";
if(is_null($width)){
$width=imagesx($image);
}
if(is_null($height)){
$height=imagesy($image);
}
$mask=imagecreatetruecolor($width, $height);
imagefilledrectangle(
$mask,
0,
0,
$width,
$height,
hexColorAllocate($mask,$transparentColor1)
);
imagefilledellipse(
$mask,
$width/2,
$height/2,
$width,
$height,
hexColorAllocate($mask,$transparentColor2)
);
imagecolortransparent($mask,hexColorAllocate($mask,$transparentColor2));
imagecopy(
$image,
$mask,
0,
0,
0,
0,
$width,
$height
);
imagedestroy($mask);
imagecolortransparent($image,hexColorAllocate($image,$transparentColor1));
}
Upvotes: 0
Reputation: 444
You can use this function if you want to circular the corners of an image. To make the circle image, you need to set $Radius to 50%; If the $source_url is a square, the output will be the circle, otherwise it will be oval.The output will be a PNG file with transparent background.
public function Crop_ByRadius($source_url,$destination_url="",$Radius="0px" ,$Keep_SourceFile = TRUE){
/*
Output File is png, Because for crop we need transparent color
if success :: this function returns url of Created File
if Fial :: returns FALSE
$Radius Input Examples ::
100 => 100px
100px => 100px
50% => 50%
*/
if( !file_exists($source_url) || $Radius == NULL )
return FALSE;
if( $destination_url == NULL || $destination_url == "" ) $destination_url = $source_url;
$PathInfo = pathinfo($destination_url);
$destination_url = $PathInfo['dirname'].DIRECTORY_SEPARATOR.$PathInfo['filename'].".png";
$ImageInfo = getimagesize($source_url);
$w = $ImageInfo[0];
$h = $ImageInfo[1];
$mime = $ImageInfo['mime'];
if( $mime != "image/jpeg" && $mime != "image/jpg" && $mime != "image/png")
return FALSE;
if( strpos($Radius,"%") !== FALSE ){
//$Radius by Cent
$Radius = intval( str_replace("%","",$Radius) );
$Smallest_Side = $w <= $h ? $w : $h;
$Radius = $Smallest_Side * $Radius / 100;
}else{
$Radius = strtolower($Radius);
$Radius = str_replace("px","",$Radius);
}
$Radius = is_numeric($Radius) ? intval($Radius) : 0;
if( $Radius == 0 ) return FALSE;
$src = imagecreatefromstring(file_get_contents($source_url));
$newpic = imagecreatetruecolor($w,$h);
imagealphablending($newpic,false);
$transparent = imagecolorallocatealpha($newpic, 0, 0, 0, 127);
//$transparent = imagecolorallocatealpha($newpic, 255, 0, 0, 0);//RED For Test
$r = $Radius / 2;
/********************** Pixel step config ********************************/
$Pixel_Step_def = 0.4;//smaller step take longer time! if set $Pixel_Step=0.1 result is better than $Pixel_Step=1 but it take longer time!
//We select the pixels we are sure are in range, to Take up the bigger steps and shorten the processing time
$Sure_x_Start = $Radius +1;
$Sure_x_End = $w - $Radius -1;
$Sure_y_Start = $Radius +1;
$Sure_y_End = $h - $Radius -1;
if( $w <= $h ){
//We want to use the larger side to make processing shorter
$Use_x_Sure = FALSE;
$Use_y_Sure = TRUE;
}else{
$Use_x_Sure = TRUE;
$Use_y_Sure = FALSE;
}
/********************** Pixel step config END********************************/
$Pixel_Step = $Pixel_Step_def;
for( $x=0; $x < $w ; $x+=$Pixel_Step ){
if( $Use_x_Sure && $x > $Sure_x_Start && $x < $Sure_x_End ) $Pixel_Step = 1;else $Pixel_Step = $Pixel_Step_def;
for( $y=0; $y < $h ; $y+=$Pixel_Step){
if( $Use_y_Sure && $y > $Sure_y_Start && $y < $Sure_y_End ) $Pixel_Step = 1;else $Pixel_Step = $Pixel_Step_def;
$c = imagecolorat($src,$x,$y);
$_x = ($x - $Radius) /2;
$_y = ($y - $Radius) /2;
$Inner_Circle = ( ( ($_x*$_x) + ($_y*$_y) ) < ($r*$r) );
$top_Left = ($x > $Radius || $y > $Radius) || $Inner_Circle;
$_x = ($x - $Radius) /2 - ($w/2 - $Radius);
$_y = ($y - $Radius) /2;
$Inner_Circle = ( ( ($_x*$_x) + ($_y*$_y) ) < ($r*$r) );
$top_Right = ($x < ($w - $Radius) || $y > $Radius) || $Inner_Circle;
$_x = ($x - $Radius) /2;
$_y = ($y - $Radius) /2 - ($h/2 - $Radius);
$Inner_Circle = ( ( ($_x*$_x) + ($_y*$_y) ) < ($r*$r) );
$Bottom_Left = ($x > $Radius || $y < ($h - $Radius) ) || $Inner_Circle;
$_x = ($x - $Radius) /2 - ($w/2 - $Radius);
$_y = ($y - $Radius) /2 - ($h/2 - $Radius);
$Inner_Circle = ( ( ($_x*$_x) + ($_y*$_y) ) < ($r*$r) );
$Bottom_Right = ($x < ($w - $Radius) || $y < ($h - $Radius) ) || $Inner_Circle;
if($top_Left && $top_Right && $Bottom_Left && $Bottom_Right ){
imagesetpixel($newpic,$x,$y,$c);
}else{
imagesetpixel($newpic,$x,$y,$transparent);
}
}
}
if( !$Keep_SourceFile && $source_url != $destination_url){
unlink($source_url);
}
imagesavealpha($newpic, true);
imagepng($newpic, $destination_url);
imagedestroy($newpic);
imagedestroy($src);
return $destination_url;
}
Upvotes: 0
Reputation: 558
For those who wants a solution in PHP, providing the picture already cropped into a circle:
// convert the picture
$w = 640; $h=480; // original size
$original_path="/location/of/your/original-picture.jpg";
$dest_path="/location/of/your/picture-crop-transp.png";
$src = imagecreatefromstring(file_get_contents($original_path));
$newpic = imagecreatetruecolor($w,$h);
imagealphablending($newpic,false);
$transparent = imagecolorallocatealpha($newpic, 0, 0, 0, 127);
$r=$w/2;
for($x=0;$x<$w;$x++)
for($y=0;$y<$h;$y++){
$c = imagecolorat($src,$x,$y);
$_x = $x - $w/2;
$_y = $y - $h/2;
if((($_x*$_x) + ($_y*$_y)) < ($r*$r)){
imagesetpixel($newpic,$x,$y,$c);
}else{
imagesetpixel($newpic,$x,$y,$transparent);
}
}
imagesavealpha($newpic, true);
imagepng($newpic, $dest_path);
imagedestroy($newpic);
imagedestroy($src);
Upvotes: 2
Reputation: 22964
For those who want a node/js based solution, you can create a circular crop at specified coordinates using node-gm like this:
gm(original)
.crop(233, 233,29,26)
.resize(size, size)
.write(output, function(err) {
gm(size, size, 'none')
.fill(output)
.drawCircle(size/2,size/2, size/2, 0)
.write(output, function(err) {
console.log(err || 'done');
});
});
You can use a lib like JCrop (demo) to allow the user to crop the image on the front-end and pass the coordinates (w,h,x,y) into crop().
Upvotes: 0
Reputation: 2077
Here is one way with ImageMagick that will acomplish this without using a mask:
convert -size 200x200 xc:none -fill walter.jpg -draw "circle 100,100 100,1" circle_thumb.png
Upvotes: 52
Reputation: 529
For those who need to do this in pure PHP using Imagick, you will need to refer to this question : circularize an image with imagick
Hope this will help.
J.
Upvotes: 2