I'm trying to use pngquant with PHP using the following code (source):
<?php
function compress_png($path_to_png_file, $max_quality = 90)
{
if (!file_exists($path_to_png_file)) {
throw new Exception("File does not exist: $path_to_png_file");
}
// guarantee that quality won't be worse than that.
$min_quality = 60;
// '-' makes it use stdout, required to save to $compressed_png_content variable
// '<' makes it read from the given file path
// escapeshellarg() makes this safe to use with any path
// maybe with more memory ?
ini_set("memory_limit", "128M");
// The command should look like: pngquant --quality=60-90 - < "image-original.png"
$comm = "pngquant --quality=$min_quality-$max_quality - < ".escapeshellarg( $path_to_png_file);
$compressed_png_content = shell_exec($comm);
var_dump($compressed_png_content);
if (!$compressed_png_content) {
throw new Exception("Conversion to compressed PNG failed. Is pngquant 1.8+ installed on the server?");
}
return $compressed_png_content;
}
echo compress_png("image-original.png");
The function is supposed to retrieve the output of the shell_exec function. With the output i should be able to create a new png file, however the output of the shell_exec in the browser is corrupt: �PNG.
Note: the execution of the command is succesfully executed in the console without PHP (pngquant --quality=60-90 - < "image-original.png")
If I execute the php code from the console, i get the following message:
error: failed writing image to stdout (16)
I've searched everywhere without any solution, can someone help me or have any idea of what could be causing the problem ?
The php-pngquant wrapper allow you to retrieve the content from the generated image by PNGQuant directly into a variable using the getRawOutput method:
<?php
use ourcodeworld\PNGQuant\PNGQuant;
$instance = new PNGQuant();
$result = $instance
->setImage("/image/to-compress.png")
->setQuality(50,80)
->getRawOutput();
// Result is an array with the following structure
// $result = array(
// 'statusCode' => 0,
// 'tempFile' => "/tmp/example-temporal.png",
// 'imageData' => [String] (use the imagecreatefromstring function to get the binary data)
//)
// Get the binary data of the image
$imageData = imagecreatefromstring($result["imageData"]);
// Save the PNG Image from the raw data into a file or do whatever you want.
imagepng($imageData , '/result_image.png');
Under the hood, the wrapper provides as the output argument in PNGQuant a temporary file, then pngquant will write the compressed image into that file and will retrieve its content in the result array. You can still verify the exit code of PNGQuant with the statusCode index of the result array.
Related
I want to convert a folder of jpeg images to webp format in another folder. I'm using the imagewebp function in the GD library. The code just halts when the function is called. Although the file is actually created it is of zero length. No errors are thrown.
gd_info shows version 2.1 with WebP support as 1.
php version 5.6
Everything works if I change to imagepng() so the library is installed and working.
I've tried on individual small images in case it was a memory issue but still has the same problem.
Also tried imagecreatefromjpeg instead of imagecreatefromstring but same.
Removed the quality filter to default (eg omitted the 65) but the same effect.
I also tried outputting a single file directly to the browser. This works with imagepng, but I get a 404 error with imagewebp
$jpgfiles = glob ('jpegs/*.jpg');
echo "<p>" . sizeof( $jpgfiles ) . " files found. </p>"; // Shows 7 files
foreach($jpgfiles as $j) {
echo "<p>Found $j</p>"; // Shows first found file
$filename = substr( $j, 6, -4) . '.webp'; // Keep the same filename but change extension
echo "<p>$filename</p>";
$jpgimage = imagecreatefromstring( file_get_contents( $j ) );
if( !$jpgimage ) die ("Failed on imagecreatefromstring");
if( $webpimage = imagewebp( $jpgimage, 'webps/' . $filename, 65 )) echo "$filename created"; // This is never echo'd
else die ("Failed on imagewebp"); // Doesn't die either
imagedestroy ( $jpgimage );
}
I expected the images to be created but program execution stops. The error log does not contain any information. The die command is never invoked. The webp file is created but is of zero length.
I've already asked a couple of questions regarding this and each step gets me closer however it still doesnt work as intended.
I want to upload an image and write it to the textfile, then when i upload another image that will be written to the end and so on so forth. So ultimately you'll have a long file with lots of images.
As far as i can tell my code should work but it doesn't. Here is a link to the site website for testing. Testing it maybe useful and below is the code.
It also always creates an empty element at the end of the array as you'll see from testing the site.
The PHP:
$sFileName = "imgDB.txt";
for ($i=0 ; $i < count($_FILES) ; $i++) {
move_uploaded_file(
$_FILES['file-'.$i]['tmp_name'],
"img/". $_FILES['file-'.$i]['name']
);
}
$sImgs = file_get_contents($sFileName); //gets a string from the file.
if (json_decode($sImgs, true) != false) {
$ajImgs = json_decode($sImgs, true);
} else {
$ajImgs = array();
}
$aOutPut = array_merge ($ajImgs, $_FILES);
$aSendToFile = json_encode(
$aOutPut,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
file_put_contents($sFileName, $aSendToFile);
Some remarks
if the move_uploaded_file call is not protected further, this allows to upload any files, including script files - this opens a security vulnerability in your application - check for MIME-type and file-extension to avoid this
json_decode returns null if the input value is empty or cannot be decoded - not false like in your code
appending to an array is not done with array_merge, this just overrides the properties of $_FILES of the previous execution - use $aOutPut[] = $_FILES; instead
I want download an image from AWS S3 and process it with php. I am using "imagecreatefromjpeg" and "getimagesize" to process my image but it seem that
Storage::disk('s3')->get(imageUrlonS3);
retrieve the image in binary and is giving me errors. This is my code:
function createSlices($imagePath) {
//create transform driver object
$im = imagecreatefromjpeg($imagePath);
$sizeArray = getimagesize($imagePath);
//Set the Image dimensions
$imageWidth = $sizeArray[0];
$imageHeight = $sizeArray[1];
//See how many zoom levels are required for the width and height
$widthLog = ceil(log($imageWidth/256,2));
$heightLog = ceil(log($imageHeight/256,2));
//more code here to slice the image
.
.
.
.
}
// ex: https://s3-us-west-2.amazonaws.com/bucketname/image.jpg
$content = Storage::disk('s3')->get(imageUrlonS3);
createSlices($content);
What am I missing here ?
Thanks
I think you are right in your question what the problem is - the get method returns the source of the image of itself, not the location of the image. When you pass that to createSlices, you're passing the binary data, not its file path. Inside of createSlices you call imagecreatefromjpeg, which expects a file path, not the image itself.
If this indeed the case, you should be able to use createimagefromstring instead of createimagefromjpeg and getimagesizefromstring instead of getimagesize. The functions createimagefromstring and getimagesizefromstring each expects the binary string of the image, which I believe is what you have.
Here's the relevant documentation:
createimagefromstring - http://php.net/manual/en/function.imagecreatefromstring.php
getimagesizefromstring - http://php.net/manual/en/function.getimagesizefromstring.php
Resulting code might look something like this:
function createSlices($imageData) {
$im = imagecreatefromstring($imageData);
$sizeArray = getimagesizefromstring($imageData);
//Everything else can probably be the same
.
.
.
.
}
$contents = Storage::disk('s3')->get($imageUrlOnS3);
createSlices($contents);
Please note I haven't tested this, but I believe from what I can see in your question and what I read in the documentation that this might just do it.
I am trying to make a preview from pdf that users must to upload. I am using 1and1 hosting server, so I don´t have total control about what to install, and I don´t know how to install ImageMagick. I followed these steps and I was using this code (that is working in a different project using a VPS):
private function preViewPDF($filename)
{
$img_path = './assets/uploads/previews';
$file_name = explode(".", $filename)[0].".jpg";
$dir = './assets/upload/files/';
$img = new Imagick($dir."/".$filename.'[0]');
$img->setImageFormat('jpg');
$img->writeImage($img_path."/".$file_name);
return "previews/".$id.$type."/".$file_name;
}
After try that and get Imagick Class not Found Exception, I am trying to convert using exec command:
Actual code
private function preViewPDF($filename)
{
$file_name = explode(".", $filename)[0].".jpg";
$dir = getcwd().'/assets/uploads/files/';
if(file_exists($dir."/".$filename))
{
exec("convert ".$dir."/".$filename.'[0]'." ".$dir."/".$file_name, $output, $return_var);
var_dump($output);
echo "<br>";
var_dump($return_var);
}
else echo "no file";
echo "<br>".$dir."/".$filename.'[0]'."<br>";
echo "<br>".$file_name."<br>";
}
The var_dump($output); throws an empty array. And the $return_var is 1... general error :(
If I change the value between [] (the number of the page I want to convert) $output throws:
array(3) {
[0]=> string(0) ""
[1]=> string(70) "Requested FirstPage is greater than the number of pages in the file: 1"
[2]=> string(53) " No pages will be processed (FirstPage > LastPage)."
}
So... any ideas what am I doing wrong?? Thank you.
Extra Data
Only two little things more (maybe obvious). The first, if I emulate the order on a SSL connection it works (I get a image from a pdf). And second, permissions are not the reason (I tried to create and write a file -with fopen and fwrite- and it works).
EDIT
First, an explanation about my actual code:
$file_name = explode(".", $filename)[0].".jpg";
This line is because the extension of $filename is .pdf, so I need remove this part and concatenate the right extension .jpg (from hello.pdf I get hello.jpg).
$dir = getcwd().'/assets/uploads/files/';
This is the folder where the pdf is uploaded and the jpg preview must be saved.
if(file_exists($dir."/".$filename))
I put this line, simply because I though that the uploading of the pdf wasn´t finished and this was the reason that doesn´t work.
exec("convert ".$dir."/".$filename.'[0]'." ".$dir."/".$file_name, $output, $return_var);
This is the line where the command convert is executed... but doesn´t work.
Second thing is a new simple code I just tried:
if(file_exists("./DpRPJTmfSArPRuGZrOddLendfbhgHTrydwukMRvOMuSzVMDuBb.pdf"))
{
exec("convert ./DpRPJTmfSArPRuGZrOddLendfbhgHTrydwukMRvOMuSzVMDuBb.pdf[0] ./DpRPJTmfSArPRuGZrOddLendfbhgHTrydwukMRvOMuSzVMDuBb.jpg", $output, $return_var);
var_dump($output);
echo "<br>";
var_dump($return_var);
}
else echo "no hay fichero";
The $output is empty, and the $return_var is 1.
Forget all the dross and start simple with the file in the same folder as the code to see if Imagemagick is working.
convert input.pdf output.jpg
Also you have so many variables etc. in the Imagemagick code it is hard to read it.
I am also confused by your code and I would create the filename and path outside the convert code and you can echo it to ensure it contains what you expect.
This looks wrong:
$filename.'[0]'
I would try:
$filename[0]
I assume your pdf has more than one page?
Edit
Try this code - it has a different way of displaying any errors and allows you to view the contents of your command if you have lots of variables etc.
$error=array();
echo "<pre>";
$cmd = "./DpRPJTmfSArPRuGZrOddLendfbhgHTrydwukMRvOMuSzVMDuBb.pdf[0] ./DpRPJTmfSArPRuGZrOddLendfbhgHTrydwukMRvOMuSzVMDuBb.jpg";
// You can use this line to see what the $cmd ontains when using a lot of variables
echo $cmd;
exec("$cmd 2>&1", $error);
echo "<br>".print_r($error)."<br>";
echo "</pre>";
I make preview with this:
exec('convert -density 300 -trim "'.$file.'" -resize 600 -quality 85 -colorspace RGB -background white "'.$destination.'" &', $output, $return_var);
Where $file is tue original and $destination is the name of image.
With the & at the end, each image will be named image-0.jpg, image-1.jpg..
$return_var == 0 when all is OK
I have an image that is sent from an iPad app to an SQL database. I can retrieve this image and display in a web page using the following php:
$img = base64_encode($row['photoData']);
echo "<img src=\"data:image/jpg;charset=utf8;base64, $img\"/>";
This displays fine. What I want to do now is put this image into a PDF document using FPDF however I am struggling to do this.
This:
$img = base64_encode($row['photoData']);
$pdf->Image($img);
give this error:
FPDF error: Image file has no extension and no type was specified:
So I tried this (although I realise I will then have to look at how to get the size of the image sorted):
$pdf->Image($img, 20, 20, 20, 20 'JPG');
which give me:
FPDF error: Missing or incorrect image file:
What is the correct way to do this?
Or would it be easier to temporarily save the image to the server and then place the saved image into the PDFdoc?
As mentioned in the comments above this is possible by using a stream ("data url") to hand over the image data to the fpdf library without writing physical files to disk:
<?php
// load the 'fpdf' extension
require('fpdf.php');
// just for demonstration purpose, the OP gets the content from a database instead
$h_img = fopen('img.jpg', "rb");
$img = fread($h_img, filesize('img.jpg'));
fclose($h_img);
// prepare a base64 encoded "data url"
$pic = 'data://text/plain;base64,' . base64_encode($img);
// extract dimensions from image
$info = getimagesize($pic);
// create a simple pdf document to prove this is very well possible:
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello Image!');
$pdf->Image($pic, 10, 30, $info[0], $info[1], 'jpg');
$pdf->Output();
If this is a good advice is another question, this is merely meant to prove that this is possible...
According to the Docs FPDF::Image accepts a filename as the first argument, not a binary blob.
If you want to use FPDF specifically, save the image to a temporary file first, and then pass that to FPDF::Image.
To do that, something like this should work:
$tmpFile = tempnam(sys_get_temp_dir(), 'fpdfimg');
if (file_put_contents($tmpFile, $row['photoData'])) {
$fpdf->Image($tmpFile);
// save/display image
unlink($tmpFile);
}
Alternatively, if you want to just serve the image as a PDF (with no other content) you could use Imagick:
$im = new \Imagick();
$im->readImageBlob($row['photoData']);
$im->setImageFormat('pdf');
header('Content-Type: application/pdf');
echo $im;
Since FPDF cannot use base64 data to produce images on the PDF, I would recommend saving the file to the disk permanently as opposed to writing a temp file for every PDF operation.
This will save you a lot of I/O overhead.
Assuming your table has unique photo_id or photo_name to accompany photoData then you can use something like this to create your images and use them in FPDF.
I will also assume you have a last_update and photo_extension column.
<?php
$path = '/path/to/fpdf/images/';
$filename = $row['photo_id'].'.'.$row['photo_extension'];
$filepath = $path.$filename;
// If a physical file is not available then create it
// If the DB data is fresher than the file then make a new file
if(!is_file($filepath) || strtotime($row['last_update']) > filemtime($filepath))
{
$result = file_put_contents($filepath, $row['photoData']);
if($result === FALSE)
{
die(__FILE__.'<br>Error - Line #'.__LINE__.': Could not create '.$filepath);
}
}
$pdf->Image($filepath);
If you plan on updating the photoData which is stored in your DB then you will have to make sure to also have a timestamp column and compare that timestamp against the filemtime($filepath) of the image on your disk.
Another solution for this ;)
Make a new php by copying and pasting this (piece of fpdf's code edited):
require('fpdf.php');
class DATAIMAGE extends FPDF
{
protected function _parsedata($file)
{
// Extract info from a JPEG file
$a = getimagesizefromstring($file);
if(!$a)
$this->Error('Missing or incorrect image file: '.$file);
if($a[2]!=2)
$this->Error('Not a JPEG file: '.$file);
if(!isset($a['channels']) || $a['channels']==3)
$colspace = 'DeviceRGB';
elseif($a['channels']==4)
$colspace = 'DeviceCMYK';
else
$colspace = 'DeviceGray';
$bpc = isset($a['bits']) ? $a['bits'] : 8;
return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$file);
}
}
Then call this php instead of fpdf.php in your main php.
You'll now be able to display an image simply by adding 'data' to the end of the function:
$pdf->Image($mysqlrow["blob"],0,0,40,0,'data');