Extend ImageMagick from FocusCrop

HI
I’m trying to create a new Kirby plugin to use ImageMagick’s image effects. Sonja recommended that I rely on the focuscrop plugin.

Activating im on config.php, I first simply modify the initial grayscale function contained in the file src/Focus /ImageMagick.php (see file here)

protected function grayscale(string $file, array $options)
  {
    if ($options['grayscale'] === true) {
    return '-colorspace gray';
  }
}

by

protected function grayscale(string $file, array $options)
  {
    if ($options['grayscale'] === true) {
    return '-colorspace gray -random-threshold 0x100%';
  }
}

It works and I get a dither effect on the image. But as soon as I increase the number of functions with different effect parameters, it doesn’t work.

protected function solarize(string $file, array $options)
  {
    if ($options['solarize'] === true) {
    return '-solarize 10%';
  }
}

...

$command[] = $this->solarize($file, $options);

The resulting image is the original one, with no change in size or effects. I don’t know what this is due to as I am only adding one or more functions, respecting the original commands.
Do you have an idea ? Am I forgetting something?

Where exactly have you put that last line?

And you mean adding this function results in no thumbnail created at all?

More precisely, this is my lines from line 47

protected function grayscale(string $file, array $options)
{
    if ($options['grayscale'] === true) {
        return '-colorspace gray -random-threshold 0x100%'; // Added some threshold here, works well
    }
}
protected function solarize(string $file, array $options)  
{
    if ($options['solarize'] === true) {
        return '-solarize 10%'; // New function, work well alone and with grayscale
    }
}
protected function edge(string $file, array $options)
{
    if ($options['edge'] === true) {
        return '-edge 10'; // One more function, neither grayscale, solarize or edge are "working"
    }
}

and these lines are needed at line 84 to output the options effect

        $command[] = $this->solarize($file, $options);
        $command[] = $this->edge($file, $options);

The result : the image thumbnail is created without any effect. I don’t think this comes from “solarize” or “edge” or any other effect.
The problem is that every effect works well independently if I set only two effects (grayscale+solarize for example), but not with three or more…

Does whatever combination of commands you want to execute work on the command line?

It takes times, but yes it’s working with '-colorspace Gray -solarize 10% -edge 10' for example.

If anyone has the courage to try:

  • install kirbyfocuscrop
  • replace src/Focus /ImageMagick.php by the file below
  • add some <?= $image->focusCrop(500, 500, ['solarize' => true])->url() ?> anywhere

My ImageMagick file :

<?php

namespace Flokosiol\Focus;

use Exception;
use Kirby\Image\Image;
use Kirby\Image\Dimensions;
use Kirby\Image\Darkroom;
use Kirby\Toolkit\F;

class ImageMagick extends Darkroom
{
    protected function autoOrient(string $file, array $options)
    {
        if ($options['autoOrient'] === true) {
            return '-auto-orient';
        }
    }

    protected function blur(string $file, array $options)
    {
        if ($options['blur'] !== false) {
            return '-blur 0x' . $options['blur'];
        }
    }

    protected function coalesce(string $file, array $options)
    {
        if (F::extension($file) === 'gif') {
            return '-coalesce';
        }
    }

    protected function convert(string $file, array $options): string
    {
        return sprintf($options['bin'] . ' "%s"', $file);
    }

    protected function defaults(): array
    {
        return parent::defaults() + [
            'bin'       => 'convert',
            'interlace' => false,
        ];
    }

    protected function grayscale(string $file, array $options)
    {
        if ($options['grayscale'] === true) {
            return '-colorspace gray';
        }
    }
    /* ADDED THESE 2 FUNCTIONS */
    protected function solarize(string $file, array $options)  
    {
        if ($options['solarize'] === true) {
            return '-solarize 10%';
        }
    }
    protected function edge(string $file, array $options)
    {
        if ($options['edge'] === true) {
            return '-edge 10';
        }
    }

    protected function interlace(string $file, array $options)
    {
        if ($options['interlace'] === true) {
            return '-interlace line';
        }
    }

