Using PHP get png type - php

In a linux console if you use identify -verbose file.png it gives you a full print out of the file. Is there anyway to get that same information in php?
Specifically I need the "Type" line which states the type of png is it. TrueColorAlpha, PaletteAlpha, ect..
Why?
Operating system corrupted and in attempting to rebuild a structure of more than 5 million images 2 million of them were dumped into lost and found. Some of them are system created and some of them were uploaded. If I am able to find a difference between the two, it would save tremendous amounts of time.

From these articles I wrote a simple function than can give you the color type of a PNG file:
https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header
http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
In short: A PNG file is composed of a header and chunks. In the header from second the forth byte should be ASCII string equal to "PNG", then the chunks which names are 4 bytes follow. The IHDR chunk gives you some data about the image like with, height and the needed color type. This chunk's position is always fixed as it is always the first chunk. And it's content is described in the second link I gave you:
The IHDR chunk must appear FIRST. It contains:
Width: 4 bytes
Height: 4 bytes
Bit depth: 1 byte
Color type: 1 byte
Compression method: 1 byte
Filter method: 1 byte
Interlace method: 1 byte
So knowing the length of the header, length of the chunk name and it's structure we can calculate the position of the color type data and it's the 26 byte. Now we can write a simple function that reads the color type of a PNG file.
function getPNGColorType($filename)
{
$handle = fopen($filename, "r");
if (false === $handle) {
echo "Can't open file $filename for reading";
exit(1);
}
//set poitner to where the PNG chunk shuold be
fseek($handle, 1);
$mime = fread($handle, 3);
if ("PNG" !== $mime) {
echo "$filename is not a PNG file.";
exit(1);
}
//set poitner to the color type byte and read it
fseek($handle, 25);
$content = fread($handle, 1);
fclose($handle);
//get integer value
$unpack = unpack("c", $content);
return $unpack[1];
}
$filename = "tmp/png.png";
getPNGColorType($filename);
Here is the color type nomenclature (from the second link):
Color Allowed Interpretation
Type Bit Depths
0 1,2,4,8,16 Each pixel is a grayscale sample.
2 8,16 Each pixel is an R,G,B triple.
3 1,2,4,8 Each pixel is a palette index;
a PLTE chunk must appear.
4 8,16 Each pixel is a grayscale sample,
followed by an alpha sample.
6 8,16 Each pixel is an R,G,B triple,
I hope this helps.

Do it with Bash code in your PHP Executing a Bash script from a PHP script
<?php
$type=shell_exec("identify -verbose $filename");
print_r($type);
?>

Related

How to set specific compression in Kb using imagejpeg in php

I'm trying to compress uploaded images down to a specific size of 200Kb. I don't want to compress them any more than they need to be and using lossless compression like PNG isn't enough. Simply setting it to imagejpeg($image, null, 40) creates different compressed sizes for different images. Is there a way to set the desired compression size in bytes or at least have some algorithm that can find out the compression output without looping through imagejpeg() from 100 to 0 quality?
I found a way to use ob to view the file size of the image before it is uploaded so I used it in a loop
// Get get new image data
ob_start();
// Build image with minimal campression
imagejpeg($newImage, NULL, 100);
// Get the size of the image file in bytes
$size = ob_get_length();
// Save new image into a variable
$compressedImage = addslashes(ob_get_contents());
// Clear memory
ob_end_clean();
// If image is larger than 200Kb
if ($size > 200000) {
// This variable will decrease by 2 every loop to try most combinations
// from least compressed to most compressed
$compressionValue = 100;
for ($i=0; $i < 50; $i++) {
$compressionValue = $compressionValue - 2;
ob_start();
imagejpeg($newImage, NULL, $compressionValue);
$size = ob_get_length();
// Overwrite old compressed image with the new compressed image
$compressedImage = addslashes(ob_get_contents());
// Clear memory
ob_end_clean();
// If image is less than or equal to 200.5Kb stop the loop
if ($size <= 200500) {
break;
}
}
}
This is incredibly well optimized on its own too. The whole process only takes a few milliseconds with a 1.5Mb starting image even when it is trying 50 combinations.
There really isn't any way to predict the level of compression beforehand. The effect of compression depends upon the image source. One of the problems is that there is a myriad of JPEG compression settings.
The quantization tables (up to 3) with 64 different values.
Sub sampling.
Spectral selection in progressive scans.
Successive Approximation in progressive scans.
Optimize huffman tables.
So there are billions upon billions of parameters that you can set.
There are JPEG optimization applications out there that will look at the compressed data.

