I'm currently using GD and some PHP to rotate images. After each rotation the quality of the image gets worse. Is there a better way to do this or is this expected?
$img = new ImageClass;
list($original, $info) = $img->load($src);
// Determine angle
$angle = strtolower($angle);
if( $angle == 'cw' || $angle == 'clockwise' ) $angle = 270;
if( $angle == 'ccw' || $angle == 'counterclockwise' ) $angle = 90;
$rgb = $img->hex2rgb($bg_color);
$bg_color = imagecolorallocate($original, $rgb['r'], $rgb['g'], $rgb['b']);
$new = imagerotate($original, $angle, $bg_color);
Is it possible to rotate the image client side with maybe jquery and then save the image via PHP to the server? I'm assuming this will help with the image quality.
In our sharing service we handle this differently. We keep the original image at all times and store the initial rotation (0).
Upon rotation, the original image is loaded, rotated according to rotation +/- 90 and a copy is written onto the file system. Afterwards the database is updated with the new rotation.
You will need two more columns in your database though, for the rotation and the rotated copy location. I'm also assuming that you always do an initial resize (and perhaps multiple for different sizes), so that the extra column always points to the copy rather than sometimes pointing to the original instead until rotated.
If you rotate an image in a finite matrix, you can't avoid degradation of the quality, server side or client side and those degradations are added at each rotation.
If you need many rotated images from the same one, you must always use the first (never rotated) image as source and increment the angle instead of rotating the last rotated image.
Every time you save a JPG, you will lose quality. It's a "lossy" format. If you use PNG, you can rotate as many times as you want with zero quality loss, because it's a "lossless" format.
a few points
as dystroy wrote, rotation is lossy when used on compressed images
clientside rotation is usually not an image manipulation but a rotation of the image object in the view, so when you send it to the server, it will be the original image
ImageMagick is rumored to deliver better results than GD on transformations (see http://de.php.net/imagick)
Related
I am using php to resize and crop images. However the cropped image file is greater than ( much greater in some cases) than the original file. I even tried without cropping (copying the original image) but the resulting size is more than the original image.
$ImageName = '/IMAGES/testImage.jpg';
//Download the Image File
$base_url = 'https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/ia62ed3191fcc424f/version/1457278719/athens-european-best-destinations.jpg';
$image_size = getimagesize($base_url);
$image_width = $image_size[0];
$image_height = $image_size[1];
$src_image = imagecreatefromjpeg($base_url);
//Copy Image Object To File
imagejpeg($src_image, $ImageName, 100);
Technically, the quality rate of the conversion is not derivable from
the jpeg data, it just tells the converter which tradeoff to make
between size and quality.
Some converters store it in the EXIF data of the JPEG header though,
so if that is still present you can use it with exif_read_data on it,
and see if the compression information is returned.
Source
try to reduce quality and set 80 for example. Quality will be still ok, it's even higher then default quality which should be 75 if you set nothing.
So, put like this:
imagejpeg($src_image, $ImageName, 80);
I think you can try this ImageCopyResampled in this case if you want to keep every pixel like original image. It's slower but better than imagejpeg.
And about quality of image using imagejpeg the quaility property refers to how much jpeg compression you want to apply to the saved or created output image.
0% quality means maximum compression and 100% quality means minimum compression.
The more compression you apply, the smaller the output filesize will be relative to the original uncompressed filesize.
Just exclude the image quality parameter:
imagejpeg($src_image, $ImageName);
GD will automatically create a smaller image for you
I have pieced together a PHP class to perform various image related functions using GD functions of PHP.
It works great for all image types. Rotate, flip, resize, crop and to a lesser extent, watermark.
All but the latter work perfectly. For example after a few changes, rotated PNG images retained their transparency whereas before they were losing that and the background turning black. Common problem, it appears. But all working now.
Where I'm still getting stuck is watermarking a PNG image with another PNG image. It appears to work fine with JPG and other images. This is the code (simplified):
public function writeWatermarkSimple()
{
$watermarkFile = 'watermark.png';
$watermarkImage = imagecreatefrompng($watermarkFile);
imagealphablending($watermarkImage, false);
imagesavealpha($watermarkImage, true);
$imageFile = 'image.png';
$baseImage = imagecreatefrompng($imageFile);
imagealphablending($baseImage, false);
imagesavealpha($baseImage, true);
$marginH = imagesx($baseImage) - imagesx($watermarkImage);
$marginV = imagesy($baseImage) - imagesy($watermarkImage);
$cut = imagecreatetruecolor(imagesx($watermarkImage), imagesy($watermarkImage));
imagecopy($cut, $baseImage, 0, 0, $marginH, $marginV, imagesx($watermarkImage), imagesy($watermarkImage));
imagecopy($cut, $watermarkImage, 0, 0, 0, 0, imagesx($watermarkImage), imagesy($watermarkImage));
imagecopymerge($baseImage, $cut, $marginH, $marginV, 0, 0, imagesx($watermarkImage), imagesy($watermarkImage), 80);
if (!imagepng($baseImage, 'watermarked_image.png'))
{
return false;
}
return true;
}
This has been pieced together with various guides and advice people have given based on a similar issue. Again, working perfectly with JPG images and PNG watermarks, but not PNG & PNG.
Some example images:
http://i.imgur.com/hHRWinj.png - This is the watermark I'm using.
http://i.imgur.com/6sy8Ncs.png - This is the image I'm applying the watermark to.
http://i.imgur.com/ghovYLm.png - This is the end result.
The bit I find interesting is that any part of the watermark that is overlaid on a non-transparent portion of the image is working fine. Just the rest of it has the black background.
This leads me to believe I'm close, and I hope that the expertise of you fine people may lead me to the solution.
Thanks ever so for reading.
So, I'm not giving up on finding the correct answer to do this using GD. However, I was overjoyed to find that what needed up to 30 lines of code with GD can be achieved using much less with ImageMagick:
$image = new Imagick();
$image->readimage($this->_image);
$watermark = new Imagick();
$watermark->readimage($this->_watermark->_getImage());
$watermark->evaluateImage(Imagick::EVALUATE_DIVIDE, 2, Imagick::CHANNEL_ALPHA);
$image->compositeImage($watermark, imagick::COMPOSITE_OVER, $marginH, $marginV);
So this is before (with GD):
http://i.imgur.com/AlS0TcO.png
And after (with ImageMagick and the code above):
http://i.imgur.com/zBxlC3R.png
If anyone has an answer that is purely GD then I'd be immensely grateful.
Ran into some similar issues recently and while this may not exactly solve your problem, these were some useful discoveries that I made.
In my case, I have an original .jpg image and a watermark .png image. The watermark image has a fully transparent background. I wanted to specify the opacity in my script and have it change the watermark opacity before placing it on top of the origina image. Most posts out there regarding PHP watermarking assume that the original watermark .png file already has the solid watermark portion set to the correct opacity rather than changing it via the script.
gd didn't like a 24 bit .png and caused some goofy issues. Switching to 8 bit resolved that with gd. On the other hand, imagick works very well with a 24 bit .png and the final result seems to be better.
For me, using gd worked just fine if I was opening the original watermark .png and using imagecopymerge() to set the watermark transparency. If however I tried to scale the original watermark .png (which has transparent background) first, then I would get similar results as you with black or white background portion of where watermark image is. See How do I resize pngs with transparency in PHP? for a partial solution by filling the new wm image with transparent rectangle first. For me this still produced an opaque white background on the final result no matter what I tried.
I switched to imagick and was using setImageOpacity() to change the transparency of my watermark .png before applying it on top of my original image and I was still getting the same effect with a black background. Finally read in the PHP doc for setImageOpacity() that if the original .png has any transparent pixels and you try to lower the opacity, those pixels become opaque (black) with the new transparency applied. Instead, need to use the evaluateImage() function. This will instead evaluate each pixel's alpha channel only and divide by the specifid number.
I assume the black / white background issue with gd is likely due to similar ways that it treats alpha channels when scaling / combining as compared to imagick and if you want to do it all in gd you just need to find some similar way to evaluate and manipulate the alpha channel per-pixel because the "easy" ways seem to take an already transparent background and make it opaque.
So, the solution:
Assuming you want to apply your watermark at an opacity of 45% and you're using imagick, then instead of this:
$watermark->setImageOpacity(.45);
do this
$watermark->evaluateImage(Imagick::EVALUATE_DIVIDE, (1/.45), Imagick::CHANNEL_ALPHA);
You need to divide 1 by your opacity to get the demoninator by which the function will divide the alpha channel value for each pixel. In this case, 1/.45 = 2.2222, so then the function will divide the alpha channel of each pixel by 2.2222. This means a solid pixel (alpha of 1) would result in 1/2.2222 or .45 alpha or transparency when finished. Any pixels that were already transparent (alpha 0) would stay transparent because 0 divided by anything is always what? Zero!
After you change the watermark transparency then you can use compositeImage() to merge the watermark onto the original image.
While i was doing some image processing, i found out that GD and Imagick in PHP does not resize image to color pixel identical level, which in most cases, were not needed.
Now in case, i need a image from whatever dimension to scale to 256*256
To make sure the TEST results are consistent, i used a 256*256 image and resize it to it's own size.
what i've attempted:
imagecopyresized($new, $img, 0, 0, $x, 0, $width, $height, $w, $h); //256 , 256
and
$compression_type = imagick::COMPRESSION_NO;
$images_ima = new Imagick($image_path); //$image_path = path to image...
$images_ima->setImageCompression($compression_type);
$images_ima->setImageCompressionQuality(100);
$images_ima->sampleImage($X_SIZE,$Y_SIZE); // 256 ,256
$images_ima->writeImages($dest_path, true); //destination path
none of them worked, if i compare the output with the original image, it will look something like this:
it looks like the functions i've used are resampling the image since the variations in the RGB value between both image are small
i can achieve pixel to pixel identical resizing from 256*256 to 256*256 in photoshop, OSX preview, and even Pixelformer.
i was wondering how can that be done i PHP?
Since your image format (jpeg - assumed from 100 quality setting) is a lossy format you won't get a lossless throughput this way as you're recompressing the image.
You should try to detect image dimensions and use the original image if the dimensions are already correct.
When you don't change the dimensions (original dimensions = dimensions after resizing) in Photoshop or OSX preview they won't recompress the image, that's why you won't see any change.
I am resizing images using:
//load file and dimensions
$obj_original_image = imagecreatefromjpeg($str_file_path);
list($int_width, $int_height, $image_type) = getimagesize($str_file_path);
//compress file
$int_thumbnail_width = 320;
$int_ratio = $int_thumbnail_width / $int_width;
$int_new_width = $int_thumbnail_width;
$int_new_height = $int_height * $int_ratio;
$obj_image = imagecreatetruecolor($int_new_width, $int_new_height);
imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
imagejpeg($obj_image, $GLOBALS['serverpath'] . '/images/uploaded/video-thumbs/'.$arr_data['thumbnail_folder'].'/' . $arr_data['id']. '.jpg', $int_compression = 85);
And this is generally working perfectly. However occasionally, it is producing a blank, white image. I have read this note at http://php.net/manual/en/function.imagecopyresampled.php:
There is a problem due to palette image limitations (255+1 colors). Resampling or filtering an image commonly needs more colors than 255, a kind of approximation is used to calculate the new resampled pixel and its color. With a palette image we try to allocate a new color, if that failed, we choose the closest (in theory) computed color. This is not always the closest visual color. That may produce a weird result, like blank (or visually blank) images. To skip this problem, please use a truecolor image as a destination image, such as one created by imagecreatetruecolor().
However, I am already using imagecreatetruecolor. I am always scaling the same sized images so it can't be a width/height issue. It is only happening sometimes, most times the image scaling is working fine. Any ideas as to how to fix this?
There's no reason to be using getimagesize() when you've already opened the image with imagecreatefromjpeg(). You can use the imagesx() and imagesy() to get the width/height. getimagesize is seperate from GD and will re-open the image, re-parse it, etc...
Also note that GD is pretty stupid and forces you to determine what image type you've got and call the appropriate createfrom function. imagecreatefromjpeg() will fail if you try to open anything OTHER than a .jpg image.
$obj_original_image = imagecreatefromjpeg($str_file_path);
if ($obj_original_image === FALSE) {
die("Unable to load $str_file_path. Not a jpg?");
}
etc... etc...
$status = imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
if ($status === FALSE) {
die("imagecopyresampled failed!");
}
As well, add some debugging to your height/width calculations - output the values you're generating. Maybe one or more of them is coming out as 0, so you end up resampling to a non-existent size.
You mentioned that the size of the image being processed is always the same, but is the resolution always the same? If you have a particularly high resolution image you're working with, you may be running out of memory for certain operations which will produce a blank white image, in my experience. If this is the case, you could try to fix it by increasing the amount of memory allocated to PHP in php.ini under the memory_limit parameter. As far as I know, that value applies to image manipulations.
HEllo,
I am trying to rotate a circular image around the center and then cut off the sides. I see the imagerotate function, but it does not seem to rotate about centre.
Anyone have any suggestions?
Thank you.
Update: Since it is a circle, I want to cut off the edges and keep my circle in the same dimensions.
The documentation says that it does rotate around the center.
Unfortunately it also says that it will scale the image so that it still fits. That means that whatever you do this function will change the size of your internal circular image.
You could (relatively easily) calculate how much scaling down will happen and then prescale the image up appropriately beforehand.
If you have the PHP "ImageMagick" functions available you can use those instead - they apparently don't scale the image.
I faced successfully that problem with the following code
$width_before = imagesx($img1);
$height_before = imagesy($img1);
$img1 = imagerotate($img1, $angle, $mycolor);
//but imagerotate scales, so we clip to the original size
$img2 = #imagecreatetruecolor($width_before, $height_before);
$new_width = imagesx($img1); // whese dimensions are
$new_height = imagesy($img1);// the scaled ones (by imagerotate)
imagecopyresampled(
$img2, $img1,
0, 0,
($new_width-$width_before)/2,
($new_height-$height_before)/2,
$width_before,
$height_before,
$width_before,
$height_before
);
$img1 = $img2;
// now img1 is center rotated and maintains original size
Hope it helps.
Bye
According to the PHP manual imagerotate() page:
The center of rotation is the center
of the image, and the rotated image is
scaled down so that the whole rotated
image fits in the destination image -
the edges are not clipped.
Perhaps the visible center of the image is not the actual center?