Find JPEG resolution with PHP - 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? =] )

Related

Using PHP get png type

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

Detect JPEG image quality

I allow users to upload images. However, I want to keep JPEG quality not more than 90%. What I plan to do is to detect the current quality:
- If less than 90% do nothing
- If more than 90%, than use Image Magick to recompress the image to 90%
Is it possible to do that? I prefer PHP but any language will help.
paddy is correct that this setting is not always stored in the JPEG file. If it is, then you can use identify from Imagemagick to read the quality. For example:
$ identify -format '%Q' tornado_ok.jpg
93%
Update: Based on the answer to this question
https://superuser.com/questions/62730/how-to-find-the-jpg-quality I
find out that apparently the identify command can still determine
the quality by reverse engineering the quantization tables even if all
the image EXIF / other meta data is lost. By the way, the title of
your question as it stands now is a possible duplicate of that
question I linked to.
But to me your question has merit on its own because in your
question's text you explain what you are trying to do, which is more
than simply detecting jpeg quality. Nevertheless, you should perhaps
update the title if you want to reflect that you are trying to solve a
more specific problem than just reading JPEG image quality.
Unless you are archiving original images, for web use even 90% is excessive. 75% used to be the default in the old days (degradation was visible only under close inspection between side-by-side images), and now in the days of high bandwidth 85% is a very high quality option. The 5% quality difference between 90% and 85% is virtually invisible, but will save you over 30% in file size typically. The JPEG algorithm is designed to begin by eliminating information that is invisible to human perception at its first compression stages (above 80% or so).
Update/note: The compression quality settings I am talking about are
from tests with libjpeg, a very widely used JPEG library. Photoshop's
compression percentages and other software's quality settings are all
independent and do not necessarily mean the same thing as the settings
of libjpeg.
paddy's idea of using image height and image width to calculate an acceptable file size is reasonable:
You can get the image height/width like this:
list($originalWidth, $originalHeight) = getimagesize($imageFile);
My own high-quality photos posted online, like this one: http://ksathletics.com/2013/wsumbb/nw.jpg
are typically saved at a ratio of about 200 KB per megapixel.
So, for example, you can multiply width times height and divide by 1000000 to calculate the megapixels in the image. Divide the file size by 1024 to calculate the KB. Then divide the resulting KB by the megapixels. If the result is under 200 or whatever value you decide upon, then you don't need to re-compress it. Otherwise, you can re-compress it with a quality of 85% or whatever quality you decide on.
Since the OP stated he prefers php, I offer the following:
$img = new Imagick($filename);
$quality = $img->getImageCompressionQuality();
whats up? I was facing the same problem with an app I'm developing... My problem is that, I extract several images from a random site and each item have several images, i would like to show a single image for each item, and bring to the user the best quality image.
I came out with this idea, its pretty simple, and will work for any language and any type of compression:
//Parameters you will need to retrieve from image
$width = 0;
$height = 0;
$filesize = 0;
//Quality answer for your image
$quality = (101-(($width*$height)*3)/$filesize);
I ran this algorithm against the http://fotoforensics.com/tutorial-estq.php mentioned above and here are the results:
Filename Width Height Pixels BitmapBytes FileBytes Quality
estq-baseline.png 400 300 120000 360000 163250 98,79
estq-90.jpg 400 300 120000 360000 34839 90,67
estq-80.jpg 400 300 120000 360000 24460 86,28
estq-70.jpg 400 300 120000 360000 19882 82,89
estq-25.jpg 400 300 120000 360000 10300 66,05
The basic idea behind this algorithm is compare the size that an image could reach if written in a bitmap way (without any compression, 3 bytes per pixel, 3 bytes for RGB) to the size that this image is currently using. The smaller is the image size, the higher is the compression, independently of the compression method used, rather its a JPG, a PNG or whatever, and that will lead us into having a bigger gap or smaller gap between uncompressed and compressed image.
It is also important to mention, that this is a mathematical solution for comparison purposes, this method will not return the actually quality of the image, it will answer the distance percentage between uncompressed and compressed sizes!
if you need more details, you can send me an email: rafaelkarst#gmail.com
You cannot guarantee that the quality setting is stored in the JPEG's metadata. This is an encoder setting, not an image attribute.
Read more here about estimating JPEG quality
It might make more sense to simply define a maximum file size. At the end of the day, restricting image quality is all about saving bandwidth. So setting a ratio between image dimensions and file size is more appropriate.
If your jpeg was created using a straight scaling of the standard image quantization tables and the 0-100 quality was used based on the Independent JPEG Group's formula, then, assuming you have the luminance quantization tables in an array called quantization (such as Python's PIL module provides in image.quantization[0]), then the original value can be obtained via:
if quantization[58] <= 100:
originalQuality = int(100 - quantization[58] / 2)
else:
originalQuality = int(5000.0 / 2.5 / quantization[15])
Basically, the default luminance quantization value #15 is 40 and #58 is 100, so these make handy values to extract the results from. IJG scales values about 50 via 5000 / Q and below 50 via 200 - 2 * Q. If the quality setting is less than 8, this won't give decent results (if quantization[5] == 255) -- in that case, perhaps use quantization table position #5
See https://www.rfc-editor.org/rfc/rfc2435#section-4.2.
For those who are using GraphicsMagick instead of ImageMagick, you can get the JPEG quality with the following command:
gm identify -format '%[JPEG-Quality]' path_to/image_file.jpg
and according to the documentation
http://www.graphicsmagick.org/GraphicsMagick.html#details-format
Please note that JPEG has no notion of "quality" and that the quality metric used by, and estimated by the software is based on the quality metric established by IJG JPEG 6b. Other encoders (e.g. that used by Adobe Photoshop) use different encoding metrics.
Here is a PHP function that tries all available methods of getting quality (that I know of):
/* Try to detect quality of jpeg.
If not possible, nothing is returned (null). Otherwise quality is returned (int)
*/
function detectQualityOfJpg($filename)
{
// Try Imagick extension
if (extension_loaded('imagick') && class_exists('Imagick')) {
$img = new Imagick($filename);
// The required function is available as from PECL imagick v2.2.2
if (method_exists($img, 'getImageCompressionQuality')) {
return $img->getImageCompressionQuality();
}
}
if (function_exists('shell_exec')) {
// Try Imagick
$quality = shell_exec("identify -format '%Q' " . $filename);
if ($quality) {
return intval($quality);
}
// Try GraphicsMagick
$quality = shell_exec("gm identify -format '%Q' " . $filename);
if ($quality) {
return intval($quality);
}
}
}

