Image Intervention w/ Laravel Storage - php

I'm using the storage facade to store an avatar which works fine, but I want to resize my image like I did in previous versions of Laravel.
How can I go about doing this?
Here is what I have so far (doesn't work)
$path = $request->file('createcommunityavatar');
$resize = Image::make($path)->fit(300);
$store = Storage::putFile('public/image', $resize);
$url = Storage::url($store);
Error Message:
Command (hashName) is not available for driver (Gd).

You're trying to pass into putFile wrong object. That method expects File object (not Image).
$path = $request->file('createcommunityavatar');
// returns \Intervention\Image\Image - OK
$resize = Image::make($path)->fit(300);
// expects 2nd arg - \Illuminate\Http\UploadedFile - ERROR, because Image does not have hashName method
$store = Storage::putFile('public/image', $resize);
$url = Storage::url($store);
Ok, now when we understand the main reason, let's fix the code
// returns Intervention\Image\Image
$resize = Image::make($path)->fit(300)->encode('jpg');
// calculate md5 hash of encoded image
$hash = md5($resize->__toString());
// use hash as a name
$path = "images/{$hash}.jpg";
// save it locally to ~/public/images/{$hash}.jpg
$resize->save(public_path($path));
// $url = "/images/{$hash}.jpg"
$url = "/" . $path;
Let's imagine that you want to use Storage facade:
// does not work - Storage::putFile('public/image', $resize);
// Storage::put($path, $contents, $visibility = null)
Storage::put('public/image/myUniqueFileNameHere.jpg', $resize->__toString());

The put method works with the Image intervention output.
The putFile method accepts either an Illuminate\Http\File or Illuminate\Http\UploadedFile instance.
$photo = Image::make($request->file('photo'))
->resize(400, null, function ($constraint) { $constraint->aspectRatio(); } )
->encode('jpg',80);
Storage::disk('public')->put( 'photo.jpg', $photo);
The above code resizes the uploaded file to 400px width while holding the aspect ratio. Then encodes to jpg at 80% quality.
The file is then stored to the public disc.
Note you must provide a filename, not just the directory.

Using Laravel 5.8
I had a similar issue when trying to read an image file with Image when this one was saved and loaded with Storage.
Beside all the answers I wasn't sure why it wasn't working.
Exception when Image was trying to read the file
Intervention\Image\Exception\NotReadableException : Unable to init from given binary data.
Short answer
Adding ->encode()solved the issue
http://image.intervention.io/api/encode
Scenario
Basically I had a test like this
Storage::fake();
$photo = factory(Photo::class)->create();
$file = \Image::make(
UploadedFile::fake()->image($photo->file_name, 300, 300)
);
Storage::disk($photo->disk)
->put(
$photo->fullPath(),
$file
);
And in the controller I had something like this
return \Image::make(
Storage::disk($photo->disk)
->get(
$photo->fullPath()
)
)->response();
Solution
After investigation I realized that any file created by Image and saved by the Storage had a size of 0 octets.
After looking at all the solutions from this post and few hours after, I noticed everyone was using encode() but no one did mention it was that. So I tried and it worked.
Investigating a bit more, Image does, in fact, encode under the hood before saving.
https://github.com/Intervention/image/blob/master/src/Intervention/Image/Image.php#L146
So, my solution was to simple doing this
$file = \Image::make(
\Illuminate\Http\UploadedFile::fake()->image('filename.jpg', 300, 300)
)->encode();
\Storage::put('photos/test.jpg', $file);
testable in Tinker, It will create a black image

The cleanest solution I could find—using the native Storage facade—is the following. I can confirm that this works in Laravel 5.7, using intervention/image version 2.4.2.
$file = $request->file('avatar');
$path = $file->hashName('public/avatars');
$image = Image::make($file)->fit(300);
Storage::put($path, (string) $image->encode());
$url = Storage::url($path);

I do it this way:
Resize and save image somewhere (such as in the public folder).
Create a new File and pass it to Laravel filesystem functions (such as putFileAs).
Delete temporary intervention file
Note: Of course you can modify it according to your needs.
$file = $request->file('portfolio_thumb_image');
$image = Image::make($file);
$image->resize(570, 326, function ($constraint) {
$constraint->aspectRatio();
});
$thumbnail_image_name = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME).'.'.$file->getClientOriginalExtension();
$image->save(public_path('images/'.$thumbnail_image_name));
$saved_image_uri = $image->dirname.'/'.$image->basename;
//Now use laravel filesystem.
$uploaded_thumbnail_image = Storage::putFileAs('public/thumbnails/'.$portfolio_returned->id, new File($saved_image_uri), $thumbnail_image_name);
//Now delete temporary intervention image as we have moved it to Storage folder with Laravel filesystem.
$image->destroy();
unlink($saved_image_uri);

