PHP - "FPDF error: Not a PNG file", but the image is - php

I'm currently running into a very odd issue with fpdf. I found a similar question with no answer: not a PNG file in FPDF. I have an image uploaded through a browser to my file server, and then pulled into a fpdf report. When this image is a png, I get the error: "FPDF error: Not a PNG file". I don't get any errors when the uploaded image is a jpg. This issue seemingly appeared overnight a few weeks ago.
Even stranger, it's only happening with new png's being uploaded. There was a png in a report that was generating fine. When I downloaded that png from the system and re-uploaded it, the errors appeared again.
Here are some of the steps I've taken while attempting to troubleshoot the issue:
I've made sure the image is actually a png (through its properties).
Nothing has changed with the way I've been saving the images, but here's the code:
$original = $time."_".$name."_o.".$extension;
$thumbnail = $time."_".$name."_t.".$extension;
include('SimpleImage.php');
$image = new SimpleImage();
$image->load($_FILES['file']['tmp_name']);
$image->save($A_path."images/".$original);
$image->resizeToHeight(200);
$image->save($A_path."images/thumbs/".$thumbnail);
$photo = "images/".$original;
$thumb = "images/thumbs/".$thumbnail;
I've checked to see if their were any changes to the PNG format or FPDF updates, with no luck.
I've converted a jpg that works into a png through gimp.
Converting a png to a jpg through gimp and then uploading the jpg to the system does not generate any errors.
WORKAROUND- I've gone ahead and converted png's to jpg's on save, rather than re-encoding the image. Thanks for the help.

Fixed it by changing the picture format manually to JPG and then repeating the process.

The error message indicates that there is something wrong with the first eight bytes of the file (the "png signature").
Use "od -c | head -1" to inspect the first 16 bytes. Every PNG file
begins with these:
211 P N G \r \n 032 \n \0 \0 \0 \r I H D R
If you prefer, use "xxd file.png | head -1" and expect to see this:
0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
These 16 bytes are the PNG signature and the length and name of the first chunk. The first 8 bytes are the format
name, plus newlines (linefeeds) and carriage returns that are designed
to detect various transmission errors. The next 8 bytes are the beginning
of the IHDR chunk, which must be length=13 expressed as a 4-byte integer, and the name="IHDR".
See the PNG specification for details.

