Search code examples
phpimagemagickimagickico

Convert ICO to PNG using PHP Imagick


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

Solution

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