Make sure to add use Illuminate\Http\File; to top of your file for this to work, and read through the documentation section Automatic Streaming.
This assumes you want all jpegs
$path = $request->file('createcommunityavatar');
$resize = Image::make($path)->fit(300)->encode('jpg');
$filePath = $resize->getRealPath() . '.jpg';
$resize->save($filePath);
$store = Storage::putFile('public/image', new File($resize));
$url = Storage::url($store);
This is how I am doing it in my application with comments to help
// Get the file from the request
$requestImage = request()->file('image');
// Get the filepath of the request file (.tmp) and append .jpg
$requestImagePath = $requestImage->getRealPath() . '.jpg';
// Modify the image using intervention
$interventionImage = Image::make($requestImage)->resize(125, 125)->encode('jpg');
// Save the intervention image over the request image
$interventionImage->save($requestImagePath);
// Send the image to file storage
$url = Storage::putFileAs('photos', new File($requestImagePath), 'thumbnail.jpg');
return response()->json(['url' => $url]);

I've done it with following way, its simple and without any path confusion :
//Get file
$path= $request->file('createcommunityavatar');
// Resize and encode to required type
$img = Image::make($file)->fit(300)->encode('jpg');
//Provide own name
$name = time() . '.jpg';
//Put file with own name
Storage::put($name, $img);
//Move file to your location
Storage::move($name, 'public/image/' . $name);

Try updating the GD extension for the current php version.
If that doesn't help, try saving the resized image on local disk and using Storage::putFile.
You may delete the file once it has been uploaded to your storage path.
The second parameter to your putFile method is an instance of the Image Intervention class. You need to pass this as the second parameter to the putFile method.
$resize->save($absolutePath . 'small/' . $imageName);

You can't store an \Intervention\Image\Image object directly with the Laravel 5 filesystem. What you can do is resize the image from your request, and save it under the same tmp path. Then just store the uploaded (overwritten) file to the filesystem.
Code:
$image = $request->file('createcommunityavatar');
//resize and save under same tmp path
$resize = Image::make($image)->fit(300)->save();
// store in the filesystem with a generated filename
$store = $image->store('image', 'public');
// get url from storage
$url = Storage::url($store);

In my case the error was caused by calling the hashName method on an image instance.
//initially
$image = Image::make(request('image')->getRealPath());
$filename = $image->hashName();
//to fix the error
$image = Image::make(request('image')->getRealPath());
$filename = request('image')->hashName();

Related

Uploaded image taken from webcam is corrupted when uploaded to server

I am trying to upload image taken from webcam. I use canvas for showing that image and send base64_encoded json data by POST method to PHP and therefore in PHP decode the data and try to create an image by file_put_contents. But the uploaded image is corrupted every time. I used header("Access-Control-Allow-Origin: *") in PHP also. I also checked that the file_get_contents is enabled in the server. What may be the error? Please help.
My code is:
JS:
// Trigger photo take
var imagedata;
document.getElementById('snap').addEventListener('click', function() {
$('#canvas').show();
context.drawImage(video, 0, 0, 640, 480);
$('#upload-button').removeAttr("disabled");
var image = new Image();
image.src = canvas.toDataURL();
imagedata = image.src;
$('#webimage').val(imagedata);
});
PHP:
header("Access-Control-Allow-Origin: *");
$input_data = json_decode(trim(file_get_contents('php://input')), true);
$base = $input_data['image'];
$destination_directory = "uploads/";
$file_name = time();
$file_name = $file_name.'.png';
$targetPath = $destination_directory . $file_name;
$input_data = base64_decode($base);
file_put_contents($targetPath, $input_data);
Did you ever inspect the data url of an image? It looks like this:
image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs=
As you can see the actual base64 encoded representation of the binary content of the image starts after the comma. All you have to do is change:
$base = $input_data['image'];
to:
// php >= 5.4
$base = explode(',', $input_data['image'])[1];
// php < 5.4
list (, $base) = explode(',', $input_data['image']);
so the base64_decode function receives proper base64 string.
You are also missing proper validation inside your script:
validate JSON input was decoded properly
validate image key exists in the resulting array
validate base64_decode did decode the string
validate resulting image is a valid image (malicious users can embed PHP code for instance inside the image)
This way you can be 99% sure your server won't get compromised in the future.