imagecopyresampled sometimes creating blank images

I am resizing images using:
//load file and dimensions
$obj_original_image = imagecreatefromjpeg($str_file_path);
list($int_width, $int_height, $image_type) = getimagesize($str_file_path);
//compress file
$int_thumbnail_width = 320;
$int_ratio = $int_thumbnail_width / $int_width;
$int_new_width = $int_thumbnail_width;
$int_new_height = $int_height * $int_ratio;
$obj_image = imagecreatetruecolor($int_new_width, $int_new_height);
imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
imagejpeg($obj_image, $GLOBALS['serverpath'] . '/images/uploaded/video-thumbs/'.$arr_data['thumbnail_folder'].'/' . $arr_data['id']. '.jpg', $int_compression = 85);
And this is generally working perfectly. However occasionally, it is producing a blank, white image. I have read this note at http://php.net/manual/en/function.imagecopyresampled.php:
There is a problem due to palette image limitations (255+1 colors). Resampling or filtering an image commonly needs more colors than 255, a kind of approximation is used to calculate the new resampled pixel and its color. With a palette image we try to allocate a new color, if that failed, we choose the closest (in theory) computed color. This is not always the closest visual color. That may produce a weird result, like blank (or visually blank) images. To skip this problem, please use a truecolor image as a destination image, such as one created by imagecreatetruecolor().
However, I am already using imagecreatetruecolor. I am always scaling the same sized images so it can't be a width/height issue. It is only happening sometimes, most times the image scaling is working fine. Any ideas as to how to fix this?
There's no reason to be using getimagesize() when you've already opened the image with imagecreatefromjpeg(). You can use the imagesx() and imagesy() to get the width/height. getimagesize is seperate from GD and will re-open the image, re-parse it, etc...
Also note that GD is pretty stupid and forces you to determine what image type you've got and call the appropriate createfrom function. imagecreatefromjpeg() will fail if you try to open anything OTHER than a .jpg image.
$obj_original_image = imagecreatefromjpeg($str_file_path);
if ($obj_original_image === FALSE) {
die("Unable to load $str_file_path. Not a jpg?");
}
etc... etc...
$status = imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
if ($status === FALSE) {
die("imagecopyresampled failed!");
}
As well, add some debugging to your height/width calculations - output the values you're generating. Maybe one or more of them is coming out as 0, so you end up resampling to a non-existent size.
You mentioned that the size of the image being processed is always the same, but is the resolution always the same? If you have a particularly high resolution image you're working with, you may be running out of memory for certain operations which will produce a blank white image, in my experience. If this is the case, you could try to fix it by increasing the amount of memory allocated to PHP in php.ini under the memory_limit parameter. As far as I know, that value applies to image manipulations.

