Convert ICO to PNG using PHP Imagick - php

I am currently trying to convert an ICO file to a 16x16 px PNG, using PHP-Imagick. What I've tried so far:
<?php
if (empty(\Imagick::queryFormats("ICO"))) {
throw new \Exception('Unsupported format');
}
$sourceFile = __DIR__ . '/favicon.ico';
$targetFile = __DIR__ . '/favicon.png';
$im = new \Imagick($sourceFile);
$im->writeImages($targetFile, true);
This works partially. The problem is, that an ICO file may contain multiple images, so the code above creates multiple PNG files
favicon-0.png
favicon-1.png
...
for every size. This is okay, but then, I need the possibility to find the one that is close to 16x16 px, scale it down (if needed) and delete all others. For this, I already tried some things and this is where I'm stuck currently:
<?php
if (empty(\Imagick::queryFormats("ICO"))) {
throw new \Exception('Unsupported format');
}
$sourceFile = __DIR__ . '/favicon.ico';
$targetFile = __DIR__ . '/favicon.png';
$im = new \Imagick($sourceFile);
$count = $im->getNumberImages();
if ($count > 1) {
for ($x = 1; $x <= $count; $x++) {
$im->previousImage();
$tmpImageWidth = $im->getImageWidth();
$tmpImageHeight = $im->getImageHeight();
// ???
}
}
$im->writeImages($targetFile, true);
I guess, i would find a working way with some trial and error. But I would like to know, if there's an easier way to achieve this.
TL;DR: I need a simple way to convert an ICO file of any size to a 16x16 px PNG, using PHP-Imagick (using GD isn't an option).
Update:
My (currently working but maybe suboptimal) solution:
<?php
if (empty(\Imagick::queryFormats("ICO"))) {
throw new \Exception('Unsupported format');
}
$sourceFile = __DIR__ . '/favicon.ico';
$targetFile = __DIR__ . '/favicon.png';
$im = new \Imagick($sourceFile);
$count = $im->getNumberImages();
$targetWidth = $targetHeight = 16;
if ($count > 1) {
$images = [];
for ($x = 1; $x <= $count; $x++) {
$im->previousImage();
$images[] = [
'object' => $im,
'size' => $im->getImageWidth() + $im->getImageHeight()
];
}
$minSize = min(array_column($images, 'size'));
$image = array_values(array_filter($images, function($image) use ($minSize) {
return $minSize === $image['size'];
}))[0];
$im = $image['object'];
if ($image['size'] <> $targetWidth + $targetHeight) {
$im->cropThumbnailImage($targetWidth, $targetHeight);
}
}
else {
if ($im->getImageWidth() <> $targetWidth || $im->getImageHeight() <> $targetHeight) {
$im->cropThumbnailImage($targetWidth, $targetHeight);
}
}
$im->writeImage($targetFile);

Updated Answer
On re-reading your question, it seems you actually want to make a PNG file from an ICO file. I have read the Wikipedia entry for ICO files and, as usual, it is a poorly specified complicated mess of closed source Microsoft stuff. I can't tell if they come in some order.. smallest first, or largest first, so I think my recommendation would be to simply iterate through all the images in your ICO file, as you planned, and get the one with the largest number of pixels and resize that to 16x16.
Original Answer
Too much for a comment, maybe not enough for an answer... I don't use PHP Imagick much at alll, but if you use ImageMagick at the command-line in the Terminal, you can set the ICO sizes like this:
magick INPUT -define icon:auto-resize="256,128,96,64,16" output.ico
to say what resolutions you want embedded in the output file. As I said, I don't use PHP, but I believe the equivalent is something like:
$imagick->setOption('icon:auto-resize', "16");
Sorry I can't help better, I am just not set up to use PHP and Imagick, but hopefully you can work it out from here.

My final solution:
<?php
if (empty(\Imagick::queryFormats("ICO"))) {
throw new \Exception('Unsupported format');
}
$sourceFile = __DIR__ . '/favicon.ico';
$targetFile = __DIR__ . '/favicon.png';
$im = new \Imagick($sourceFile);
$count = $im->getNumberImages();
$targetWidth = $targetHeight = 16;
if ($count > 1) {
$images = [];
for ($x = 1; $x <= $count; $x++) {
$im->previousImage();
$images[] = [
'object' => $im,
'size' => $im->getImageWidth() + $im->getImageHeight()
];
}
$minSize = min(array_column($images, 'size'));
$image = array_values(array_filter($images, function($image) use ($minSize) {
return $minSize === $image['size'];
}))[0];
$im = $image['object'];
if ($image['size'] <> $targetWidth + $targetHeight) {
$im->cropThumbnailImage($targetWidth, $targetHeight);
}
}
else {
if ($im->getImageWidth() <> $targetWidth || $im->getImageHeight() <> $targetHeight) {
$im->cropThumbnailImage($targetWidth, $targetHeight);
}
}
$im->writeImage($targetFile);

Related

PHP Imagick Resizing Gif frames has weird issues

I am trying to resize all frames of a gif and sometimes they turn out extremely weird.
I've seen examples using the command line and I would like to try and avoid this for now.
Original:
Resized:
You can see clearly the problem.
Now my code:
$imgBlob = file_get_contents(__DIR__ . '/../assets/test_gif.gif');
if ($imgBlob === false) {
echo 'img blob failed!' . PHP_EOL;
return;
}
$img = new Imagick();
$img->readImageBlob($imgBlob);
$img->coalesceImages();
$count = 0;
foreach ($img as $_img) {
// $_img->coalesceImages();
$imgWidth = $_img->getImageWidth();
$imgHeight = $_img->getImageHeight();
$ratio = $imgHeight / $imgWidth;
$resizeWidth = 200;
$resizeHeight = 300;
if ($ratio > 0) {
$resizeWidth = round($resizeHeight / $ratio, 0);
} else {
$resizeHeight = round($resizeWidth / $ratio, 0);
}
//if ($_img->adaptiveResizeImage($resizeWidth, $resizeHeight) === false) {
if ($_img->resizeImage($resizeWidth, $resizeHeight, Imagick::FILTER_CATROM, 1, 0) === false) {
echo 'FAILED' . PHP_EOL;
}
$count++;
}
$thumbnailOnDisk = __DIR__ . '/../assets/test_resized.gif';
if (file_exists($thumbnailOnDisk)) {
unlink($thumbnailOnDisk);
}
$img = $img->deconstructImages();
if ($count > 1) {
$file = $img->writeImages($thumbnailOnDisk, true);
} else {
$file = $img->writeImage($thumbnailOnDisk);
}
echo 'DONE' . PHP_EOL;
Not sure exactly what coalesceImages or deconstructImages is doing and I am having a hard time finding an example online that would fix my problem.
$img->coalesceImages();
Returns an imagick object, which I was discarding.
$img = $img->coalesceImages();
Works.
As you resize the image, you need to set size of image page as well.
http://php.net/manual/en/imagick.setimagepage.php
I have read once that the header of the gif might not say the correct image size in some cases. This is why it is useful to set the image page any way.
Example:
`$image->resizeImage(120, 110, imagick::FILTER_CATROM, 1);
// Set the page size
$image->setImagePage(120, 110, 0, 0);

Downscaling with php imagick

I'm having trouble limiting the output of a pdf-to-jpg converter using imagick, but I can't seem to find the right solution for only resizing images larger than 16 megapixel.
https://stackoverflow.com/a/6387086/3405380 has the solution I'm looking for, but I can't find the php equivalent of convert -resize '180000#>' screen.jpg (in my case that would be '16000000#>'). I tried $imagick->resizeImage(Imagick::RESOURCETYPE_AREA, 4000 * 4000);, but that just cuts off the conversion instead of resizing.
public static function storeFilePdfToJpg($file) {
$destinationPath = public_path() . Config::get('my.image_upload_path');
$filename = str_random(10) . '_' . str_replace('.pdf', '', $file->getClientOriginalName()) . '.jpg';
$imagick = new Imagick();
$imagick->setResolution(350,350);
$imagick->readImage($file->getPathName());
$imagick->setImageCompression(imagick::COMPRESSION_JPEG);
$imagick->setImageCompressionQuality(100);
$imagick->resizeImage(Imagick::RESOURCETYPE_AREA, 4000 * 4000);
$imagick->writeImages($destinationPath . '/' . $filename, false);
return Config::get('my.full_image_upload_path') . $filename;
}
The C api for Image Magick doesn't (afaik) expose this functionality, so it isn't possible for the PHP Imagick extension to implement this.
This is pretty easy to implement in PHP:
function openImageWithArea(int $desiredArea, string $filename) : Imagick
{
$image = new Imagick();
$dataRead = $image->pingImage($filename);
if (!$dataRead) {
throw new \Exception("Failed to ping image of filename $filename");
}
$width = $image->getImageWidth();
$height = $image->getImageHeight();
$area = $width * $height;
$ratio = $desiredArea / $area;
$desiredWidth = (int)($ratio * $width);
$desiredHeight = (int)($ratio * $height);
$imagick = new Imagick();
$imagick->setSize($desiredWidth, $desiredHeight);
$imagick->readImage($file);
return $imagick;
}
Should work.

Compress and RESCALE uploaded image

I have a function that uploads files up to 8MB but now I also want to compress or at least rescale larger images, so my output image won't be any bigger than 100-200 KB and 1000x1000px resolution. How can I implement compress and rescale (proportional) in my function?
function uploadFile($file, $file_restrictions = '', $user_id, $sub_folder = '') {
global $path_app;
$new_file_name = generateRandomString(20);
if($sub_folder != '') {
if(!file_exists('media/'.$user_id.'/'.$sub_folder.'/')) {
mkdir('media/'.$user_id.'/'.$sub_folder, 0777);
}
$sub_folder = $sub_folder.'/';
}
else {
$sub_folder = '';
}
$uploadDir = 'media/'.$user_id.'/'.$sub_folder;
$uploadDirO = 'media/'.$user_id.'/'.$sub_folder;
$finalDir = $path_app.'/media/'.$user_id.'/'.$sub_folder;
$fileExt = explode(".", basename($file['name']));
$uploadExt = $fileExt[count($fileExt) - 1];
$uploadName = $new_file_name.'_cache.'.$uploadExt;
$uploadDir = $uploadDir.$uploadName;
$restriction_ok = true;
if(!empty($file_restrictions)) {
if(strpos($file_restrictions, $uploadExt) === false) {
$restriction_ok = false;
}
}
if($restriction_ok == false) {
return '';
}
else {
if(move_uploaded_file($file['tmp_name'], $uploadDir)) {
$image_info = getimagesize($uploadDir);
$image_width = $image_info[0];
$image_height = $image_info[1];
if($file['size'] > 8000000) {
unlink($uploadDir);
return '';
}
else {
$finalUploadName = $new_file_name.'.'.$uploadExt;
rename($uploadDirO.$uploadName, $uploadDirO.$finalUploadName);
return $finalDir.$finalUploadName;
}
}
else {
return '';
}
}
}
For the rescaling I use a function like this:
function dimensions($width,$height,$maxWidth,$maxHeight)
// given maximum dimensions this tries to fill that as best as possible
{
// get new sizes
if ($width > $maxWidth) {
$height = Round($maxWidth*$height/$width);
$width = $maxWidth;
}
if ($height > $maxHeight) {
$width = Round($maxHeight*$width/$height);
$height = $maxHeight;
}
// return array with new size
return array('width' => $width,'height' => $height);
}
The compression is done by a PHP function:
// set limits
$maxWidth = 1000;
$maxHeight = 1000;
// read source
$source = imagecreatefromjpeg($originalImageFile);
// get the possible dimensions of destination and extract
$dims = dimensions(imagesx($source),imagesy($source),$maxWidth,$maxHeight);
// prepare destination
$dest = imagecreatetruecolor($dims['width'],$dims['height']);
// copy in high-quality
imagecopyresampled($dest,$source,0,0,0,0,
$width,$height,imagesx($source),imagesy($source));
// save file
imagejpeg($dest,$destinationImageFile,85);
// clear both copies from memory
imagedestroy($source);
imagedestroy($dest);
You will have to supply $originalImageFile and $destinationImageFile. This stuff comes from a class I use, so I edited it quite a lot, but the basic functionality is there. I left out any error checking, so you still need to add that. Note that the 85 in imagejpeg() denotes the amount of compression.
you can use a simple one line solution through imagemagic library the command will like this
$image="path to image";
$res="option to resize"; i.e 25% small , 50% small or anything else
exec("convert ".$image." -resize ".$res." ".$image);
with this you can rotate resize and many other image customization
Take a look on imagecopyresampled(), There is also a example that how to implement it, For compression take a look on imagejpeg() the third parameter helps to set quality of the image, 100 means (best quality, biggest file) and if you skip the last option then default quality is 75 which is good and compress it.

Read text in image with PHP

I'm trying to read the text from this image:
I want to read the price, e.g. "EUR42721.92"
I tried these libraries:
How to Create a PHP Captcha Decoder with PHP OCR Class: Recognize text & objects in graphical images - PHP Classes
phpOCR: Optical Character Recognizer written in PHP
But they don't work. How can I read the text?
Try this (it worked with me):
$imagick = new Imagick($filePath);
$size = $imagick->getImageGeometry();
$width = $size['width'];
$height = $size['height'];
unset($size);
$textBottomPosition = $height-1;
$textRightPosition = $width;
$black = new ImagickPixel('#000000');
$gray = new ImagickPixel('#C0C0C0');
$textRight = 0;
$textLeft = 0;
$textBottom = 0;
$textTop = $height;
$foundGray = false;
for($x= 0; $x < $width; ++$x) {
for($y = 0; $y < $height; ++$y) {
$pixel = $imagick->getImagePixelColor($x, $y);
$color = $pixel->getColor();
// remove alpha component
$pixel->setColor('rgb(' . $color['r'] . ','
. $color['g'] . ','
. $color['b'] . ')');
// find the first gray pixel and ignore pixels below the gray
if( $pixel->isSimilar($gray, .25) ) {
$foundGray = true;
break;
}
// find the text boundaries
if( $foundGray && $pixel->isSimilar($black, .25) ) {
if( $textLeft === 0 ) {
$textLeft = $x;
} else {
$textRight = $x;
}
if( $y < $textTop ) {
$textTop = $y;
}
if( $y > $textBottom ) {
$textBottom = $y;
}
}
}
}
$textWidth = $textRight - $textLeft;
$textHeight = $textBottom - $textTop;
$imagick->cropImage($textWidth+10, $textHeight+10, $textLeft-5, $textTop-5);
$imagick->scaleImage($textWidth*10, $textHeight*10, true);
$textFilePath = tempnam('/temp', 'text-ocr-') . '.png';
$imagick->writeImage($textFilePath);
$text = str_replace(' ', '', shell_exec('gocr ' . escapeshellarg($textFilePath)));
unlink($textFilePath);
var_dump($text);
You need ImageMagick extension and GOCR installed to run it.
If you can't or don't want to install the ImageMagick extension, I'll send you a GD version with a function to calculate colors distances (it's just an extended Pythagorean Theorem).
Don't forget to set the $filePath value.
The image shows that it looks for a gray pixel to change the $foundGray flag.
After that, it looks for the first and last pixels from the left and from the top.
It crops the image with some padding, the resulting image is resized and it's saved to a temporary file. After that, it's easy to use gocr (or any other OCR command or library). The temporary file can be removed after that.
Improve the quality of the image of the numbers before you start the OCR. Use a drawing program to improve the quality (bigger size, straight lines).
You can either modify the PHP scripts and adapt the pattern recognition to your needs.
https://github.com/ogres/PHP-OCR/blob/master/Image2String.php
Or try out other OCR tools:
https://github.com/thiagoalessio/tesseract-ocr-for-php

IMagick function to cut an image into roughly equally-sized tiles

I'm trying to use the IMagick PHP wrapper to assist in chopping a specified image into a set of tiles (the number of which is variable).
In the ImageMagick documentation there is reference to the -crop operator accepting an optional flag of # that will instruct it to cut an image into "roughly equally-sized divisions" (see here), solving the problem of what to do when the image size is not an exact multiple of the desired tile size.
Does anyone know if there is a way to leverage this functionality in the IMagick PHP wrapper? Is there anything I can use besides cropImage()?
I had to do the same thing (if I'm reading your question correctly). Although it does use cropImage...
function slice_image($name, $imageFileName, $crop_width, $crop_height)
{
$dir = "dir where original image is stored";
$slicesDir = "dir where you want to store the sliced images;
mkdir($slicesDir); //you might want to check to see if it exists first....
$fileName = $dir . $imageFileName;
$img = new Imagick($fileName);
$imgHeight = $img->getImageHeight();
$imgWidth = $img->getImageWidth();
$crop_width_num_times = ceil($imgWidth/$crop_width);
$crop_height_num_times = ceil($imgHeight/$crop_height);
for($i = 0; $i < $crop_width_num_times; $i++)
{
for($j = 0; $j < $crop_height_num_times; $j++)
{
$img = new Imagick($fileName);
$x = ($i * $crop_width);
$y = ($j * $crop_height);
$img->cropImage($crop_width, $crop_height, $x, $y);
$data = $img->getImageBlob();
$newFileName = $slicesDir . $name . "_" . $x . "_" . $y . ".jpg";
$result = file_put_contents ($newFileName, $data);
}
}
}

Categories