Laravel S3 image upload creates a folder with the filename automatically

I'm using Laravel 5.4.*. I've this simple code in a helper file to upload images/gif in S3 bucket under a folder named say "instant_gifs/". The code is below:
if ( !function_exists('uploadFile') ) {
function uploadFile($fileContent, $fileName, $size='full', $disk='s3')
{
$rv = '';
if( empty($fileContent) ) {
return $rv;
}
if($size == 'full') {
dump($fileName);
$path = Storage::disk($disk)->put(
$fileName,
$fileContent,
'public'
);
}
if ( $path ) {
$rv = $fileName;
}
return $rv;
}
}
From the controller, I'm calling the helper method as below:
$file = $request->gif;
$file_name = 'instant_gifs/' . $user_id . '_' . time() . '_' . $file->getClientOriginalName();
$result = uploadFile($file, $file_name);
In the the $fileName parameter of the helper method, I'm providing the fileName as for example in this format:
"instant_gifs/83_1518596022_giphy.gif"
but after the upload, I see that the file gets stored under this folder
"vvstorage/instant_gifs/83_1518596022_giphy.gif/CRm1o1YEcvX3fAulDeDfwT7DIMCxOKG8WFGcA3lB.gif"
with a random file name
CRm1o1YEcvX3fAulDeDfwT7DIMCxOKG8WFGcA3lB.gif
Whereas, according to the code, it should get stored in this path:
"vvstorage/instant_gifs/83_1518596022_giphy.gif"
Doesn't get any explanation why this is happening. Any clue will be appreciated.
BucketName = vvstorage
Folder I'm mimicking = instant_gifs
After some research & testing, found the issue. put() method expects the 2nd parameter as the file contents or stream not the file object. In my code, I was sending the file as $file = $request->gif; or $file = $request->file('gif'); hoping that Storage class will implicitly get the file contents. But to get the expected result, I needed to call the helper method from the controller as below. Notice the file_get_contents() part.
$file = $request->gif;
$file_name = 'instant_gifs/' . $user_id . '_' . time() . '_' . $file>getClientOriginalName();
$result = uploadFile( file_get_contents($file), $file_name );
Now, I got the image correctly stored under the correct path for example in /instant_gifs/9_1518633281_IMG_7491.jpg.
Now, let me compare/summarize the available methods for achieving the same result:
1) put():
$path = Storage::disk('s3')->put(
'/instant_gifs/9_1518633281_IMG_7491.jpg', #$path
file_get_contents($request->file('gif')), #$fileContent
'public' #$visibility
Got it stored in /vvstorage/instant_gifs/9_1518633281_IMG_7491.jpg
2) putFileAs(): To achieve the same thing withputFileAs(), I needed to write it as below. 1st parameter expects the directory name, I left it blank as I'm mimicking the directory name in s3 through the filename.
$path = Storage::disk('s3')->putFileAs(
'', ## 1st parameter expects directory name, I left it blank as I'm mimicking the directory name through the filename
'/instant_gifs/9_1518633281_IMG_7491.jpg',
$request->file('gif'), ## 3rd parameter file resource
['visibility' => 'public'] #$options
);
Got it stored in /vvstorage/instant_gifs/9_1518633281_IMG_7491.jpg
3) storeAs():
$path = $request->file('gif')->storeAs(
'', #$path
'/instant_gifs/9_1518633281_IMG_7491.jpg', #$fileName
['disk'=>'s3', 'visibility'=>'public'] #$options
);
Got it stored in /vvstorage/instant_gifs/9_1518633281_IMG_7491.jpg
Extras::
4) For storing Thumbnails through put(). Example of stream() ...
$imgThumb = Image::make($request->file('image'))->resize(300, 300)->stream(); ##create thumbnail
$path = Storage::disk('s3')->put(
'profilethumbs/' . $imgName,
$imgThumb->__toString(),
'public'
);
Hope that it helps someone.
1.) Why is there vvstorage in the url?
It is appending that route because your root folder inside of your configuration for S3 is set as vvstorage, so whenever you upload to S3 all files will be prepended with vvstorage.
2.) Why random name even when you passed the name of the file?
Because when using put the file will get a unique ID generated and set as it's file name so no matter what you pass, it won't save the file under the name you wanted. But if you use putFileAs then you can override the default behaviour of put and pass a name of the file.
Hope this clarifies it

