Duplicate image and compress it automatically via panel upload?

I want Kirby to make a copy of every image which is uploaded via the panel and let it compress automatically.

What I want to achieve is a blurry effect on a very low-res image while an image is loading.

Do anyone knows how I can do that with Kirby or maybe a plugin?

Here’s a piece of code for a file.create:after hook that I use to automatically downsize uploaded images (I removed a few if clauses, as I only do this to images uploaded to specific templates) that should get you started:

'file.create:after' => function ($file) {
    try {
        kirby()->thumb($file->root(), $file->root(), [
            'width' => 2000,
        ]);
    } catch (Exception $e) {
        throw new Exception($e->getMessage());
    }
},

There are two things you’d need to change: the thumb parameter array (set a width or other specs for your use case) and the target, where the processed file is written. If I am not entirely wrong, the first parameter of the thumb() method is the source file and the second is the target file. You’d need to make sure to provide a string that includes an altered file path and name.

1 Like

Do you really need the duplicates in the content folder? You could create those thumbs in your templates, so they only get created in the media folder and don’t lie around in the content where you would have to exclude them from sections and fields…

1 Like

Thank you @sebastiangreger!

@moonwalk, yes you’re right – the images don’t need to be in the content folder.

I thought this would be a bit easier… To be honest, I don’t really get anything which is written is this piece of code… Is it also copying the image? And how can I tell Kirby to insert the low res image (from which path) first?

Thanks again! :slight_smile:

The code above, in its current form, would replace the uploaded image – hence it would need to be modified to fit your use case.

In kirby()->thumb($file->root(), $file->root(), ['width' => 2000]), the first $file->root() is the file we read (i.e. the uploaded file this hook is executed on), the second $file->root() is the target to write the altered image (currently the same, as my use case was a bit different from yours; i.e. instead of $file->root() you’d want to modify the string returned by $file->root() by changing the file name withing), the array as the third argument tells the Kirby thumb engine how to change the image file).

But as @moonwalk suggests, if you only need this reduced image file for presentation, there is no need to store it in the content folder. You can just call <img src="<?= $image->thumb(['width' => 20]) ?>" alt=""> to output a mini version of an image in your frontend template (these temporary “thumb” versions of an image are then cached in the /media folder and regenerated when needed).

What is the final HTML markup you need to send to the browser?

There’s also this plugin; I haven’t used it myself, but the description sounds like a potential fit to your needs (unless you need that reduced version to generate the blur effect with an existing solution): GitHub - johannschopplich/kirby-blurry-placeholder: 🖼 Blurry image placeholders and lazy loading for better UX

1 Like

@sebastiangreger thank you so much for explaining, now I get it. Is the piece of code even necessary as I only need the low res version for presentation?

I have two Kirby Blocks - in the first you can upload 1 image and in the second block you can upload up to 2 images.
Then I just call the blocks in my HTML like this:

        foreach ($page->blocks()->toBlocks() as $block): ?>
            <div id="<?= $block->id() ?>" class="block block-type-<?= $block->type() ?>">
                <?php if (in_array($block->type(), ['image-1', 'video'])) {
                    snippet('blocks/' . $block->type());
                } elseif ($block->type() === 'image-2') {
                snippet('blocks/' . $block->type(),);
                } else {
                echo $block;
                }
                ?>
            </div>
        <?php endforeach ?>

In that case, the code above is probably overkill (I initially assumed you also planned to generate the blurred image on the server, in which case you could build on that; now it sounds all you need is a thumb generated on the fly and you then do the blurring in the frontend?).

To answer your second question, we’d need to know what is the code in the block snippet that is called (/site/snippets/blocks/<BLOCKNAME>.php). That’s where you’d need to potentially use the <img src="<?= $image->thumb(['width' => 20]) ?>" alt=""> to output a mini version of the image.

1 Like

Sure, makes sense!

First Block with one Image:

<?php /** @var \Kirby\Cms\Block $block */ ?>
<div class="image-1-container">
  <?php foreach ($block->images()->toFiles() as $image): ?>
    <?= $image ?>
  <?php endforeach ?>
  <?php if ($block->imageTag()->isNotEmpty()): ?>
  <div class="image-number">
      <p>
          Abb. <?php echo $imageCounter?>
      </p>
  </div>
  <?php endif ?>
