I'm using imagecopyresampled to resize (shrink) an image, which happens to be a gif. The image contains text which, when resized, is quite blurry. I wouldn't necessarily mind that, but when displaying the original image on a web page, at the reduced size, my browser scales it down with much nicer results. Any idea what I can do to improve what PHP produces?
UPDATE: Here's an example of the code I'm running:
$x1 = 0;
$y1 = 0;
$w1 = 196;
$h1 = 260;
$x2 = 0;
$y2 = 0;
$w2 = 140;
$h2 = 186;
$r1 = imagecreatefromgif($source);
$r2 = imagecreatetruecolor($w2, $h2);
imagealphablending($r2, false);
imagesavealpha($r2, true);
$res = imagecopyresampled($r2, $r1, $x2, $y2, $x1, $y1, $w2, $h2, $w1, $h1);
imagegif($r2, $dest);
Here's an example of the image scaled by the browser:
Here's an example of the image scaled with the above code:
Try to use imagepng instead of imagegif. imagepng third parameter is quality. Check specs: http://php.net/manual/en/function.imagepng.php
The PHP libraries aren't going to produce the best quality resize operation however you may find you have better results if you stick to a ratio based on the original file such that resizing a 200x200 to 100x100 or 50x50 would be find as its easy maths for the process to handle (i.e. merge 2/4 pixels into one). Your current operation is producing blurry results as it'll be a random value such as scaling to 0.63% of the original size.
Related
why is scaling image in php so complicated ? A lot of confusin about
imagecreatetruecolor
imagecreatefromjpeg
imagecopyresampled
imagejpeg - and so on
I simply want to scale down an image - if it's width is over 960px
something like this:
$path = "sbar/01.jpg";
$w = getimagesize($path)[0];
if($w > 960){scale($path, 960, auto);}
what is the simplest way to do this ? Here is my try:
$max = 960;
$h = getimagesize($path)[1];
$ratio = $w/$h;
$new_height = $w/$ratio;
$img = imagecreatetruecolor($max, $new_height);
$image = imagecreatefromjpeg($path);
And what now? There is still no desired image (960 x auto) in $path
There is no "best" way to do it. There are just different ways to do it. If you have an image that is already stored and you want to scale it and then save the scaled version you can simply do this:
$image = imagecreatefromjpeg('test.jpg');
$newImage = imagescale($image, 960, -1);
imagejpeg($newImage, 'scaled_image.jpg');
You first load the image from a given path. Then you scale it (while the image stays in memory). At the end, you save it under the provided path. Done.
The -1 (or if you omit the value altogether) means to only scale the other dimension and keep the aspect ratio.
In a recent competition I was given the task to extract binary data (another PNG) from a PNG image file's alpha channel. The data was encoded in such a way that if I read the values in the alpha channel for each pixel from the top left (e.g. 80,78,71,13,10,26,10) up to a specific point then the resulting data would form another image.
Initially I tried to complete this task using PHP, but I hit a roadblock that I could not overcome. Consider the code below:
function pad($hex){
return strlen($hex) < 2 ? "0$hex" : $hex;
}
$channel = '';
$image = 'image.png';
$ir = imagecreatefrompng($image);
imagesavealpha($ir, true);
list($width, $height) = getimagesize($image);
for ($y = 0; $y < $height; $y++){
for ($x = 0; $x < $width; $x++){
$pixel = imagecolorat($ir, $x, $y);
$colors = imagecolorsforindex($ir, $pixel);
$a = pad(dechex($colors['alpha']));
$channel.= $a;
}
}
After running this, I noticed that the output did not contain the PNG magic number, and I just didn't know what went wrong. After a bit of digging I found out that $colors['alpha'] only contained values less than or equal to 127. Due to the data having been encoded with a 0-255 range in mind, I could not find a way to extract it, and ended up (successfully) solving the problem with node.js instead.
So, my question is: Is there any way I could have read the PNG file's alpha channel that would have returned the values in a 0 to 255 range as opposed to 0-127, or is this a hard-coded limitation of PHP and/or GD?
For the record, I tried to use ($colors['alpha']/127)*255 in order to try and forge the value in the incorrect range to the correct one, but to no avail.
It is a limitation of GD. According to https://bitbucket.org/libgd/gd-libgd/issues/132/history-behind-7-bit-alpha-component, in GD source it says:
gd has only 7 bits of alpha channel resolution, and 127 is
transparent, 0 opaque. A moment of convenience, a lifetime of
compatibility.
If you install ImageMagick PHP extension, you can get the alpha value between 0-255 for a pixel (let's say with x=300 and y=490) like this:
$image = new Imagick(__DIR__ . DIRECTORY_SEPARATOR . 'image.png');
$x = 300;
$y = 490;
$pixel = $image->getImagePixelColor($x, $y);
$colors = $pixel->getColor(true);
echo 'Alpha value: ' . round($colors['a'] * 255, 0);
ImageMagick: https://www.imagemagick.org
ImageMagick for PHP (called Imagick): http://php.net/manual/en/book.imagick.php
In your code:
list($width, $height) = getimagesize($image);
references a variable '$image' that was not defined in the code.
using getimagesize means $image is a filename.
so this line get's width and height from a filename.
This line broke the code when I tested it.
You already have your answer, but this is for posterity.
Considering the code, I feel:
$width=imagesx ($ir);
$height=imagesy ($ir);
Would be more logical (and actually works)
I have a series of images on white backgrounds.
My problem is they are in a variety of shapes and sizes and I want them to all be equal in size and all centred in a square ratio without cropping and losing any of the actual image.
Below is my best attempt to date (using imagemagik), but the is not scaling it is just cropping square at 80x80 and losing most of the content
$im = new Imagick("myimg.jpg");
$im->trimImage(20000);
$im_props = $im->getImageGeometry();
$width = $im_props['width'];
$height = $im_props['height'];
$diff = abs($width-$height);
$color=new ImagickPixel();
$color->setColor("white");
if($width > $height){
$im->thumbnailImage(80, 0);
$im->borderImage($color, ($diff/2), 0);
}else{
$im->thumbnailImage(0, 80);
$im->borderImage($color, 0, ($diff/2));
}
$im->cropImage (80,80,0,0);
$im->writeImage("altimg.jpg");
Any help gratefully recieved
Thanks #Mark Setchel for pointing me in the right direction. I managed to achieve what I wanted, (an un-cropped image centred in a white square and trimmed to the longest side).
I have voted up your comments but thought I would post my final code for completeness.
$im = new Imagick("myimg.jpg");
$im->trimImage(20000);
$im->resizeImage(80, 80,Imagick::FILTER_LANCZOS,1, TRUE);
$im->setImageBackgroundColor("white");
$w = $im->getImageWidth();
$h = $im->getImageHeight();
$off_top=0;
$off_left=0;
if($w > $h){
$off_top = ((80-$h)/2) * -1;
}else{
$off_left = ((80-$w)/2) * -1;
}
$im->extentImage(80,80, $off_left, $off_top);
$im->writeImage("altimg.jpg");
I've scoured around the internet a fair bit and I can't seem to find any reference to what I am attempting to achieve... I fear that means I'm probably going about doing something the wrong way, but I'll pose this question here anyways in hopes that maybe I am not.
I would like to take an already generated image that has a rectangular selection already drawn on it via a specific color and a dynamic (but always rectangular) path, and crop or cut-out (and use) the inner area of that rectangular path.
Let's use an image generated by google maps as an example for this:
I thought perhaps the imagemagick library would hold a solution for this, but, I don't know if it's because I haven't quite narrowed down the exact key terms for what I am looking to do exactly, or if it's because it cannot (at least not simply) be done, but I haven't turned up any solutions.
Any solutions, advice, or smacks to the head are welcome.
[Please note that (for now) I would like to operate under the assumption that these images already exist, so any information regarding the pixel coordinates of the relative selection area on the image doesn't exist]
Your problem seems to boil down to this: How do I find a red rectangle in an image?
This is quite an open-ended problem, and could actually be quite difficult to solve. However, if the following assumptions can be made, then the task will be a lot easier:
The rectangle is drawn in pure RGB red (#ff0000).
The rectangle is aligned parallel with the image edges.
The image is saved in a lossless format like PNG.
The image contains no other pixels of this exact colour.
We know the width of the rectangle's edges.
The example you provided seems to tick all these boxes. Since it's stored as an 8-bit indexed color image, the first step would be to convert it into a true color image. This makes it easier to check the pixel values.
Then find the outermost edges of the frame, inset the coordinates by the frame width, and crop the image. Here's some code that will do this for you:
<?php
$src_img = 'er7RT.png';
$frame_color = 0xff0000;
$frame_width = 6;
// Load image and copy to true color image resource
$im = imagecreatefrompng($src_img);
$sw = imagesx($im);
$sh = imagesy($im);
$im1 = imagecreatetruecolor($sw, $sh);
imagecopy ($im1, $im, 0, 0, 0, 0, $sw, $sh);
imagedestroy($im);
// Get outer dimensions of frame.
// Assume the frame color appears nowhere else in the image.
$minx = $miny = 999999;
$maxx = $maxy = -$minx;
for ($x=0; $x<$sw; $x++) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $minx = $x; break 2; }
}
for ($x=$sw-1; $x>=0; $x--) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxx = $x; break 2; }
}
for ($y=0; $y<$sh; $y++) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $miny = $y; break 2; }
}
for ($y=$sh-1; $y>=0; $y--) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxy = $y; break 2; }
}
if ($minx>=$maxx || $miny>=$maxy) die("Couldn't locate frame");
// Subtract frame width to obtain crop region
$minx += $frame_width;
$maxx -= $frame_width;
$miny += $frame_width;
$maxy -= $frame_width;
// Create new image with cropped dimensions
$im2 = imagecreatetruecolor($maxx-$minx, $maxy-$miny);
imagecopy ($im2, $im1, 0, 0, $minx, $miny, $maxx-$minx, $maxy-$miny);
// Finish up
header("Content-Type: image/png");
imagepng($im2);
imagedestroy($im1);
imagedestroy($im2);
As a brief run down, I am currently making a dating type site. Users can create accounts and upload profile pictures (up to 8). In order to display these in the browse area of the website, I am looking for a way in PHP (with third party processor/scripts) to resize all images uploaded to have thumbnails that adhere to certain dimensions.
As an example, I will want "profile" images (thumbnails) to be NO larger than 120*150px. The scripting needs to resize uploaded images (regardless of whether they are portrait or landscape, and regardless of proportions) to adhere to these dimensions without getting stretched.
The width (eg. 120pixels) should always remain the same, but the height (eg. 150px) can vary in order to keep the image in proportion. If it's a landscape photo, I'm assuming the script would need to take a chunk out of the middle of the image?
The reason that all images to be resized is so that when profiles are display in a grid that all thumbnails are roughly the same size.
Any input would be greatly appreciated.
$maxwidth = 120;
$maxheight = 150;
$img = imagecreatefromjpeg($jpgimage);
//or imagecreatefrompng,imagecreatefromgif,etc. depending on user's uploaded file extension
$width = imagesx($img); //get width and height of original image
$height = imagesy($img);
//determine which side is the longest to use in calculating length of the shorter side, since the longest will be the max size for whichever side is longest.
if ($height > $width)
{
$ratio = $maxheight / $height;
$newheight = $maxheight;
$newwidth = $width * $ratio;
}
else
{
$ratio = $maxwidth / $width;
$newwidth = $maxwidth;
$newheight = $height * $ratio;
}
//create new image resource to hold the resized image
$newimg = imagecreatetruecolor($newwidth,$newheight);
$palsize = ImageColorsTotal($img); //Get palette size for original image
for ($i = 0; $i < $palsize; $i++) //Assign color palette to new image
{
$colors = ImageColorsForIndex($img, $i);
ImageColorAllocate($newimg, $colors['red'], $colors['green'], $colors['blue']);
}
//copy original image into new image at new size.
imagecopyresized($newimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);
imagejpeg($newimg,$outputfile); //$output file is the path/filename where you wish to save the file.
//Have to figure that one out yourself using whatever rules you want. Can use imagegif() or imagepng() or whatever.
This will shrink any images down proportionally based on whichever side is longer (width or height), to the maximum size. It will also blow up any images smaller than max, which you can stop with a bit of checking on whether or not both width and height are less than their max. So, a 200x300 image will be shrunk to 100x150, and a 300x200 image will be shrunk to 120x80.
Hmm, you want the width to always be 120, so it would change a bit, and yeah, it would have to cut something out in the case of an image like 200x300, because that would shrink to 120x180 without any distortion, or it would have to shrink it farther and letterbox it, but that should get you started nicely.
Letterboxing this example would just involve figuring out what the proper x and y to start the drawing to the new image would be in the imagecopyresized() function. In the case of something like 100x150, the X would be 10, I think, so there would be 10px of blank space on each side for 120x150 in the end. Letterboxing 120x80 X would be 0 but Y would be 35, so there would be 35px of blank space above and below for 120x150.
You'd also want to make $newimg with $maxwidth,$maxheight rather than $newwidth,$newheight, but the imagecopyresized() would still use both $new values.
Since I'm bored and don't have anything else to do, these changes would do it:
if ($height > $width)
{
$ratio = $maxheight / $height;
$newheight = $maxheight;
$newwidth = $width * $ratio;
$writex = round(($maxwidth - $newwidth) / 2);
$writey = 0;
{
else
{
$ratio = $maxwidth / $width;
$newwidth = $maxwidth;
$newheight = $height * $ratio;
$writex = 0;
$writey = round(($maxheight - $newheight) / 2);
}
$newimg = imagecreatetruecolor($maxwidth,$maxheight);
//Since you probably will want to set a color for the letter box do this
//Assign a color for the letterbox to the new image,
//since this is the first call, for imagecolorallocate, it will set the background color
//in this case, black rgb(0,0,0)
imagecolorallocate($newimg,0,0,0);
//Loop Palette assignment stuff here
imagecopyresized($newimg, $img, $writex, $writey, 0, 0, $newwidth, $newheight, $width, $height);
That should work, haven't tried it yet.
GD or Imagick functions are what you need depending on your PHP configuration.
Sorry I'm a newbie I can't post both links in the same message :(
I have recently needed php resizing (thumbnail) solution and found Zebra_Image library, which is a lightweight image manipulation library written in PHP. The code is really clean and it's also easy to use. I highly recommend using this library. The example code is just enough to get you started.
Be make sure that you have enough memory limit set in php.ini file to manipulate images which have relatively big resolution (e.g 2560x1600). I had an error with big images and there was no error to print. I debugged the problem down to imagecreatefrom{gif,jpeg,png} calls in function _create_from_source in lines 1262, 1279, and 1288. The calls were silented with # so I couldn't have a change to get the error. I removed # lines and saw a PHP error that memory limit has been exceeded. The original memory limit was set to 32MB and I increased it to 64MB. Now I can manipulate 2560x1600 and I refuse to manipulate images which are bigger.
Below is the example code for controlling the image resolution.
$image_properties = getimagesize($UPLOADED_FILE_PATH);
$file_width = $image_properties[0];
$file_height = $image_properties[1];
if ($file_width > 2560 || $file_height > 1600) {
// handle your error whichever you like, I simply 'die' just to show
die('Cannot manipulate image bigger than 2560x1600');
}
(Note: I use Zebra Image version 2.2.2)
You can do this with ImageMagick:
convert susan.jpg -resize x200 susan_thumb.jpg
This runs with a shell command, so in PHP you'd use shell_exec() to run the above command. I don't think you need any PHP extensions.
A few flags can be found in the ImageMagick documentation to control the resizing operation. If I recall correctly, the x200 before the number means "scale with the same aspect ratio to 200px".
I wrote an install guide for ImageMagick (and ghostscript): How to install, test, convert, resize PDF using ImageMagick, Ghostscript, Windows Vista/7 x64 based on my bumbling around, maybe it can help you.
The other option is the GD library (detailed in dqhendricks answer). This is faster and apparently better documented, used for basic operations.
you don't need imagick. here is a link that will take you to a function that will resize any image using PHP GD to any arbitrary size. the function has options to use letterboxing or crop-to-fit methods to resize to the new aspect ratio. the function is also explained thoroughly. check it out.
http://www.spotlesswebdesign.com/blog.php?id=1
if this is what you are looking for, please select the check mark next to this answer. thanks!
Some says imagic faster, i use next
function resizeImageProportional($imagePath, $destinationPath, $width = false, $height = false, $filterType = \Imagick::FILTER_POINT, $blur = 1, $bestFit = false, $cropZoom = false)
{
if (!$width && !$height) {
trigger_error("WTF_IMAGE_RESIZE");
return false;
}
//The blur factor where > 1 is blurry, < 1 is sharp.
$imagick = new \Imagick(realpath($imagePath));
if (!$width || !$height) {
$orig_width = $imagick->getImageWidth();
$orig_height = $imagick->getImageHeight();
$proportion = $orig_height/$orig_width;
if (!$width) {
$width = $height*$proportion;
} elseif (!$height) {
$height = $width*$proportion;
}
}
if ($bestFit) {
$imagick->resizeImage($width, $height, $filterType, $blur, $bestFit);
} else {
$imagick->resizeImage($width, $height, $filterType, $blur);
}
if ($cropZoom) {
$cropWidth = $imagick->getImageWidth();
$cropHeight = $imagick->getImageHeight();
$newWidth = $cropWidth / 2;
$newHeight = $cropHeight / 2;
$imagick->cropimage(
$newWidth,
$newHeight,
($cropWidth - $newWidth) / 2,
($cropHeight - $newHeight) / 2
);
$imagick->scaleimage(
$imagick->getImageWidth() * 4,
$imagick->getImageHeight() * 4
);
}