PHP - Simple script for image compression with pngquant

:) i found this 1 line of code in another post which successfully compresses the image using pngquant. the thing is, it outputs the optimised image with a different name (obviously to preserve the original).
im trying to find a way to:
a) add a minimum quality parameter of 60
b) use an if/else statement to to allow the user to choose to overwrite the existing file or output a new optimised image (of a user specified name)
thank you! ntlri - not to long read it
<?php system('pngquant --quality=85 image.png'); ?>
so what i have tried is the following.. for some reason the single quotes need to be double quotes to parse the variables correctly..
<?php
$min_quality = 60; $max_quality = 85;
$keep_original = 'dont_keep';
if ($keep_original == 'keep') {
$image_name = 'image.png';
$path_to_image = 'images/' . $image_name;
$new_file = 'image2.png';
$path_to_new_image = 'images/' . $new_file;
// don't know how to output to specified $new_file name
system("pngquant --quality=$min_quality-$max_quality $path_to_image");
} else {
$image_name = 'image.png';
$path_to_image = 'images/' . $image_name;
// don't know if you can overwrite file by same name as additional parameter
system("pngquant --quality=$min_quality-$max_quality $path_to_image");
// dont't know how you get the name of the new optimised image
$optimised_image = 'images/' . $whatever_the_optimised_image_is_called;
rename($optimised_image, $image_name);
unlink($optimised_image);
}
?>
from the docs of this program :
The output filename is the same as the input name except that\n\ it
ends in \"-fs8.png\", \"-or8.png\" or your custom extension
so , for this question:
// don't know how to output to specified $new_file name
system("pngquant --quality=$min_quality-$max_quality $path_to_image");
to choose a new name, assume you are compress image name.png :
--ext=_x.png
this will create new image called name_x.png
so , your $new_file would be just a suffix ,
$new_file = '_x.png'; // to choose new file name name_x.png
// don't know if you can overwrite file by same name as additional
parameter
as mentioned in the program docs , the new file name will be suffixed by either -fs8.png or -or8.png , so you may rename the file which will produced with this suffix , OR simply set the --ext option to : .png and this will append to the original file
--ext=.png
for more details, check the repository
i spoke to pornel whos the chappie that developed pngquant. its actually a lot simpler than all that i wrote that before...
! important - it is very important to use escapeshellarg() else people can take over your server by uploading a file with a special filename apparently.
$image_name = 'image.png';
$target_file = 'images/' . $image_name;
$existing_image = 'image.png'; // image already on server if applicable
$keep = 'keep';
$target_escaped = escapeshellarg($target_file);
if ($keep == 'keep') {
// process/change output file to image_compressed.png keeping both images
system("pngquant --force --quality=70 $target_escaped --ext=_compressed.png");
$remove_ext = substr($newFileName, 0 , (strrpos($newFileName, ".")));
// $new_image is just the name (image_compressed.png) if you need it
$new_image = $remove_ext . '_compressed.png';
// remove old file if different name
if ($existing_image != $newFileName) {
$removeOld = '../images/' . $existing_image;
unlink($removeOld);
} // comment out if you want to keep existing file
} else {
// overwrite if file has the same name
system("pngquant --force --quality=70 $target_escaped --ext=.png");
// remove old file if different name
if ($existing_image != $newFileName) {
$removeOld = '../images/' . $existing_image;
unlink($removeOld);
}
$new_image = $newFileName;
}
to override same name use this command
pngquant.exe --ext=.png --force input.png
so the output name will remain input.png

PHP : Updating new image still showing the old image

