I'm trying to read a directory of .jpg files from a folder ./gallery/, make them a bit smaller, and then save them back to ./gallery/_thumbs/.
Everything seems to work up until I try to use the imagecopyresampled() to actually do the resizing. The PHP man says it should return a bool on success/failure, but I'm able to get anything and hence don't know what I'm doing wrong.
I'm assuming that if I get a valid result from imagecopyresampled() that the imagejpeg() will work okay the way I have it?
for($i=0;$files_in_dir[$i]!=null;$i++)
{
if (!($files_in_dir[$i]=="."||$files_in_dir[$i]==".."||$files_in_dir[$i]=="_thumb"))
{
$my_images['image_name']=$files_in_dir[$i];
$my_images['path_to_current']=$directory.$my_images['image_name'];
$my_images['path_to_finished_thumb']=$directory.$sub_directory.$prefix.$my_images['image_name'];
$my_images['image_handler']=imagecreatefromjpeg($my_images['path_to_current']);
$imagesize = getimagesize($my_images['path_to_current']);
$my_images['width']=$imagesize[0];
$my_images['height']=$imagesize[1];
$my_images['ratio']=$my_images['height']/$my_images['width'];
$my_height = round($my_width / $ratio);
echo "<pre>";
var_dump($my_images);
$newImage = imagecreatetruecolor($my_width,$my_height);
$success = imagecopyresampled($newImage,$my_images['image_handler'],0,0,0,0,$my_width,$my_height,$my_images['width'],$my_images['height']);
echo "my success was: ",$success,"<br />";
imagejpeg($newImage,$my_images['path_to_finished_thumb'],80);
imagedestroy($my_images['image_handler']);
imagedestroy($newImage);
echo "</pre>";
}
}
?>
There are a couple things to come to mind.
First, check to make sure PHP is able to write to the files and directories in question.
Make sure you're seeing all errors/warnings so you know when something fails.
From your report it certainly sounds like your copy of PHP has the GD extension installed, but do make sure before you assume that extension-based functions like imagejpeg() are available. You can use a page containing <?php phpinfo(); to determine this yourself, or you can do it programmatically using function_exists().
I've taken the liberty of streamlining your script to make it easier to catch possible bugs (fewer lines = fewer possible mistakes):
<?php
// Make sure we see processing errors during development/testing.
error_reporting(E_ALL);
ini_set('display_errors', true);
// Set up operating parameters.
$filePattern = 'gallery/*.[jJ][pP][gG]';
$thumbPattern = 'thumbs/%s';
$targetWidth = 200;
$targetQuality = 80;
// Create the output dir, if it doesn't already exist. This will fail
// if PHP does not have write access to the local folder.
$thumbDir = sprintf($thumbPattern, '');
if (!is_dir($thumbDir)) {
mkdir($thumbDir);
}
// Abort if GD is not installed.
if (!function_exists('imagejpeg')) {
die('PHP does not have the GD extension installed. Aborting.');
}
// Abort if the output directory is not writable.
if (!is_writable($thumbDir)) {
die('The output directory is not writable by PHP. Check permissions. Aborting.');
}
// Loop over all found jpg files.
foreach (new GlobIterator($filePattern) as $f) {
// var_dump($f->getRealpath()); // Debugging the path name.
// Skip regenerating cached thumbs for performance.
$thumbPath = sprintf($thumbPattern, $f->getFilename());
if (file_exists($thumbPath)) {
continue;
}
// Determine thumbnail output size.
list($w, $h) = getimagesize($f->getRealpath());
$ratio = $h / $w;
$targetHeight = round($targetWidth * $ratio);
// Generate the thumbnail from the original.
$jpg = imagecreatefromjpeg($f->getRealpath());
$thumb = imagecreatetruecolor($targetWidth, $targetHeight);
$success = imagecopyresampled(
$thumb,
$jpg,
0, 0, 0, 0,
$targetWidth, $targetHeight, $w, $h
);
imagejpeg($thumb, $thumbPath, $targetQuality);
}
(Gist version here.)
One thing I caught during this process was that the logic for determining the thumbnail ratio was reversed. It needs to be $targetWidth * $ratio (instead of $targetWidth / $ratio).
This version of the script doesn't do the same naming manipulation-- it just creates a new thumbs/ folder wherever the script lives and saves the thumbnail versions from gallery/ into it.
Related
I am using a PHP function to add my logo as the watermark on images uploaded on my website. But I don't know why my watermark function is not working for png files. however, it works for jpeg files perfectly. this is my PHP function.
function watermark($img) {
global $wm_file, $wm_right, $wm_bottom;
// image values pulled from config.inc.php
$logo = './images/' . $wm_file; // path to the watermark.png
$sp = $wm_right; // spacing from right side
$sq = $wm_bottom; // spacing from bottom
$size = getImageSize($img);
$sizel = getImageSize($logo);
$imgA = imageCreateFromJpeg($img);
imageAlphaBlending($imgA, TRUE);
if($sizel[0] > $size[0] || $sizel[1] > $size[1])
{
// logo size > img size
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
$sizel[0] = ($sizel[0]/2);
$sizel[1] = ($sizel[1]/2);
}
else
{
$sizelo[0] = $sizel[0];
$sizelo[1] = $sizel[1];
}
$imgBa = imageCreateFromPng($logo);
$imgB = imageCreateTrueColor($sizel[0], $sizel[1]);
imageAlphaBlending($imgB, TRUE);
imageCopyResampled($imgB, $imgBa, 0, 0, 0, 0, $sizel[0], $sizel[1], $sizelo[0], $sizelo[1]);
imageColorTransparent($imgB, ImageColorAllocate($imgB, 0, 0, 0));
$perc = 100;
imageCopymerge($imgA, $imgB, ($size[0]-$sizel[0]-$sp), ($size[1]-$sizel[1]-$sq), 0, 0, $sizel[0], $sizel[1], $perc);
unlink($img);
if(imageJpeg($imgA, $img, 100))
{
imageDestroy($imgB);
imageDestroy($imgA);
return true;
}
chmod($img, 0777);
}
The problem I see is that you are using imageCreateFromJpeg() as the way to generate the resource for your $img that you are passing to the function.
If you pass a jpeg through the function it will work. If you pass a png it will not.
I recommend using imagecreatefromstring() to create all your resources as it is not dependent on the file type. Like so:
$source = imagecreatefromstring(file_get_contents($filePath));
Another benefit of this is that it will return false if the function fails to create a resource from the file path that you supplied meaning that the file is not an image file.
Now that you have a resource to use for the rest of your code, imageJpeg() will save the resource as a jpeg back to the file path.
Hope that helps.
One other side note. If you intend on using bmp images, the GD library does not have a built in function for bmps. However on PHP.net, someone did write a createimagefromBMP() that works really well. Also I think that on the latest version of PHP the GD library does now actually have a createimagefromBMP() function.
I also see that you are using unlink() to delete the image from your directory. This is not necessary for two reasons. The imageJpeg() will just overwrite the original. Also, if for some reason your script fails it may delete the image prematurely and you will loose the image without the new one being written.
Please be careful when using chmod(), always make sure that you set permissions back to the original permissions when you are done.
chmod($img, 777); //Give broad permissions.
//Do something.
chmod($img, 600(or whatever they were)); //Reset permission back to where they were before you changed them.
I am using the following Imagick function to resize an image. The code runs and resizes the image, but with an error from the server saying "An error occurred while requesting the document from the testing server." And I cannot detect the problem. In the browser it, however, outputs non-human readable characters. In case I try to output the resized/modified image to the browser, there is no problem. I face this problem as I try to save the image to the disk.
Here is my code:
<?php
imagick_resize('running.jpg');
function imagick_resize($image, $width = 460, $height = 300)
{
// define widescreen dimensions
// $width = 460;
// $height = 300;
// load an image
$i = new Imagick($_SERVER['DOCUMENT_ROOT'] . '/alchemyapi/' . $image);
// get the current image dimensions
$geo = $i->getImageGeometry();
// crop the image
if(($geo['width']/$width) < ($geo['height']/$height))
{
$i->cropImage($geo['width'], floor($height*$geo['width']/$width), 0, (($geo['height']-($height*$geo['width']/$width))/2));
}
else
{
$i->cropImage(ceil($width*$geo['height']/$height), $geo['height'], (($geo['width']-($width*$geo['height']/$height))/2), 0);
}
// thumbnail the image
$i->ThumbnailImage($width,$height,true);
// save or show or whatever the image
# $i->setImageFormat('png');
# header("Content-Type: image/png");
# unlink('small_square_img.png');
$i->writeImage($_SERVER['DOCUMENT_ROOT'] . '/alchemyapi/tmp/small_square_img.png');
# file_put_contents('small_square_img.png', $i);
exit($i);
}
?>
The data you are sending to the browser is not a valid image. It is quite likely that you have a Byte-Order-Marker in some of your files before the
You should check for that and fix it if it is occuring, or provide an example of the "non-human readable characters" for people to have a clue of what the problem is.
I'm writing a PHP-based image viewer, and I've come across an odd issue. When I click an image to view it, the code says that it should show a resized version of the picture, which it has created and saved to the hard drive (so it doesn't need to remake the image more than once). However, it just shows a broken image and outputs an error complaining that the file it's looking for doesn't exist. If I refresh the page, the file does exist, and loads fine. What is going on here? I was under the impression that PHP was single-threaded
Below is the entirety of image-view.php (I have since deleted this code, as I made it for work, and it's not relevant to the problem I had)
Here is the resize_image() function from functions.php
//Return the path to a new or already existing resized version of the image you are looking for.
//I repeat: THIS FUNCTION RETURNS A PATH. IT DOES NOT RETURN AN ACTUAL IMAGE.
function resize_image($img, $imgPath, $width) {
//error_log("!!!Calling resize_image({$img},{$imgName},{$width},{$imgDir})!!!");
//Put the image in the .cache subdirectory.
$imgName = end(explode("/",$imgPath));
$imgDir = str_replace($imgName,"",$imgPath);
$pathToThumbs = $imgDir . "/.cache/";
$thumbWidth = $width;
$thumbHeight = floor(imagesy($img) * ($width / imagesx($img)));
$thumbName = get_resized_image_name($thumbWidth, $thumbHeight, $imgName);
$resizedPath = "{$pathToThumbs}{$thumbName}";
//Don't make a new image if it already exists.
if (file_exists($pathToThumbs)) {
if (file_exists($resizedPath)) {
error_log("Resized image {$resizedPath} already exists. Exiting.");
return $resizedPath;
}
}
else {
error_log("Cache folder does not exist. Creating.");
$old_umask = umask(0);
mkdir($pathToThumbs, 0777);
umask($old_umask);
}
error_log("Resized image {$resizedPath} does not exist. Creating.");
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
//This is the magic line right here. Create the thumbnail.
imagecopyresized($thumb, $img, 0, 0, 0, 0, $thumbWidth, $thumbHeight, imagesx($img), imagesy($img));
if (!file_exists($pathToThumbs)) {
}
imagepng($thumb, "{$pathToThumbs}{$thumbWidth}x{$thumbHeight}-{$imgName}");
return $thumb;
}
Change:
imagepng($thumb, "{$pathToThumbs}{$thumbWidth}x{$thumbHeight}-{$imgName}");
return $thumb;
To:
$outPath = "{$pathToThumbs}{$thumbWidth}x{$thumbHeight}-{$imgName}";
imagepng($thumb, $outPath);
return $outPath;
The reason why it does not work is because at the first call you are returning $thumb which is returned by imagecreatetruecolor() call.
The problem is it returns an image identifier, not file name. Check the documentation
You can easily fix your code by changing the last line
//return $thumb;
return $resizedPath;
On your first run, you return the resource that you pass in to the imagepng function. On second run, you return the path.
imagepng($thumb, "{$pathToThumbs}{$thumbWidth}x{$thumbHeight}-{$imgName}");
// $thumb is a resource.
return $thumb;
Make the function return the path in both cases.
Our server is saving EXIF data to every file saved with imagejpeg(). As far as I know, this is not the default behavior (or even possible, from what I've read). But, it is occurring, and due to the FileDateTime information being included (and using the time of save), it is breaking functionality in our upload/approval system (md5_file() returns a different value for the exact same image due to FileDateTime always being different).
Is there a way to prevent imagejpeg() from saving EXIF data for images by default?
Server Information
CentOS 5
Parallels Plesk Panel 10.4.4
GD Version: bundled (2.0.34 compatible)
PHP 5.3
Code
<?php
public function upload_book_cover($book, $cover, $filename = NULL, $approved = NULL){
global $c_consummo, $user;
$approved = bool($approved, true, true);
if(filesize($cover)>5242880){
return false; // Too large;
}
$max_width = 450;
$cover_info = getimagesize($cover);
if(!$this->is_valid_book_cover_type($cover_info['mime'])){
return false; // Invalid image type
}
$width = $cover_info[0];
$height = $cover_info[1];
if($width<200){
return false; // Too small
} elseif($width>1500){
return false; // Too wide
}
$original_cover = false;
switch($cover_info[2]){
case IMAGETYPE_GIF:
$original_cover = imagecreatefromgif($cover);
break;
case IMAGETYPE_JPEG:
$original_cover = imagecreatefromjpeg($cover);
break;
case IMAGETYPE_PNG:
$original_cover = imagecreatefrompng($cover);
break;
case IMAGETYPE_BMP:
$original_cover = imagecreatefrombmp($cover);
break;
}
if(!$original_cover){
return false; // Unsupported type
}
if($width>$max_width){
$new_width = $max_width;
} else {
$new_width = $width;
}
$new_height = round($height*($new_width/$width));
$new_cover = imagecreatetruecolor($new_width, $new_height);
if(!$new_cover){
return false; // Could not create true color image
}
if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
return false; // Could not copy image
}
if(!imagejpeg($new_cover, $cover, 100)){
return false; // Image could not be saved to tmp file
// This is adding *new* EXIF data to images by itself
}
$file_hash = md5_file($cover);
$duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
if($duplicate_book_cover){
return $duplicate_book_cover;
}
$file_id = $c_consummo->upload_file($cover, $filename);
...
}
It looks like you have tried several things here, but lets try one more.
Do you EVER need EXIF information in your application?
If not lets take out support for EXIF and see if that completely removes it.
If it does not remove it, then perhaps the functions are reading it from the existing photos and then just blindly including it in the file that is written.
You can know for sure by printing out the EXIF information at each step of process
No idea why EXIF data is being written - so the following may help you remove it.
One suggestion is to run something as a command on the server - it will need some installation: http://hacktux.com/read/remove/exif - then run throuhg EXEC from PHP.
There's also solution posted here that uses ImageMagick if you also have that installed (or cen get it installed: Remove EXIF data from JPG using PHP (but note warning about colour)
Otherwise, the other suggestion is as above, try turning off the EXIT extension.
Sorry if they don't help, but you did ask for any suggestions.
Apparently, GD doesn't like when the path to the input / output file is the same, but the credit isn't mine. To fix, use a new (tmp) file to save the newly created image to:
<?php
...
if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
return false; // Could not copy image
}
// Create a tmp file.
$cover_new = tempnam('/tmp', 'cover-');
// Use $cover_new instead of $cover
if(!imagejpeg($new_cover, $cover_new, 100)){
return false; // Image could not be saved to tmp file
}
// Use $cover_new instead of $cover
$file_hash = md5_file($cover_new);
$duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
if($duplicate_book_cover){
return $duplicate_book_cover;
}
// Use $cover_new instead of $cover
$file_id = $c_consummo->upload_file($cover_new, $filename);
...
Read this, maybe it will help:
http://www.php.net/manual/en/function.imagecreatefromjpeg.php#65656
and this:
http://www.sentex.net/~mwandel/jhead/usage.html
You could possibly convert the jpeg to a gif first, then convert the gif back to a jpeg. In doing so, my understanding is that you would destroy the EXIF data. It's a hack, but it should work.
The only thing that spring to mind is, that you could ignore the EXIF data and just make your hash from something else? My suggestion would be to try either hashing the raw output without saving to a file (here outputting in gif format -which makes a smaller, 8-bit image for performance- to an output buffer and hashing the buffer content)
if(!imagecopyresampled(...)) {...}
ob_start();
imagegif($new_cover);
$file_hash = md5(ob_get_contents());
ob_end_clean();
if(!imagejpeg($new_cover, $cover, 100)) {...}
Or you could build a string containing only the pixel information and hash that (here done by accessing each pixel and appending its value to a string, in reverse for performance)
$pixels = '';
for($x=$new_width-1; $x>=0; $x--) {
for($y=$new_height-1; $y>=0; $y--) {
$pixels .= imagecolorat($new_cover, $x, $y);
}
}
$file_hash = md5($pixels);
For performance, you could also choose only to take samples from the image, as this should work just as well or maybe even better (here sampling every 5th pixel of every 5th row)
$pixels = '';
for($x=$new_width-1; $x>=0; $x-=5) {
for($y=$new_height-1; $y>=0; $y-=5) {
$pixels .= imagecolorat($new_cover, $x, $y);
}
}
$file_hash = md5($pixels);
I hope some of this will work (as I can't test it right now) or at least will help you find the way that works for you :)
This is a bit of a hack, but it will work, just do this after your switch() statement:
$original_cover = imagerotate($original_cover,360,0);
GD will strip any EXIF data out, as it doesn't support it.
You could try using imagecreatefromjpeg on the image created with imagejpeg and replacing with the newly created one.
$res = imagecreatefromjpeg($filename) to load the image, then imagejpeg($res, $filename, QUALITY)to rerender it.
You can use imagemagick too:
$img = new Imagick($image);
$img->stripImage();
$img->writeImage($image);
$img->clear();
$img->destroy();
Your PHP must be compiled in with --enable-exif.
Try to disable globally EXIF functionality by recompiling PHP without this option.
Is there any way to remove the EXIF data from a JPG using PHP? I have heard of PEL, but I'm hoping there's a simpler way. I am uploading images that will be displayed online and would like the EXIF data removed.
Thanks!
EDIT: I don't/can't install ImageMagick.
Use gd to recreate the graphical part of the image in a new one, that you save with another name.
See PHP gd
edit 2017
Use the new Imagick feature.
Open Image:
<?php
$incoming_file = '/Users/John/Desktop/file_loco.jpg';
$img = new Imagick(realpath($incoming_file));
Be sure to keep any ICC profile in the image
$profiles = $img->getImageProfiles("icc", true);
then strip image, and put the profile back if any
$img->stripImage();
if(!empty($profiles)) {
$img->profileImage("icc", $profiles['icc']);
}
Comes from this PHP page, see comment from Max Eremin down the page.
A fast way to do it in PHP using ImageMagick (Assuming you have it installed and enabled).
<?php
$images = glob('*.jpg');
foreach($images as $image)
{
try
{
$img = new Imagick($image);
$img->stripImage();
$img->writeImage($image);
$img->clear();
$img->destroy();
echo "Removed EXIF data from $image. \n";
} catch(Exception $e) {
echo 'Exception caught: ', $e->getMessage(), "\n";
}
}
?>
I was looking for a solution to this as well. In the end I used PHP to rewrite the JPEG with ALL Exif data removed. I didn't need any of it for my purposes.
This option has several advantages...
The file is smaller because the EXIF data is gone.
There is no loss of image quality (because the image data is unchanged).
Also a note on using imagecreatefromjpeg: I tried this and my files got bigger. If you set quality to 100, your file will be LARGER, because the image has been resampled, and then stored in a lossless way. And if you don't use quality 100, you lose image quality. The ONLY way to avoid resampling is to not use imagecreatefromjpeg.
Here is my function...
/**
* Remove EXIF from a JPEG file.
* #param string $old Path to original jpeg file (input).
* #param string $new Path to new jpeg file (output).
*/
function removeExif($old, $new)
{
// Open the input file for binary reading
$f1 = fopen($old, 'rb');
// Open the output file for binary writing
$f2 = fopen($new, 'wb');
// Find EXIF marker
while (($s = fread($f1, 2))) {
$word = unpack('ni', $s)['i'];
if ($word == 0xFFE1) {
// Read length (includes the word used for the length)
$s = fread($f1, 2);
$len = unpack('ni', $s)['i'];
// Skip the EXIF info
fread($f1, $len - 2);
break;
} else {
fwrite($f2, $s, 2);
}
}
// Write the rest of the file
while (($s = fread($f1, 4096))) {
fwrite($f2, $s, strlen($s));
}
fclose($f1);
fclose($f2);
}
The code is pretty simple. It opens the input file for reading and the output file for writing, and then starts reading the input file. It data from one to the other. Once it reaches the EXIF marker, it reads the length of the EXIF record and skips over that number of bytes. It then continues by reading and writing the remaining data.
The following will remove all EXIF data of a jpeg file. This will make a copy of original file without EXIF and remove the old file. Use 100 quality not to loose any quality details of picture.
$path = "/image.jpg";
$img = imagecreatefromjpeg ($path);
imagejpeg ($img, $path, 100);
imagedestroy ($img);
(simple approximation to the graph can be found here)
function remove_exif($in, $out)
{
$buffer_len = 4096;
$fd_in = fopen($in, 'rb');
$fd_out = fopen($out, 'wb');
while (($buffer = fread($fd_in, $buffer_len)))
{
// \xFF\xE1\xHH\xLLExif\x00\x00 - Exif
// \xFF\xE1\xHH\xLLhttp:// - XMP
// \xFF\xE2\xHH\xLLICC_PROFILE - ICC
// \xFF\xED\xHH\xLLPhotoshop - PH
while (preg_match('/\xFF[\xE1\xE2\xED\xEE](.)(.)(exif|photoshop|http:|icc_profile|adobe)/si', $buffer, $match, PREG_OFFSET_CAPTURE))
{
echo "found: '{$match[3][0]}' marker\n";
$len = ord($match[1][0]) * 256 + ord($match[2][0]);
echo "length: {$len} bytes\n";
echo "write: {$match[0][1]} bytes to output file\n";
fwrite($fd_out, substr($buffer, 0, $match[0][1]));
$filepos = $match[0][1] + 2 + $len - strlen($buffer);
fseek($fd_in, $filepos, SEEK_CUR);
echo "seek to: ".ftell($fd_in)."\n";
$buffer = fread($fd_in, $buffer_len);
}
echo "write: ".strlen($buffer)." bytes to output file\n";
fwrite($fd_out, $buffer, strlen($buffer));
}
fclose($fd_out);
fclose($fd_in);
}
It is a prototype for a call from a command line.
this is the simplest way:
$images = glob($location.'/*.jpg');
foreach($images as $image) {
$img = imagecreatefromjpeg($image);
imagejpeg($img,$image,100);
}
I completely misunderstood your question.
You could use some command line tool to do this job. or write your own php extension to do it. have a look at this lib that would be useful: http://www.sno.phy.queensu.ca/~phil/exiftool/
Cheers,
vfn
I'm not pretty sure about it, but if its possible using GD o ImageMagick, the first thing that come to my mind is to create a new Image and add the old image to the new one.