Check the depth of the image. FPDF supports 24bit depth (i'm not sure about 32bit depth), neither does it support alpha channel.
I'd try to reencode to png with ImageMagick (or paint.net under windows).
convert input.png -depth 8 +matte output.png

I found a crud solution that works for me but this will take little more space on your host. But you can determine which extension worked and delete the rest However its worth it.
First take the file contents and convert them to base64_encode.
Create an array of the file formats you want the file to be in "png","jpg","jpeg" and decode the base64 image looping through the file extensions. This recreates the image with three file extensions in your folder.
Use the
try{
}catch (Exception $e) {
}
to loop trough and find which image extension works and use it.
Here is my full code
$base64 = base64_encode(file_get_contents("full/domain/path/to/image"));
$f_ex = array('.png', '.jpg', '.jpeg'); //array of extensions to recreate
$path = "path/to/new/images"; //this folder will have there images.
$i = 0;
$end = 3;
while ($i < $end) {
$data = base64_decode($base64); //decode the image file from base64
$filename = "unique_but_memorable_filename(eg invoice id)" . $f_ex[$i]; //$f_ex loops through the file extensions
file_put_contents($path . $filename, $data); //we save our new images to the path above
$i++;
}
Inside your FPDF where your image is set, we loop through the images we recreated and see which one works and stop there
try {
$filename = "remember_unique_but_memorable_filename(eg invoice id)" . $f_ex[0];
$logo = "your domail.com where image was stored" . '/' . $path . $filename;
$pdf->Image($logo, 10, 17, 100, 100);
//Put your code here to delete the other image formats.
} catch (Exception $e) {
try {
$filename = "remember_unique_but_memorable_filename(eg invoice id)" . $f_ex[1];
$logo = "your domail.com where image was stored" . '/' . $path . $filename;
$pdf->Image($logo, 10, 17, 100, 100);
//Put your code here to delete the other image formats.
} catch (Exception $e) {
try {
$filename = "remember_unique_but_memorable_filename(eg invoice id)" . $f_ex[2];
$logo = "your domail.com where image was stored" . '/' . $path . $filename;
$pdf->Image($logo, 10, 17, 100, 100);
//Put your code here to delete the other image formats.
} catch (Exception $e) {
//if all the three formats fail, lets see the error
echo 'Message: ' . $e->getMessage();
}
}
}

Related

php imagick convert pdf to png high quality

I'm trying to convert a PDF to a high quality PNG via Imagick, but the file keeps coming out fuzzy. Currently, I'm running the following options but can't find the right flags to get a clear PNG out of the conversion. The original PDF file is 8.5 x 11. Suggestions? Thanks!
$image = new \Imagick(storage_path('app/'.$path));
$image->setResolution( 200, 200 );
$image->scaleImage(1700,2200);
$image->setImageFormat( "png32" );
$image->writeImage(storage_path('app/'.$split[0].'.png'));
You need to set the resolution before reading the image because the image is rasterised when read, so it doesn't help to set the resolution afterwards - it's too late!
Try along these lines:
$imagick = new Imagick();
$imagick->setResolution(288,288);
$imagick->readImage('someFile.pdf');
$imagick_i = new Imagick();
$imagick_i->setResolution( 595, 842 );
$imagick_i->readImageblob($blob);
$imagick_i->setImageFormat( "png32" );
foreach ($imagick_i as $auxiliaryvalue) {
echo '<img src="data:image/png;base64,' . base64_encode($auxiliaryvalue->getimageblob()) . '" /><br>';
}

Get Exif image orientation from Base-64 encoded image too slow

I'm using this code to get the Exif orientation of a JPEG image from its base64 encoded data.
$exif = exif_read_data('data://image/jpeg;base64,' . $base64EncodedImageData);
$orientation = $exif['Orientation'];
However this code is slow especially when dealing with HD images. Is there any way to get the orientation faster?
It seems to be much faster when passing a actual file path instead of the base-64 encoded string, but the image is not on the disk and the base-64 encoded string is all I have.
The fact you're loading in the entire blob is why it is taking so long, so you really just need to load in the part of the image that contains the exif data.
When you give exif_read_data a file path instead of a blob, it only reads the necessary information by default. If that is simply not an option, then you may need an ugly workaround / hack.
For example, you could specify a hard-coded value:
$image = base64_encode(file_get_contents('test.jpg'));
echo strlen($image) . '<br />'; // 81684
// only read the first 30000 characters
$exif = exif_read_data('data://image/jpeg;base64,' . substr($image, 0, 30000));
echo '<pre>';
var_dump($exif);
echo '</pre>';
Or you could increment it dynamically until the appropriate size is found:
$image = base64_encode(file_get_contents('test.jpg'));
$read = '8192';
// this will produce errors, so i am suppressing them
while (!$exif = #exif_read_data('data://image/jpeg;base64,' . substr($image, 0, $read))) {
$read += $read;
}
echo '<pre>';
var_dump($exif);
echo '</pre>';
There may be a better solution but I don't know what it is. Hope that helps.

How to processing an image downloaded from AWS S3 with Laravel 5?

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.

Image from database into PDF using FPDF

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');

PDF to JPG Imagic page selection

Loads of answers on how to do it for a command line
convert /path/to/file/file.pdf[3] output.jpg
great... but what if I am using in memory processing, I am generating PDF with PDFlib and then output its buffer to a function that I want to generate jpg preview of selected page. How? My code :
[...]
$buf = $pdf->get_buffer();
//$buff is just a PDF stored in a string now.
$im = new Imagick();
$im->readimageblob($buf);
$im->setImageFormat("jpg");
$im->setimagecompressionquality(60);
$len = strlen($im);
header("Content-type: image/jpeg");
header("Content-Length: $len");
header("Content-Disposition: inline; filename=test.jpg");
echo $im;
This creates a jpeg but always returns last page of the PDF. I want to be able to choose which one will be converted. Is it doable without saving temporary files and using command line (exec('convert /path/to/file/file.pdf[3] output.jpg')) syntax?
Let me add that I tried
$im->readimageblob($buf[2]);
and it did not work :)
For the ones who is still searching for solution of reading specific page number from blob, please check this question Creating array populated by images from a PDF using PHP and ImageMagick
$img_array = array();
$im = new imagick();
$im->setResolution(150,150);
$im->readImageBlob($pdf_in);
$num_pages = $im->getNumberImages();
for($i = 0;$i < $num_pages; $i++)
{
$im->setIteratorIndex($i);
$im->setImageFormat('jpeg');
$img_array[$i] = $im->getImageBlob();
}
$im->destroy();
I'm loading the PDF binary into memory from Amazon S3 and then selecting the specific page I want using setIteratorIndex() followed by getImage()
function get_image_from_pdf($pdf_bytes, $page_num){
$im = new \Imagick();
$im->setResolution(150, 150);
$im->readImageBlob($pdf_bytes);
$im->setIteratorIndex($page_num);
$im = $im->getImage();
$im->setImageFormat('png');
return $im->getImageBlob();
}
version 3.0.1
being on the last image or first image in imagic object
$image_obj = new Imagick("test.pdf"); then you on last image in $image_obj
if you use
fp_pdf = fopen("test.pdf", 'rb');
$image_obj = new Imagick();
$image_obj -> readImageFile($fp_pdf);
then you on the first image in $image_obj
In second case to switch to last image you can do
fp_pdf = fopen("test.pdf", 'rb');
$image_obj = new Imagick();
$image_obj -> readImageFile($fp_pdf,2); // 2 can be any positive number?
then you on the last image in $image_obj
echo $image_obj->getNumberImages() // Returns the number of images in the object
then
if ($image_obj->hadPreviousImage)
$image_obj->previousImage() //Switch to the previous image in the object
}
or
if ($image_obj->hasNextImage()) {
$image_obj->nextImage()) //Switch to the next image in the object
}
e.g. if you have 6 images total and you need 4th then do from the end
$image_obj->previousImage()
$image_obj->previousImage()
$image_obj->setImageFormat("png");
header("Content-Type: image/png");
echo $image_obj;
EDIT: Another find is that you can
foreach($image_obj as $slides) {
echo "<br>".$Obj_img->getImageWidth();
//or wehatever you need to do.
}
EDIT 2: Very simple solution would be to use this function $image_obj->setIteratorIndex(4) count starts with zero.
It's not good news unfortunately, but I can definitively say that, as of time of writing, the ImageMagick (and PHP libraries) don't support the page notation that you're trying to use. (For people from the future finding this: I'm checking php-imagick-3.0.1 and imagemagick-6.6.0.4).
I'm trying to do the exact same thing as you, and I've just spent the last few hours trawling through the source, trying to figure out what it does and how it gets the pages, and it looks like it simply won't use it when reading from a stream (ie. the readBlob() call).
As such, I'm just going to be putting it in a temporary file and reading it from there instead. Not as elegant, but it'll work.

Categories