So I am creating a new folder everytime if folder not exists using mdkir.
$dir_name = '../../assets/contestant_double/'.$event_id.'eventPortfolio/';
if(!is_dir($dir_name)){
mkdir('../../assets/contestant_double/'.$event_id.'eventPortolfio/', 0777, true);
}
Now I am trying to update my uploaded image on that folder. And its working fine when i look on that designated directory
$static_name = $event_id."d".$getThed1d2."_contestant_";
$extension = pathinfo($name, PATHINFO_EXTENSION);
$theRealName = $static_name.$lastDigit.".".$extension;
echo $theRealName;
But when i try to get the image why is it still showing up the old image instead the new one?
<img class="img_here_edit" src="../assets/contestant_double/'.$event_id.'eventPortfolio/'.$soc_sql['images'].'" width="100%" >
I doubt but I'm not sure that its on my new created directory mdkir which has 0777 because those of my code are working fine in my other regular folder? How do i solve this
use unlink(''); function to old image it will solve your problem
Why not delete the old image and create and upload a new image with the same name?.
Try sending a random id at the end of image url to get the latest image like this :
<img class="img_here_edit" src="../assets/contestant_double/'.$event_id.'eventPortfolio/'.$soc_sql['images'].'?'.time().'" width="100%" >
what you see is the picture on browser cache, so you have to clear cache of browser. however, you wont do that every time you replace a picture either your users, it is too tedious and unpractical. I had same sutation and what i did was the follown. create filename + time().jpg like this.
So by the time i was going to replace old picture, i loaded from folder, delete it an then place new one. look...
function uploadphoto( $id, $ref ) {
$find = $ref.'_';
$data = $_POST[ 'image' ];
list( $type, $data ) = explode( ';', $data );
list( , $data ) = explode( ',', $data );
$data = base64_decode( $data );
$imageName = $ref.'_'.time().'.jpg'; // new picture name
foreach(glob('uploads/'.$find.'*.*') as $filename){
$localizacion = $_SERVER['DOCUMENT_ROOT'].'/'. $filename ;
chmod('./uploads', 0777);
unlink($localizacion);
}
file_put_contents( 'uploads/' . $imageName, $data );
}

Rotate an image on same page

I want to rotate an uploaded and retrieved image from one location. Yes i am almost done. But the problem is, due to header("content-type: image/jpeg") the page redirected to another/or image format. I want to display it in same page as original image in. Here my code..
$imgnames="upload/".$_SESSION["img"];
header("content-type: image/jpeg");
$source=imagecreatefromjpeg($imgnames);
$rotate=imagerotate($source,$degree,0);
imagejpeg($rotate);
i also did with css property.
echo "<img src='$imgnames' style='image-orientation:".$degree."deg;' />";
But anyway my task is to done only with php. Please guide me, or give any reference you have
thanks advance.
<?php
// Okay, so in your upload page
$imgName = "upload/".$_SESSION["img"];
$source=imagecreatefromjpeg($imgName);
$rotate=imagerotate($source, $degree,0);
// you generate a PHP uniqid,
$uniqid = uniqid();
// and use it to store the image
$rotImage = "upload/".$uniqid.".jpg";
// using imagejpeg to save to a file;
imagejpeg($rotate, $rotImage, $quality = 75);
// then just output a html containing ` <img src="UniqueId.000.jpg" />`
// and another img tag with the other file.
print <<<IMAGES
<img src="$imgName" />
<img src="$rotName" />
IMAGES;
// The browser will do the rest.
?>
UPDATE
Actually, while uniqid() usually works, we want to use uniqid() to create a file. That's a specialized usage for which there exists a better function, tempnam().
Yet, tempnam() does not allow a custom extension to be specified, and many browsers would balk at downloading a JPEG file called "foo" instead of "foo.jpg".
To be more sure that there will not be two identical unique names we can use
$uniqid = uniqid('', true);
adding the "true" parameter to have a longer name with more entropy.
Otherwise we need a more flexible function that will check if a unique name already exists and, if so, generate another: instead of
$uniqid = uniqid();
$rotImage = "upload/".$uniqid.".jpg";
we use
$rotImage = uniqueFile("upload/*.jpg");
where uniqueFile() is
function uniqueFile($template, $more = false) {
for ($retries = 0; $retries < 3; $retries++) {
$testfile = preg_replace_callback(
'#\\*#', // replace asterisks
function() use($more) {
return uniqid('', $more); // with unique strings
},
$template // throughout the template
);
if (file_exists($testfile)) {
continue;
}
// We don't want to return a filename if it has few chances of being usable
if (!is_writeable($dir = dirname($testfile))) {
trigger_error("Cannot create unique files in {$dir}", E_USER_ERROR);
}
return $testfile;
}
// If it doesn't work after three retries, something is seriously broken.
trigger_error("Cannot create unique file {$template}", E_USER_ERROR);
}
You need to generate the image separately - something like <img src="path/to/image.php?id=123">. Trying to use it as a variable like that isn't going to work.

Categories