</div>

Second Block with up to 2 Images:

<?php /** @var \Kirby\Cms\Block $block */ ?>
<div class="image-2-container">
  <?php foreach ($block->images()->toFiles() as $image): ?>
    <?= $image ?>
  <?php endforeach ?>
  <div class="image-number">
      <?php if ($block->imageTag()->isNotEmpty()): ?>
        <p>
            Abb. <?php echo $imageCounter?>
        </p>
      <?php endif ?>
      <?php $imageCounter++; if ($block->imageTagTwo()->isNotEmpty()): ?>
        <p>
            Abb. <?php echo $imageCounter?>
        </p>
      <?php endif ?>
  </div>
</div>

<div class="image-2-container-mobile">
<?php $imageCounter = $imageCounter-1; ?>
  <?php foreach ($block->images()->toFiles() as $image): ?>
    <?php if($image->is($block->images()->toFiles()->first())): ?>
      <div class="cm-1-container">
        <?= $image ?>
          <div class="image-number">
            <?php if ($block->imageTag()->isNotEmpty()): ?>
              <p>
                  Abb. <?php echo $imageCounter?>
              </p>
            <?php endif ?>
          </div>
      </div>
    <?php else: ?>
      <div class="cm-2-container">
        <?= $image ?>
          <div class="image-number">
            <?php $imageCounter++; if ($block->imageTagTwo()->isNotEmpty()): ?>
              <p>
                  Abb. <?php echo $imageCounter?>
              </p>
            <?php endif ?>
          </div>
      </div>
    <?php endif ?>
  <?php endforeach ?>
</div>

(The whole stuff you see in my blocks is generating a footnote with the number of the image, just ignored it :slight_smile: )

Yes, I am creating the blurry effect in the frontend but then I am yet not sure how I can change the image-thumb to the original res as soon as it completes loading…

Thanks for the plugin suggestion, which looks very suitable indeed! But maybe it’s even too complex for my desired solution.

I didn’t know it was possible, it’s beautiful!!

1 Like

In your block snippets, replacing

<?= $image ?>

with

<img src="<?= $image->thumb(['width' => 20]) ?>" alt="">

should render an image tag that presents the tiny version of the file (20 just as an example, depending on what you need).

To include the original image URL, as it will probably be used by your lazy loading script you can include it for example as a data attribute:

<img src="<?= $image->thumb(['width' => 20]) ?>" alt="" data-original="<?= $image->url() ?>">

But, indeed, you’d need a lazy loader script to replace the original src. If you don’t have a ready solution selected yet, the plugin might well be the easiest path for you – replacing images is a rather complex endeavour with many pitfalls.

1 Like

Thanks first of all!
I’ll take a look at both solutions as I haven’t implemented lazy load yet.

Hey @sebastiangreger, the plugin seems to be very complicated and I think a lazy loader would be easier… do you have any recommendation or any tips what the lazy loader needs to have in order to work with the code above? :slight_smile:

I tend to use lazysizes: GitHub - aFarkas/lazysizes: High performance and SEO friendly lazy loader for images (responsive and normal), iframes and more, that detects any visibility changes triggered through user interaction, CSS or JavaScript without configuration.

Lozad.js is also quite popular: Lozad.js | lozad.js

Looks good, and is it also adaptable with Kirby because the uploaded images always are different in width and height?

Yes, sure.

Hi,

I saw that I have to assign class=“lazyload” and “data-src” to my image, but how can I do that in my Kirby code? I think it’s completely different?

  <?php foreach ($block->images()->toFiles() as $image): ?>
    <?= $image ?>
  <?php endforeach ?>

It should be like this:

<img data-src="image.jpg" class="lazyload" />

Instead of echoing the image, you create the tag manually:

<?php foreach ($block->images()->toFiles() as $image): ?>
    <img data-src="<?= $image->url() ?>" class="lazyload" />
  <?php endforeach ?>
1 Like

Makes sense, thanks!
But it does not seem to work yet, all images are loaded instantly… I just have to use the code above and add the lazysizes.js, right? Or am I missing something which is needed for Kirby especially?