PHP continually compress PNG

I am trying to compress a PNG file until I hit a target mark, using a dirty-ish method. But I am struggling to get any compression going. I know that at the final point, I set compression to 0, but that is because I expect the file to have been compressed.
1) Upload a png with a form
2) While the size is greater than 25000, compress it by 5 (imagepng())
I need to use the buffer to re-capture the image instead of constantly saving it (I think that's a good way). The following code isn't my production code but it's the exact method.
$postedImg = $_FILES['file']['tmp_name'];
$size = filesize($postedImg); // eg 35000
$info = getimagesize($postedImg);
$mime = $info['mime'];
if($mime === 'image/png')
$img = imagecreatefrompng($postedImg);
while($size > 25000){
ob_start();
imagepng($img , NULL, 5);
$obImgStr = ob_get_contents();
$img = imagecreatefromstring($obImgStr);
ob_end_clean();
$size = filesize($obImgStr);
}
imagepng($img, 'image/goes/here.png', 0);
PNG compression is lossless. If you compress it to 5, then to 5 again, you're going to end up with 5 still. In the same way, when you finally output your image at 0 (which would only happen if your first attempt at 5 hit the 25k), what you're actually doing is undoing any compression you just did. That's because the quality of the image is not affected at all by the compression (the doc's name for that option is misleading). It's just like a zip... all it does is reduce the file size, with the drawback that the file needs to be reinflated on the other end.
A better way of approaching this is to compress it incrementally, starting at e.g. 1, then 2, then 3 until you get the size you want. Even so, as said in the comments above, you may never get your target size.

PHP Test if Image is Interlaced

I have a folder that will consist of hundreds of PNG files and I want to write a script to make them all interlaced. Now, images will be added to that folder over time and processing all the images in the folder (wether their interlaced or progressive) seems kinda silly.
So I was wondering, is there any way to use PHP to detect if an image is interlaced or not that way I can choose wether to process it or not.
Thanks heaps!
You can also take the low-level approach - no need of loading the full image, or to use extra tools or libraries. If we look at the spec, we see that the "interlaced" flag it's just the byte 13 of the iHDR chunk, so we have to skip 8 bytes from the signature, plus 8 bytes of the iHDR Chunk identifier+length, plus 12 bytes of the chunk... That gives 28 bytes to be skipped, and if the next byte is 0 then the image is not interlaced.
The implementation takes just 4 lines of code:
function isInterlaced( $filename ) {
$handle = fopen($filename, "r");
$contents = fread($handle, 32);
fclose($handle);
return( ord($contents[28]) != 0 );
}
BTW, are you sure you want to use interlaced PNG? (see eg)
I think ImageMagick could solve your problem.
http://php.net/manual/en/imagick.identifyimage.php
Don't know if all the attributes are returned, but if you look at the ImageMagick tool documentation you can find that they can spot if an image is interlaced or not.
http://www.imagemagick.org/script/identify.php
At worst you can run the command for ImageMagick via PHP if the ImageMagick extension is not installed and parse the output for the "Interlace" parameter.

How can I convert a large string of base64 image data back to an image with PHP?

I have a large string of base64 image data (about 200K). When I try to convert that data by outputting the decoded data with the correct header, the script dies, as if there isn't enough memory. I get no error in my Apache logs. The example code I have below works with small images. How can I decode a large image?
<?php
// function to display the image
function display_img($imgcode,$type) {
header('Content-type: image/'.$type);
header('Content-length: '.strlen($imgcode));
echo base64_decode($imgcode);
}
$imgcode = file_get_contents("image.txt");
// show the image directly
display_img($imgcode,'jpg');
?>
Since base64-encoded data is cleanly separated every 4 bytes (i.e. 3 bytes of plaintext are encoded into 4 bytes of base64-encoded text), you could split your b64 string into multiples of 4 bytes, and process them separately:
while (not at end of string) {
take next 4096 bytes // for example - 4096 is 2^12, therefore a multiple of 4
// you could use much larger blocks, depends on your memory limits
base64-decode them
append the decoded result to a file, or a string, or send it to the output
}
If you have a valid base64 string, this will work identically to decoding it all at once.
OK, here is a closer resolution. While this seems to decode the base64 data in smaller chunks, I still don't get an image in the browser. If I echo the data before I place a header, I get output. Again, this works with a small image but not a large one. Thoughts?
<?php
// function to display the image
function display_img($file,$type) {
$src = fopen($file, 'r');
$data = "";
while(!feof($src)) {
$data .= base64_decode(fread($src, 4096));
}
$length = strlen($data);
header('Content-type: image/'.$type);
header('Content-length: '.$length);
echo $data;
}
// show the image directly
display_img('image.txt','jpg');
?>
Content-length must specify the actual (decoded) content length not the length of the base64 encoded data.
Though I'm not sure that fixing it would solve this problem...
Save the base64 string to an image file using imagejpeg() or the correct function for the different formats, and then display the image with a simple <img> tag.

