1
votes

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);
2
Looks like you have a working solution, well done! Put it as an answer and accept it as correct and bag the points!Mark Setchell

2 Answers

1
votes

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.

0
votes

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