    public function process(string $file, array $options = []): array
    {
        $options = $this->preprocess($file, $options);

        // original image dimension for focus cropping
        $originalImage = new Image($file);
        if ($dimensions = $originalImage->dimensions()) {
            $options['originalWidth'] = $dimensions->width();
            $options['originalHeight'] = $dimensions->height();
        }

        $command = [];

        $command[] = $this->convert($file, $options);
        $command[] = $this->strip($file, $options);
        $command[] = $this->interlace($file, $options);
        $command[] = $this->coalesce($file, $options);
        $command[] = $this->grayscale($file, $options);
        $command[] = $this->autoOrient($file, $options);
        $command[] = $this->resize($file, $options);
        $command[] = $this->quality($file, $options);
        $command[] = $this->blur($file, $options);
        $command[] = $this->save($file, $options);
        
        /* ADDED THESE 2 LINES */
        $command[] = $this->solarize($file, $options);
        $command[] = $this->edge($file, $options);

        // remove all null values and join the parts
        $command = implode(' ', array_filter($command));

        // try to execute the command
        exec($command, $output, $return);

        // log broken commands
        if ($return !== 0) {
            throw new Exception('The imagemagick convert command could not be executed: ' . $command);
        }

        return $options;
    }

    protected function quality(string $file, array $options): string
    {
        return '-quality ' . $options['quality'];
    }

    protected function resize(string $file, array $options): string
    {
        // simple resize
        if ($options['crop'] === false) {
            return sprintf('-resize %sx%s!', $options['width'], $options['height']);
        }

        // focus cropping
        if (!empty($options['focus'])) {
            $focusCropValues = \Flokosiol\Focus::cropValues($options);
            return sprintf('-crop %sx%s+%s+%s -resize %sx%s^', $focusCropValues['width'], $focusCropValues['height'], $focusCropValues['x1'], $focusCropValues['y1'], $options['width'], $options['height']);
        }

        $gravities = [
            'top left'     => 'NorthWest',
            'top'          => 'North',
            'top right'    => 'NorthEast',
            'left'         => 'West',
            'center'       => 'Center',
            'right'        => 'East',
            'bottom left'  => 'SouthWest',
            'bottom'       => 'South',
            'bottom right' => 'SouthEast'
        ];

        // translate the gravity option into something imagemagick understands
        $gravity = $gravities[$options['crop']] ?? 'Center';

        $command  = sprintf('-resize %sx%s^', $options['width'], $options['height']);
        $command .= sprintf(' -gravity %s -crop %sx%s+0+0', $gravity, $options['width'], $options['height']);

        return $command;
    }

    protected function save(string $file, array $options): string
    {
        return sprintf('-limit thread 1 "%s"', $file);
    }

    protected function strip(string $file, array $options): string
    {
        return '-strip';
    }
}
1 Like

The problem is that you add the new commands to the $command array after $this->save(), they should come before.

Another thing I noticed, however, is that the option doesn’t get added to the filename, as happens when you use blur, grayscale etc. But I think that could be a potential pitfall and we would have to look into where these are added.

Thank you. Moving the lines before $command[] = $this->save($file, $options); didn’t change anything (but makes sense)
As you mentioned, the extension is not added to the image, it must comes from there.
So, as long as this question is not “resolved”, my proposal to extend ImageMagick with a plugin is not possible? :sob:

In my test it works with solarize(), but not with edge(), not idea if that is a matter of order or of generally not working.

It’s like Photoshop’s filters, if you add “clouds” and “noise” filters, the results is different from “noise” and then “clouds”. The order changes the result, not the execution. In addition, solarize and edge are the most primitive functions from ImageMagick.

(If you keep edge and remove solarize, edge will work…)

But as you mentionned, for solarize(), there is no “…-so.jpg” as extension planned in the core or “…-ed.jpg” for edge(). That could probably comes from here, right ?

I don’t know that would require a deeper look into the original Darkroom class and what’s happening there (not today though…)

Ok, let’s take a break :slight_smile:
I just let this image here, because I personnaly think it is an incredible way to transform images (here -random threshold), and to wish you a happy new year ! xx

1 Like

Thank you, Olivier!

Bonne année!

I tried to track it down with Xdebug but haven’t been able yet to find out why adding more than one option doesn’t work.

The problem with the file naming of the thumbs happens in kirby/src/Cms/Filename.php, the naming options there are limited to dimensions, crop, q(uality), blur & bw.

Thanks a lot for your time !
If you don’t know where it comes from, that’s a very bad sign for me :slight_smile: .
Any advices or experts I can ask ?

The rest of the team :wink: