I'm very new to PHP and this is the very first time I've tried to use Imagick, so there must be plenty wrong with the script I wrote in order to achieve my goal. :-)
What I'd like to do:
Convert an image that was uploaded to JPEG and resize it;
Make a blurred version of it;
Move the two images to a certain directory.
I wrote a function that's supposed to do it, but, as you guessed, it doesn't do anything at all:
function convertAndBlur($inputIMG, $dst, $width, $quality) {
$img = new Imagick($inputIMG);
$img->scaleImage($width, 0, true); // keep aspect ratio
$img->setImageFormat('jpeg');
$img->setImageCompressionQuality($quality);
$img->writeImage($dst . ".jpg");
$imgBlur = new Imagick($img);
$imgBlur->blurImage(5,3);
$imgBlur->writeImage($dst . "_blur.jpg");
$img->clear(); $img->destroy();
$imgBlur->clear(); $imgBlur->destroy();
}
The function is called this way:
$picture = $_FILES["picture"]["tmp_name"];
$pictureName = "foo";
if(!is_image($picture)) { die("not an image"); }
$picturePath = "uploads/" . $pictureName;
convertAndBlur($picture, $picturePath, 1000, 90);
This will certainly make some of you roll their eyes, but again, that's completely uncharted territory to me. Could anyone help me out? Thanks!
Imagick's constructor cannot take an instance of Imagick as an argument. To create another object, try $imgBlur = clone $img; in place of $imgBlur = new Imagick($img);, which will create a carbon copy of $img. Read more about cloning Imagick objects here.
Related
I've managed to install Imagemagick 7 on my server and set-up the appropriate PHP module in order to work with HEIC and WebP files using Imagick.
I can convert these files easily to other formats like PNG or JPEG without any problems.
Now, I try to convert a HEIC sequence (original file) to GIF, but the result is imperfect.
First off all, it seems, that Imagick isn't able to determine the delay between every image. This results in a lightning fast animation. So for now, I've hardcoded a delay of 40 ticks using Imagick::setImageDelay. I'm not sure, if I'm missing something, or if Imagick/Imagemagick isn't able to do this on HEIC sequences.
The next problem I'm experiencing is, that Imagick can't handle the original image dimensions properly. To get around this, I have to get the original image's dimensions and perform Imagick::cropImage and Imagick::thumbnailImage on every frame, before I finalize it with Imagick::setImagePage.
However, my biggest problem for now is, that the result has a broken, first frame:
So the final (broken) result looks like this:
While the expected result (generated by heic2any) is:
The (simplified) code I am using:
<?php
if (!in_array('HEIC', \Imagick::queryFormats("HEI*"))) {
throw new \Exception('Unsupported format');
}
$sourceFile = __DIR__ . '/3.heic';
$targetFile = __DIR__ . '/3.gif';
if (!file_exists($sourceFile)) {
try {
$content = file_get_contents('https://github.com/alexcorvi/heic2any/blob/master/demo/' . basename($sourceFile) . '?raw=true');
if ($content === false) {
throw new \Exception('Unable to download source file.');
}
file_put_contents($sourceFile, $content);
} catch (\Exception $e) {
throw new \Exception('Unable to download source file: ' . $e->getMessage());
}
if (!file_exists($sourceFile)) {
throw new \Exception('Cannot find source file.');
}
}
$im = new \Imagick($sourceFile);
$width = $im->getImageWidth();
$height = $im->getImageHeight();
$im = $im->coalesceImages();
foreach ($im as $frame) {
$im->setImageDelay(40);
$frame->cropImage($width, $height, 0, 0);
$frame->thumbnailImage($width, $height);
$frame->setImagePage($width, $height, 0, 0);
}
$im = $im->deconstructImages();
$im->writeImages($targetFile, true);
echo 'Size: ' . $width . 'x' . $height . '<br />Result:<br /><br /><img src="' . basename($targetFile) . '?v=' . time() . '" alt="" />';
So my questions are:
Where does the 1st frame come from?
How can I avoid it?
Is there anything to optimize the conversion (code)?
UPDATE:
There's another .HEIC sequence to play with (click) and this one can be converted without problems:
UPDATE:
Even conversion services like cloudconvert can't handle the image correctly and producing GIF animations like this:
I am using a PHP function to add my logo as the watermark on images uploaded on my website. But I don't know why my watermark function is not working for png files. however, it works for jpeg files perfectly. this is my PHP function.
function watermark($img) {
global $wm_file, $wm_right, $wm_bottom;
// image values pulled from config.inc.php
$logo = './images/' . $wm_file; // path to the watermark.png
$sp = $wm_right; // spacing from right side
$sq = $wm_bottom; // spacing from bottom
$size = getImageSize($img);
$sizel = getImageSize($logo);
$imgA = imageCreateFromJpeg($img);
imageAlphaBlending($imgA, TRUE);
if($sizel[0] > $size[0] || $sizel[1] > $size[1])
{
// logo size > img size
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
$sizel[0] = ($sizel[0]/2);
$sizel[1] = ($sizel[1]/2);
}
else
{
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
}
$imgBa = imageCreateFromPng($logo);
$imgB = imageCreateTrueColor($sizel[0], $sizel[1]);
imageAlphaBlending($imgB, TRUE);
imageCopyResampled($imgB, $imgBa, 0, 0, 0, 0, $sizel[0], $sizel[1], $sizelo[0], $sizelo[1]);
imageColorTransparent($imgB, ImageColorAllocate($imgB, 0, 0, 0));
$perc = 100;
imageCopymerge($imgA, $imgB, ($size[0]-$sizel[0]-$sp), ($size[1]-$sizel[1]-$sq), 0, 0, $sizel[0], $sizel[1], $perc);
unlink($img);
if(imageJpeg($imgA, $img, 100))
{
imageDestroy($imgB);
imageDestroy($imgA);
return true;
}
chmod($img, 0777);
}
The problem I see is that you are using imageCreateFromJpeg() as the way to generate the resource for your $img that you are passing to the function.
If you pass a jpeg through the function it will work. If you pass a png it will not.
I recommend using imagecreatefromstring() to create all your resources as it is not dependent on the file type. Like so:
$source = imagecreatefromstring(file_get_contents($filePath));
Another benefit of this is that it will return false if the function fails to create a resource from the file path that you supplied meaning that the file is not an image file.
Now that you have a resource to use for the rest of your code, imageJpeg() will save the resource as a jpeg back to the file path.
Hope that helps.
One other side note. If you intend on using bmp images, the GD library does not have a built in function for bmps. However on PHP.net, someone did write a createimagefromBMP() that works really well. Also I think that on the latest version of PHP the GD library does now actually have a createimagefromBMP() function.
I also see that you are using unlink() to delete the image from your directory. This is not necessary for two reasons. The imageJpeg() will just overwrite the original. Also, if for some reason your script fails it may delete the image prematurely and you will loose the image without the new one being written.
Please be careful when using chmod(), always make sure that you set permissions back to the original permissions when you are done.
chmod($img, 777); //Give broad permissions.
//Do something.
chmod($img, 600(or whatever they were)); //Reset permission back to where they were before you changed them.
I am using Glide to deliver image content from one of my sites. This is working well and I have now built a file upload so that admins can upload images to the site for subsequent download.
Some of the images that admins will upload will be much larger than I need (or want the overhead of storing on the server), so I want to downsize them, preferably during the upload routine or failing that, just after they have been saved to their new location (storage/app/images)
So, I've been hacking around with intervention for instance without much success because of my poor understanding of the file names and paths available from getClientOriginalName/Extension etc.
Could anyone show me a pattern for this which would work well. Ideally I'd love to include something like I've seen on others' examples like...
$img = Image::make('foo.jpg')->resize(300, 200);
... in the correct place in my code
foreach($files as $file) {
$fileExtension = $file->getClientOriginalExtension();
$fileMimeType = $file->getMimeType();
if(in_array($fileExtension, $allowableExtensions)) {
if(in_array($fileMimeType, $allowableMimes)) {
array_push($dbFileList, $file->getClientOriginalName());
$newImage = '/images/' . $propertyCode . '/' . $file->getClientOriginalName();
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
}else{
$errorMessage = 'At least one file was not an image, check your results...';
}
}else{
$errorMessage = 'At least one file was not an image, check your results...';
}
}
Update 1:
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
$img = Image::make($file);
Storage::put('/images/new/' . $file->getClientOriginalName(), $img);
This updated code outputs the files to the /new directory and all looks fine, but the output files have 'zero bytes'. What am I missing?
Update 2: Final code
The final answer (after using the proper code provided by contributors) was that:
I had to move my app from virtual box on to the dev machine (iMac) to prevent extra confusion with paths
The path for the images must exist prior to making the ->save()
The path variable must be set in advance of the ->save()
I don't need the Storage::put at all, so the larger file never ends up on the server.
Then this final code started to work.
$path = storage_path('app/smallpics/')."/".$file->getClientOriginalName();
$img = Image::make($file)->resize(300,200)->save($path);
Much thanks to all of you. You make my Laravel learning curve a bit less terrifiying!!
You can use Intervention to manipulate your image (resize etc.) as
$new_image = Image::make($file)->resize(300,200)->save('/path/to/save');
The image upload and resize work flow is like:
Upload the image from tmp to your directory.
Make a copy of that image by setting the height, width, quality and save it in the same or some other directory.
Delete the original image.
So as per your code flow:
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
after this code, put the image compress code and after that delete the original image.
you can use Intervention or just use imagemagick convert command line command for resize or convert.
Pay attention to comments :
public function saveUploadPic(Request $request)
{
$pic = $request->file('<NAME_OF_FILE_INPUT_IN_HTML_FORM>');
#check for upload correctly
if(!$pic->isValid())
{
throw new Exception("IMAGE NOT UPLOADED CORRECTLY");
}
#check for mime type and extention
$ext = $pic->getClientOriginalExtension();
$mime = $pic->getMimeType();
if(!in_array($mime, $allowedMimeTypeArray) || !in_array($ext, $allowedExtArray))
{
throw new Exception("This Image Not Support");
}
#check for size
$size = $pic->getClientSize() / 1024 / 1024;
if($size > $allowedSize)
{
throw new Exception("Size Of Image Is More Than Support Size");
}
########################YOU HAVE TWO OPTION HERE###################
#1- save image in a temporary location with random hash for name if u need orginal image for other process
#below code save image in <LARAVEL_APP_PATH>/storage/app/tmp/pics/
$hash = md5(date("YmdHis").rand(1,10000));
$pic->storeAs('tmp/pics', $hash.'.'.$ext);
#Then resize or convert it
$img = Image::make(storage_path('app/tmp/pics/'.$hash.'.'.$ext))->resize(300, 200);
#save new image whatever u want
$img->save('<PATH_TO_SAVE_IMAGE>');
#after u finish with orginal image delete it
Storage::delete(storage_path('app/tmp/pics/'.$hash.'.'.$ext);
#2- Or just use below for resize and save image witout need to save in temporary location
$img = Image::make($pic->getRealPath())->resize(300,200);
$img->save('<PATH_TO_SAVE_IMAGE>');
}
if you want to use convert see this link.
I running this script to create a jpg image from a pdf.
$im = new Imagick();
$im->setResolution(300, 300);
$im->readImage($temp_path . $file);
if ($im->getImageColorspace() == \Imagick::COLORSPACE_CMYK) {
$profiles = $im->getImageProfiles('*', false);
// we're only interested if ICC profile(s) exist
$has_icc_profile = (array_search('icc', $profiles) !== false);
// if it doesnt have a CMYK ICC profile, we add one
if ($has_icc_profile === false) {
$icc_cmyk = file_get_contents(dirname(dirname(__FILE__)) . '/USWebUncoated.icc');
$im->profileImage('icc', $icc_cmyk);
unset($icc_cmyk);
}
// then we add an RGB profile
$icc_rgb = file_get_contents(dirname(dirname(__FILE__)) . '/sRGB_v4_ICC_preference.icc');
$im->profileImage('icc', $icc_rgb);
unset($icc_rgb);
}
$im->setImageBackgroundColor('white');
$im = $im->flattenImages();
$im->setImageFormat('jpeg');
$im->thumbnailImage(900, 900, true);
Is working fine but the problem is that is taking to long to excecute. and some time if the file have to much detail I get a timeout excecution from php.
I was using it before without the profileImage() file and was working perfect, but the color on the CMYK was not right.
How can I make better and efficient. I running this on linux with php5.5.9
Thanks.
I recommend using cloudinary.com, check it out, it will save you a lot of time.
See if this is any faster and still contains the correct color:
<php
$im = new Imagick();
$im->readImage($temp_path . $file); //pdf
if ($im->getImageColorspace() == \Imagick::COLORSPACE_CMYK) {
$im->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
}
$im->writeImage('out.jpg'); // jpg
?>
Our server is saving EXIF data to every file saved with imagejpeg(). As far as I know, this is not the default behavior (or even possible, from what I've read). But, it is occurring, and due to the FileDateTime information being included (and using the time of save), it is breaking functionality in our upload/approval system (md5_file() returns a different value for the exact same image due to FileDateTime always being different).
Is there a way to prevent imagejpeg() from saving EXIF data for images by default?
Server Information
CentOS 5
Parallels Plesk Panel 10.4.4
GD Version: bundled (2.0.34 compatible)
PHP 5.3
Code
<?php
public function upload_book_cover($book, $cover, $filename = NULL, $approved = NULL){
global $c_consummo, $user;
$approved = bool($approved, true, true);
if(filesize($cover)>5242880){
return false; // Too large;
}
$max_width = 450;
$cover_info = getimagesize($cover);
if(!$this->is_valid_book_cover_type($cover_info['mime'])){
return false; // Invalid image type
}
$width = $cover_info[0];
$height = $cover_info[1];
if($width<200){
return false; // Too small
} elseif($width>1500){
return false; // Too wide
}
$original_cover = false;
switch($cover_info[2]){
case IMAGETYPE_GIF:
$original_cover = imagecreatefromgif($cover);
break;
case IMAGETYPE_JPEG:
$original_cover = imagecreatefromjpeg($cover);
break;
case IMAGETYPE_PNG:
$original_cover = imagecreatefrompng($cover);
break;
case IMAGETYPE_BMP:
$original_cover = imagecreatefrombmp($cover);
break;
}
if(!$original_cover){
return false; // Unsupported type
}
if($width>$max_width){
$new_width = $max_width;
} else {
$new_width = $width;
}
$new_height = round($height*($new_width/$width));
$new_cover = imagecreatetruecolor($new_width, $new_height);
if(!$new_cover){
return false; // Could not create true color image
}
if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
return false; // Could not copy image
}
if(!imagejpeg($new_cover, $cover, 100)){
return false; // Image could not be saved to tmp file
// This is adding *new* EXIF data to images by itself
}
$file_hash = md5_file($cover);
$duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
if($duplicate_book_cover){
return $duplicate_book_cover;
}
$file_id = $c_consummo->upload_file($cover, $filename);
...
}
It looks like you have tried several things here, but lets try one more.
Do you EVER need EXIF information in your application?
If not lets take out support for EXIF and see if that completely removes it.
If it does not remove it, then perhaps the functions are reading it from the existing photos and then just blindly including it in the file that is written.
You can know for sure by printing out the EXIF information at each step of process
No idea why EXIF data is being written - so the following may help you remove it.
One suggestion is to run something as a command on the server - it will need some installation: http://hacktux.com/read/remove/exif - then run throuhg EXEC from PHP.
There's also solution posted here that uses ImageMagick if you also have that installed (or cen get it installed: Remove EXIF data from JPG using PHP (but note warning about colour)
Otherwise, the other suggestion is as above, try turning off the EXIT extension.
Sorry if they don't help, but you did ask for any suggestions.
Apparently, GD doesn't like when the path to the input / output file is the same, but the credit isn't mine. To fix, use a new (tmp) file to save the newly created image to:
<?php
...
if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
return false; // Could not copy image
}
// Create a tmp file.
$cover_new = tempnam('/tmp', 'cover-');
// Use $cover_new instead of $cover
if(!imagejpeg($new_cover, $cover_new, 100)){
return false; // Image could not be saved to tmp file
}
// Use $cover_new instead of $cover
$file_hash = md5_file($cover_new);
$duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
if($duplicate_book_cover){
return $duplicate_book_cover;
}
// Use $cover_new instead of $cover
$file_id = $c_consummo->upload_file($cover_new, $filename);
...
Read this, maybe it will help:
http://www.php.net/manual/en/function.imagecreatefromjpeg.php#65656
and this:
http://www.sentex.net/~mwandel/jhead/usage.html
You could possibly convert the jpeg to a gif first, then convert the gif back to a jpeg. In doing so, my understanding is that you would destroy the EXIF data. It's a hack, but it should work.
The only thing that spring to mind is, that you could ignore the EXIF data and just make your hash from something else? My suggestion would be to try either hashing the raw output without saving to a file (here outputting in gif format -which makes a smaller, 8-bit image for performance- to an output buffer and hashing the buffer content)
if(!imagecopyresampled(...)) {...}
ob_start();
imagegif($new_cover);
$file_hash = md5(ob_get_contents());
ob_end_clean();
if(!imagejpeg($new_cover, $cover, 100)) {...}
Or you could build a string containing only the pixel information and hash that (here done by accessing each pixel and appending its value to a string, in reverse for performance)
$pixels = '';
for($x=$new_width-1; $x>=0; $x--) {
for($y=$new_height-1; $y>=0; $y--) {
$pixels .= imagecolorat($new_cover, $x, $y);
}
}
$file_hash = md5($pixels);
For performance, you could also choose only to take samples from the image, as this should work just as well or maybe even better (here sampling every 5th pixel of every 5th row)
$pixels = '';
for($x=$new_width-1; $x>=0; $x-=5) {
for($y=$new_height-1; $y>=0; $y-=5) {
$pixels .= imagecolorat($new_cover, $x, $y);
}
}
$file_hash = md5($pixels);
I hope some of this will work (as I can't test it right now) or at least will help you find the way that works for you :)
This is a bit of a hack, but it will work, just do this after your switch() statement:
$original_cover = imagerotate($original_cover,360,0);
GD will strip any EXIF data out, as it doesn't support it.
You could try using imagecreatefromjpeg on the image created with imagejpeg and replacing with the newly created one.
$res = imagecreatefromjpeg($filename) to load the image, then imagejpeg($res, $filename, QUALITY)to rerender it.
You can use imagemagick too:
$img = new Imagick($image);
$img->stripImage();
$img->writeImage($image);
$img->clear();
$img->destroy();
Your PHP must be compiled in with --enable-exif.
Try to disable globally EXIF functionality by recompiling PHP without this option.