imagecreatetruecolor fails with 1536X1962 but not smaller diminsions

I have narrowed down my problem to show the size of the file matters. I don't know when the file is to big however. How can I catch the error. A 1.1mg file causes imagecreatetruecolor to bail but it chuggs along just fine when processing a 688k file.
Thanks
From what I can tell in the PHP/GD documentation, this function creates a 24-bit RGB image with black as the default color. The width and height it takes as arguments are ints for the pixel dimensions. Therefore to calculate size, you could multiply them as follows to determine if the raw image (before compression) is to blame:
1536 * 1962 = 3,013,632 pixels
3,013,632 * 24 = 72,327,168 bits
72,327,168 / 8 = 9,040,896 bytes
1024 * 768 = 786,432 pixels
786,432 * 24 = 18,874,368 bits
18,874,368 / 8 = 2,359,296 bytes
It seems unusual to me that this function would cause problems at a size of 1.1 MB but perhaps you are referring to a compressed image such as a jpg, where the actual raw size could be much, much greater. (As you can see a "small" image of 1024x768 is still well over 1.1 MB raw.)
The function will throw an error if you try to create an image too big. Just suppress the error and handle it yourself. For example,
$img = #imagecreatetruecolor(100000, 100000);
if ($img === false)
{
// Handle error
}

How to check a PNG for grayscale/alpha color type?

PHP and GD seem to have trouble creating images from PNGs of type greyscale with alpha when using imagecreatefrompng(). The results are incredibly distorted.
I was wondering if anyone knew of a way to test for the colour type in order to notify the user of the incompatibility?
Example:
Original Image: http://dl.dropbox.com/u/246391/Robin.png
Resulting Image: http://dl.dropbox.com/u/246391/Robin_result.png
Code:
<?php
$resource = imagecreatefrompng('./Robin.png');
header('Content-type: image/png');
imagepng($resource);
imagedestroy($resource);
Cheers,
Aron
The colour type of a PNG image is stored at byte offset 25 in the file (counting from 0). So if you can get hold of the actual bytes of the PNG file, simply look at byte 25 (I don't do PHP, so I don't know how to do that):
0 - greyscale
2 - RGB
3 - RGB with palette
4 - greyscale + alpha
6 - RGB + alpha
The preceding byte (offset 24) gives the number of bits per channel. See the PNG spec for more details.
In a slight twist a PNG file may have "1-bit alpha" (like GIFs) by having a tRNS chunk (when it is colour type 0 2 or 3).
i landed here today searching for a way to tell (via php) if a specific .png image is an alpha-png one -
David Jones' answer points to the right direction, really easy to implement in php:
file_get_contents to load just that 25' byte (there it is, indeed!), and
ord() to get its ASCII value, to test it (against '6' in my case)
if(ord(file_get_contents($alpha_png_candidate, NULL, NULL, 25, 1)) == 6) {
is_alpha_png_so_do_something();
}
actually i needed that for assuring backward compatibility with ie6
within cms-user-generated-pages, to replace all alpha-png < img > tags with inline-block < spans > - the alpha-png file will then be served as variable for the ms-proprietary css property filter
.alpha_png_span{
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(
src='$alpha_png_candidate', sizingMethod='crop')
}
...and it all works, so thanks!
paolo
see this answer
:
Another usefull note for those using ImageCreateFromPng:
PHP and GD do not recognize grayscale/alpha images.
So if you use grayscale images with transparency between 0% and 100%, then save the image as RGB.
At least this is true for PHP Version 4.4.2-1 and in 5.1.2-1 with pictures made with GIMP 2.2.8.
url :
http://php.net/manual/en/function.imagecreatefrompng.php

Categories