I am trying to build a script that retrieves a list of thumbnail images from an external link, much like Facebook does when you share a link and can choose the thumbnail image that is associated with that post.
My script currently works like this:
file_get_contents on the URL
preg_match_all to match any <img src="" in the contents
Works out the full URL to each image and stores it in an array
If there are < 10 images it loops through and uses getimagesize to find width and height
If there are > 10 images it loops through and uses fread and imagecreatefromstring to find width and height (for speed)
Once all width and heights are worked out it loops through and only adds the images to a new array that have a minimum width and height (so only larger images are shown, smaller images are less likely to be descriptive of the URL)
Each image has its new dimensions worked out (scaled down proportionally) and are returned...
<img src="'.$image[0].'" width="'.$image[1].'" height="'.$image[2].'"><br><br>
At the moment this works fine, but there are a number of problems I can potentially have:
SPEED! If the URL has many images on the page it will take considerably longer to process
MEMORY! Using getimagesize or fread & imagecreatefromstring will store the whole image in memory, any large images on the page could eat up the server's memory and kill my script (and server)
One solution I have found is being able to retrieve the image width and height from the header of the image without having to download the whole image, though I have only found some code to do this for JPG's (it would need to support GIF & PNG).
Can anyone make any suggestions to help me with either problem mentioned above, or perhaps you can suggest another way of doing this I am open to ideas... Thanks!
** Edit: Code below:
// Example images array
$images = array('http://blah.com/1.jpg', 'http://blah.com/2.jpg');
// Find the image sizes
$image_sizes = $this->image_sizes($images);
// Find the images that meet the minimum size
for ($i = 0; $i < count($image_sizes); $i++) {
if ($image_sizes[$i][0] >= $min || $image_sizes[$i][1] >= $min) {
// Scale down the original image size
$dimensions = $this->resize_dimensions($scale_width, $scale_height, $image_sizes[$i][0], $image_sizes[$i][1]);
$img[] = array($images[$i], $dimensions['width'], $dimensions['height']);
}
}
// Output the images
foreach ($img as $image) echo '<img src="'.$image[0].'" width="'.$image[1].'" height="'.$image[2].'"><br><br>';
/**
* Retrieves the image sizes
* Uses the getimagesize() function or the filesystem for speed increases
*/
public function image_sizes($images) {
$out = array();
if (count($images) < 10) {
foreach ($images as $image) {
list($width, $height) = #getimagesize($image);
if (is_numeric($width) && is_numeric($height)) {
$out[] = array($width, $height);
}
else {
$out[] = array(0, 0);
}
}
}
else {
foreach ($images as $image) {
$handle = #fopen($image, "rb");
$contents = "";
if ($handle) {
while(true) {
$data = fread($handle, 8192);
if (strlen($data) == 0) break;
$contents .= $data;
}
fclose($handle);
$im = #imagecreatefromstring($contents);
if ($im) {
$out[] = array(imagesx($im), imagesy($im));
}
else {
$out[] = array(0, 0);
}
#imagedestroy($im);
}
else {
$out[] = array(0, 0);
}
}
}
return $out;
}
/**
* Calculates restricted dimensions with a maximum of $goal_width by $goal_height
*/
public function resize_dimensions($goal_width, $goal_height, $width, $height) {
$return = array('width' => $width, 'height' => $height);
// If the ratio > goal ratio and the width > goal width resize down to goal width
if ($width/$height > $goal_width/$goal_height && $width > $goal_width) {
$return['width'] = floor($goal_width);
$return['height'] = floor($goal_width/$width * $height);
}
// Otherwise, if the height > goal, resize down to goal height
else if ($height > $goal_height) {
$return['width'] = floor($goal_height/$height * $width);
$return['height'] = floor($goal_height);
}
return $return;
}
getimagesize reads only header, but imagecreatefromstring reads whole image. Image read by GD, ImageMagick or GraphicsMagic is stored as bitmap so it consumes widthheight(3 or 4) bytes, and there's nothing you can do about it.
The best possible solution for your problem is to make curl multi-request (see http://ru.php.net/manual/en/function.curl-multi-select.php ), and then one by one process recieved images with GD or any other library. And to make memory consumption a bit lower, you can store image files on disk, not in memory.
The only idea that comes to mind for your current approach (which is impressive) is to check the HTML for existing width and height attributes and skip the file read process altogether if they exist.
Related
I am kind of experimenting with the PHP Image saving.
I want to compare the image's actual file sizes before upload and after resize/compress processing, but want to know the file size that will going to be if I save it to the disk.
So that if the file size is less, then the newly processed file will replace old one, else no change will be made.
This is the code I am using to save jpeg image.
imagejpeg($this->newImage, $savePath, $imageQuality);
I tried getting the actual file size and size after
print_r(filesize($actual_file_size));
print_r($this->newImage);
where $actual_file_size gives the data by reading from tmp object file, however $this->newImage is giving blank (no value).
I am using ResizeImage class from github
Is there any way I can get the file size before saving to disk? So that I can keep the minimal size version.
This is how you do it:
$original_file = 'original.jpg';
$resized_file = 'resized.jpg';
$max_file_size = '10000';
$original_image = imagecreatefromjpeg($original_file);
$image_quality = 100;
do {
$temp_stream = fopen('php://temp', 'w+');
$saved = imagejpeg($original_image, $temp_stream, $image_quality--);
rewind($temp_stream);
$fstat = fstat($temp_stream);
fclose($temp_stream);
$file_size = $fstat['size'];
}
while (($file_size > $max_file_size) && ($image_quality >= 0));
if (-1 == $image_quality) {
echo "Unable to get the file that small. Best I could do was $file_size bytes at image quality 0.\n";
}
else {
echo "Successfully resized $original_file to $file_size bytes using image quality $image_quality. Resized file saved as $resized_file.\n";
imagejpeg($new_image, $resized_file, $image_quality + 1);
}
This requires no file to be written until you've successfully found the ideal compression ratio.
This loop starts with an $image_quality of 100, then counts downward trying each new value on a temporary in-memory buffer. If gets down to trying 0 and the file would still be too big, it returns an error message. Otherwise, if it gets to a value that works, it reports that and saves the new file using that image quality setting.
If you're feeling adventurous, you could implement a binary search algorithm to find the ideal compression ratio more quickly, but this quick-fix gives the same result, albeit possibly a tad slower.
There is no way to calculate the size before saving it to the disk. I advice you create some kind of temporary folder to save the image, then check the size and if it is what you want move it to the folder you see fit, if you dont want to save it, delete it from the temporary folder.
Not really true.
There is a way..
The following piece of code works with JPEG and PNG :-)
///######## GET THE TOTAL PIXELS
$total_pixels = $image_width * $image_height;
$size_at_max = (3 * $total_pixels) * 0.001; /// **** IN KB
Brian's answer was a bit slow with large numbers of pictures... so I used a loop which modified by half the distance each time (how some digital volt meters work)...
So quality changes in steps 50, 25, 12.5 ... etc and can go up or down as needed...
$max_file_size = 100000; //100k max saved file size
$delta = 10000; //10k distance off max when OK to stop optimizing...
$n = 1;
$quality = 100;
$size = image_size($image, $quality);
if ($size > $max_file_size) {
while ((($size > $max_file_size + $delta) or ($size < $max_file_size - $delta))) {
if ($size > $max_file_size) $quality = $quality - (100 / (2 ** $n));
elseif( $size < $max_file_size) $quality = $quality + (100 / (2 ** $n));
else break;
$size = image_size($image, $quality);
$n++;
}
}
function image_size ($image, $quality) {
ob_start();
imagewebp($image, NULL, $quality);
$size = ob_get_length();
ob_end_clean();
return $size;
}
This grabs all images from a page, and is supposed to check if image has more width and height than 200. if so, grab the first of it. But its an expensive process, and im wondering if there are more lightweight approaches to this than using getimagesize. Does anyone know of a different approach without the use of external services like YQL etc?
if($ogimage!=''|| !empty($ogimage)){
$arrimg = $ogimage;
} else {
$imgarr = array();
foreach ($doc->getElementsByTagName('img') as $img) {
$arrimg_push = $img->getAttribute('src');
array_push($imgarr, $arrimg_push);
}
$i=0;
foreach($imgarr as $img){
list($width, $height, $type, $attr) = getimagesize($img);
if($width > 200 && $height > 200){
if($i > 0){
$arrimg = $img;
$i++;
}
}
}
}
ImageMagick's pingImage or pingImageFile will read as little of the image file as possible to get the basic attributes, which can then be accessed using getImageWidth and getImageHeight.
This is currently what I have. When I include it in my index.php and then call the function on pageload, I get a blank page. So something is wrong here, but I don't know what. I feel like I'm really close though. I just want to create thumbnails of images in a directory, and then show them in HTML as a list of images you can click that trigger lightboxes.
I'm still really shaky in PHP. I'm trying to wrap my head around editing images in a directory.
<?php
function buildThumbGallery(){
$h = opendir('/Recent_Additions/'); //Open the current directory
while (false !== ($curDir = readdir($h))) {
if (!file_exists('/Recent_Additions/thumbs/')) {
$thumbDir = mkdir('/Recent_Additions/thumbs/', 0777, true);
}else{
$thumbDir = '/Recent_Additions/thumbs/';
}
$width = 200;
$height = 200;
foreach ($curDir as $image) {
$filePath = $curDir."/".$image;
$genThumbImg = $image->scaleImage($width, $height, true);
$newThumb = imagejpeg($genThumbImg, $thumbDir, 100);
echo '<li> <a class="fancybox" data-fancybox-group="'.basename($curDir).'" href="'.$filePath.'" title="'.basename($curDir)." ".strpbrk(basename($filePath, ".jpg"), '-').'"><img src="'.$newThumb.'"/>'.basename($curDir).'</a>';
imagedestroy($newThumb);
}echo '</li>';
}
?>
You are doing several things wrong:
You're not closing the while loop.
Readdir already loops through a directory, your foreach is not doing anything.
You are missing quotes in your echo.
You are calling the method scaleImage on a string, I think you meant to call the function imagescale.
You're missing and misunderstanding a lot of stuff, take a look at how to create a thumbnail here: https://stackoverflow.com/a/11376379/4193448
Also see if you can enable PHP errors, getting a blank page while your code is full of errors is not really helping is it?
::EDIT::
With help from #swordbeta, I got my script working properly. Here is the code for future reference:
<?php
function buildThumbGallery(){
$curDir = "./Recent_Additions/";
$thumbsPath = $curDir."thumbs/";
if (!file_exists($thumbsPath)) {
mkdir($thumbsPath, 0777, true);
}
foreach(scandir($curDir) as $image){
if ($image === '.' || $image === '..' || $image === 'thumbs') continue;
if(!file_exists($thumbsPath.basename($image, ".jpg")."_thumb.jpg")){
// Max vert or horiz resolution
$maxsize=200;
// create new Imagick object
$thumb = new Imagick($curDir.$image); //'input_image_filename_and_location'
$thumb->setImageFormat('jpg');
// Resizes to whichever is larger, width or height
if($thumb->getImageHeight() <= $thumb->getImageWidth()){
// Resize image using the lanczos resampling algorithm based on width
$thumb->resizeImage($maxsize,0,Imagick::FILTER_LANCZOS,1);
}else{
// Resize image using the lanczos resampling algorithm based on height
$thumb->resizeImage(0,$maxsize,Imagick::FILTER_LANCZOS,1);
}
// Set to use jpeg compression
$thumb->setImageCompression(Imagick::COMPRESSION_JPEG);
$thumb->setImageCompressionQuality(100);
// Strip out unneeded meta data
$thumb->stripImage();
// Writes resultant image to output directory
$thumb->writeImage($thumbsPath.basename($image, ".jpg")."_thumb.jpg"); //'output_image_filename_and_location'
// Destroys Imagick object, freeing allocated resources in the process
$thumb->destroy();
}else{
echo '<a class="fancybox" data-fancybox-group="'.basename($curDir).'" href="'.$curDir.basename($image, "_thumb.jpg").'" title="Recent Addition - '.basename($image, ".jpg").'"><img src="'.$thumbsPath.basename($image, ".jpg")."_thumb.jpg".'"/></a>';
echo '<figcaption>'.basename($image, ".jpg").'</figcaption>' . "<br/>";
}
}
}
?>
::Original Post::
Ok, after going back and doing some more research and suggestions from #swordbeta, i've got something that works. My only issue now is I can't get the images to show in my index.php. I'll style the output in CSS later, right now I just want to see the thumbnails, and then later build them into lightbox href links:
<?php
function buildThumbGallery(){
$curDir = "./Recent_Additions/";
$thumbsPath = $curDir."/thumbs/";
if (!file_exists($thumbsPath)) {
mkdir($thumbsPath, 0777, true);
}
$width = 200;
foreach(scandir($curDir) as $image){
if ($image === '.' || $image === '..') continue;
if(file_exists($thumbsPath.basename($image)."_thumb.jpg")){
continue;
}else{
// Max vert or horiz resolution
$maxsize=200;
// create new Imagick object
$thumb = new Imagick($curDir.$image); //'input_image_filename_and_location'
// Resizes to whichever is larger, width or height
if($thumb->getImageHeight() <= $thumb->getImageWidth()){
// Resize image using the lanczos resampling algorithm based on width
$thumb->resizeImage($maxsize,0,Imagick::FILTER_LANCZOS,1);
}else{
// Resize image using the lanczos resampling algorithm based on height
$thumb->resizeImage(0,$maxsize,Imagick::FILTER_LANCZOS,1);
}
// Set to use jpeg compression
$thumb->setImageCompression(Imagick::COMPRESSION_JPEG);
$thumb->setImageCompressionQuality(100);
// Strip out unneeded meta data
$thumb->stripImage();
// Writes resultant image to output directory
$thumb->writeImage($thumbsPath.basename($image)."_thumb.jpg"); //'output_image_filename_and_location'
// Destroys Imagick object, freeing allocated resources in the process
$thumb->destroy();
}
} echo '<img src="'.$thumbsPath.basename($image)."_thumb.jpg".'" />' . "<br/>";
}
?>
At the moment, the output from the echo isn't showing anything, but the rest of the script is working properly (i.e. generating thumbnail images in a thumbs directory).
I'm guessing i'm not formatting my echo properly. This script is called in my index.php as <?php buildThumbGallery(); ?> inside a styled <div> tag.
Is it possible to get image information without loading the actual image with PHP? In my case I want the Height and Width.
I have this code to fetch images from a directory. I echo out the image's url and fetch it with JS.
<?php
$directory = "./images/photos/";
$sub_dirs = glob($directory . "*");
$i = 0;
$len = count($sub_dirs);
foreach($sub_dirs as $sub_dir)
{
$images = glob($sub_dir . '/*.jpg');
$j = 0;
$len_b = count($images);
foreach ($images as $image)
{
if ($j == $len_b - 1) {
echo $image;
} else {
echo $image . "|";
}
$j++;
}
if ($i == $len - 1) {
} else {
echo "|";
}
$i++;
}
?>
getImageSize() is the proper way to get this information in PHP
It does a minimal amount of work based on the type of image. For example, a GIF image's height/width are stored in a header. Very easy to access and read. So this is how the function most likely gets that information from the file. For a JPEG, it has to do a little more work, using the SOFn markers.
The fastest way to access this information would be to maintain a database of file dimensions every time a new one is uploaded.
Given your current situation. I recommend writing a PHP script that takes all of your current image files, gets the size with this function, and then inserts the info into a database for future use.
It depends what you mean by "without loading it".
The built-in getimagesize() does this.
list($w, $h) = getimagesize($filename);
You can programmatically get the image and check the dimensions using Javascript...
var img = new Image();
img.onload = function() {
alert(this.width + 'x' + this.height);
}
img.src = 'http://www.google.com/intl/en_ALL/images/logo.gif';
This can be useful if the image is not a part of the markup ;)
You can store the width'n'height information in a text file, and load it later on.
list($width, $height, $type, $attr) = getimagesize($_FILES["Artwork"]['tmp_name']);
Use this.
I have a page while is used only for print, and some of the images on there are uploaded through a script I have. It seems to always reduce the image to 72 dpi, reguardless of what I set imagejpeg() and imagepng() to for quality.
I've used my own personal script and this one on git hub
https://github.com/maxim/smart_resize_image
I was hoping for a little guidance on preserving the dpi at the original 300dpi.
Here is my own personal script
if (!empty($_FILES['image']['name'])) //checking if file upload box contains a value
{
$saveDirectory = 'pics/'; //name of folder to upload to
$tempName = $_FILES['image']['tmp_name']; //getting the temp name on server
$fileName1 = $_FILES['image']['name']; //getting file name on users computer
$count = 1;
do{
$location = $saveDirectory . $_GET['section'] . $count . $fileName1;
$count++;
}while(is_file($location));
if (move_uploaded_file($tempName, $location)) //Moves the temp file on server
{ //to directory with real name
$image = $location;
// Get new sizes
list($width, $height, $type) = getimagesize($image); //gets information about new server image
$framewidth = 932;
$frameheight = 354;
$realwidth = $width; //setting original width and height
$realheight = $height;
// Load
$file1new = imagecreatetruecolor($framewidth, $frameheight); //creates all black image with target w/h
if($type == 2){
$source = imagecreatefromjpeg($image);
imagecopyresampled($file1new, $source , 0, 0, 0, 0, $framewidth, $frameheight, $realwidth, $realheight);
}
elseif($type == 3){
$source = imagecreatefrompng($image);
imagecopyresampled($file1new, $source , 0, 0, 0, 0, $framewidth, $frameheight, $realwidth, $realheight);
}
else{
echo "Wrong file type";
}
if($type == 2){
//creates jpeg image from file1new for file1 (also retains quality)
imagejpeg($file1new, $image,100);
//frees space
imagedestroy($file1new);
}
elseif($type == 3){
//creates png image from file1new for file1 (also retains quality)
imagepng($file1new, $image,10);
//frees space
imagedestroy($file1new);
}
else{
echo "Wrong file type";
}
}
else
{
echo '<h1> There was an error while uploading the file.</h1>';
}
}
}
Edit: Even if dpi isn't the answer, as I see jpgs in specific don't retain that information. I need some way of keeping these images very clear and crisp.
If you generate image and open with a browser, the browser will reduce it to 72dpi before rendering.
If you open with gimp/phptoshop/whatever image editor , it should preserve the same dpi quality.
Though on a screen, there is no difference since your screen is 72 dpi.
Not tested on new browsers, but it was like this in netscape and first firefox versions, I assume it has not changed since.
The function posted by lorezyra (at) lorezyra (dot) com here: http://www.php.net/manual/es/function.imagejpeg.php#85712 might do the trick.