Find JPEG resolution with PHP

Calling all PHP gurus!
I understand that you can use getimagesize() to get the actual pixel height and width of an image in PHP. However, if you open an image in photoshop and look at the image size dialog, you notice that there is a resolution value that determines the print size of the image.
Given an arbitrary jpg image file, I need to use PHP to determine this resolution number. It appears that this information is stored in the jpg file somewhere, so how do I get to it?
One other requirement - I only have gdlib available to me. I need to do this without the use of other php libraries (imagemagick, etc.)
Thanks for the help!
You could just read the JPEG file directly, bytes 14-18 specify:
byte 14: 01, X and Y density unit specifier (00: none, pixel ratios, 01: DPI,02: DPC)
bytes 15-16: horizontal pixel density,
byte 16-18: vertical pixel densit
Also see: http://www.obrador.com/essentialjpeg/headerinfo.htm
There are two places that a resolution (i.e. resolution of the JPEG when printed, also referred to in shorthand as DPI or dots per inch) can potentially be stored.
The first is in the JPEG's JFIF header, which is often (but NOT always) right at the start of the JPEG.
The other is in the EXIF data.
Note that resolution data is usually NOT present, as it only has meaning if associated with a physical output size. E.g. if a digital camera writes the value, it is usually meaningless . However, when the JPEG is being output to a printer (e.g.) then the value will have meaning.
Here is some code to get it from the JFIF header, provided one is present, and is inside an APP0 chunk which is the second chunk in the file. (The first chunk is always the SOI marker.):
function read_JFIF_dpi($filename)
{
$dpi = 0;
$fp = #fopen($filename, r);
if ($fp) {
if (fseek($fp, 6) == 0) { // JFIF often (but not always) starts at offset 6.
if (($bytes = fread($fp, 16)) !== false) { // JFIF header is 16 bytes.
if (substr($bytes, 0, 4) == "JFIF") { // Make sure it is JFIF header.
$JFIF_density_unit = ord($bytes[7]);
$JFIF_X_density = ord($bytes[8])*256 + ord($bytes[9]); // Read big-endian unsigned short int.
$JFIF_Y_density = ord($bytes[10])*256 + ord($bytes[11]); // Read big-endian unsigned short int.
if ($JFIF_X_density == $JFIF_Y_density) { // Assuming we're only interested in JPEGs with square pixels.
if ($JFIF_density_unit == 1) $dpi = $JFIF_X_density; // Inches.
else if ($JFIF_density_unit == 2) $dpi = $JFIF_X_density * 2.54; // Centimeters.
}
}
}
}
fclose($fp);
}
return ($dpi);
}
SOLUTION: User the PHP JPEG Metadata Toolkit - downloaded from here: http://www.ozhiker.com/electronics/pjmt/
This toolkit has some handy scripts that will do all sorts of things, including viewing and editing of the header, metadata, and jfif information in jpeg file. Here is a script that gives you the XDensity and the YDensity (the x and y print resolution) of a jpg:
<?php
include_once("./JPEG.php");
include_once("./JFIF.php");
$image_header = get_jpeg_header_data("./myImage.jpg");
$image_info = get_JFIF($image_header);
print( "XDensity:" . $image_info['XDensity'] . "<br />");
print( "YDensity:" . $image_info['YDensity'] . "<br />");
?>
Depending on how the image is saved, EXIF contains a metric crapload of information - Read more about it in the PHP manual. You may need to parse/process the results a bit, however (e.g. the flash info is, or at least has been, just a byte, expressing various states).
I don't understand this. Pixels = printsize x resolution, and the number of pixels is a set value. So, if you have an image of 300x300 pixels, you have 1"x1" of 300 DPI resolution, 2"x2" of 150 DPI resolution, or 4"x4" of 75 DPI resolution etc. An image doesn't have a resolution unless it has a physical size to compare to its pixel size.
What is it I'm missing? (and just how glaringly obvious is it to everyone else? =] )

Categories