cdowdy / boltbetterthumbs

Bolt Extension for thumbnails, srcset and picture element using Glide
6 stars 2 forks source link

Option to use original image? #5

Closed saltpeanuts closed 7 years ago

saltpeanuts commented 7 years ago

Is there currently any way to choose to use the original image at a specific width? Or an option to prevent an image being processed if it already matches the w value?

For example, it would be useful when the original is a .png and a user only needs to generate smaller sizes as .jpg files

My experience with using GD to process .png files is that they invariably come back much larger than the original.

Just tested with a 51k .png file which came back at 220k

cdowdy commented 7 years ago

forgive me if I'm misunderstanding you.. but you want to prevent upscaling of images correct?

if that's the case you can use a fit of 'max' see http://glide.thephpleague.com/1.0/api/size/#fit-fit

as an example from the config. Here our source image is 700px wide. in the "large" section use a fit of 'max' and that 700 px image will be used.

srcset:
  widthDensity: 'w'
  sizes: [ '100vw'  ]
  modifications:
    small: 
      w: 175 
      fit': stretch
    medium:
      w: 350
      fit: stretch
    large:
      w: 700
      fit: 'max'
    xlarge:
      w: 1400
      fit: stretch

As for a true "pass through" no there isn't a way to do that currently.

I just looked through Glide's PNG processing and their docs and quality is only relevant for jpeg images so it looks like it's maybe adding transparency to PNG images (even if there is none) so even with a config like:

srcset:
  widthDensity: 'w'
  sizes: [ '100vw'  ]
  modifications:
    small:
      w: 200
      fit: 'crop'
      fm: 'jpg'
    medium:
      w: 350
      fit: 'crop'
      fm: 'jpg'
    large:
      w: 800
      fit: 'max'  

With a PNG image that is 800px wide (and ~745KB) it will be 800px wide and ~830KB for the "large" image.

Let me know if that's what you were looking for.

As for the PNG file size "issues" if I find some time maybe I'll see if i can put together a custom manipulator (or a size comparison check? but that might add a bit of overhead getting the image from the cache since file names aren't actual names) that merges JPG quality with PNG quality similar to how Bolt's thumbnail handler does it https://github.com/bolt/bolt-thumbs/blob/9e55c0a5389e17811d9f5954c297437c6a5e8cb9/src/ImageResource.php#L509-L523

saltpeanuts commented 7 years ago

Yes, I was thinking more of a pass-through. fit: max seems to work the same as Bolt's allow_upscale: false which is great. But in some instances it would be useful to leave the original file completely untouched.

If the original file has been exported from a graphics package already at the largest pixel-size it's ever going to be used on the site, and the image has already been optimised at the best quality, lowest filesize and sharpened just right for it's content, it would be best if that file is left untouched. Instead I'd prefer to just generate smaller versions from it.

I think this would be true regardless of format, but using .png causes extra complications because it seems that GD inflates the file-size significantly. That's a separate but related issue.


Here's a .png test case to clarify:

I have an original 640px wide png image. That's the maximum size it will be used. It was resized and sharpened in photoshop, then optimised in pngquant. It's the best balance of image quality and file size I can get. it's 52k

Now for my srcset I'd like a version at 320, 480 and say 580

If I set things up like this and stick to the .png format:

    small:
      w: 320
      fit: max
      fm: png
    medium:
      w: 480
      fit: max
      fm: png
    large:
      w: 580
      fit: max
      fm: png
    orig:
      w: 640
      fit: max
      fm: png

File sizes come out like this:

small: 81k medium: 186k large: 273k orig: 242k

So the original 640px version now has a dramatically larger file size and even the smallest 320px size is significantly larger than the original.

I've read around a little bit and it looks like others have experienced this problem with GD and .png, there's a few suggestions like this: http://stackoverflow.com/questions/13257405/why-is-resized-png-image-so-much-bigger-than-original-image

Next approach was to accept the problem and look for a workaround: to use the original .png and save the srcset images as .jpg, This works out ok for the smaller versions, each saw a decent saving in file size while still looking ok, but the original would need a much higher file size to look decent as a .jpg than as a .png. Being able to use the untouched .png at the 640 size would provide a useful workaround


So to recap, I guess having a fix for the .png problem would be ideal. But the pass-through would be a workaround, and also really useful in cases where the user wants to make sure their original large image is used totally untouched, whatever the file format.

cdowdy commented 7 years ago

I could toy with a passthrough since every image passed through this extension is at least opened and saved to glides cache (which leads to the PNG file bloat).

It'll take some time because you'd have to either compare the original source image to the sizes passed in by the modifications, which might add some overhead for a config block with lots of sizes or set a flag in the relevant modifications block like use_original: true and instead of being served from img/file.jpg?modifications it would be served from disk @ /files/file.jpg

That final one right now seems best / more perfomant since I have no control over glide not opening and re-saving an image passed to it. I'd just have to remove that from the array of images and then merge it after the modifications are done.

When I get some other things knocked out on my to-do list I'll look into it and see if it doesn't cause other issues :)

saltpeanuts commented 7 years ago

The second solution with use_original: true serving the original from /files sounds perfect.

The only other approach I thought of was maybe a config option to output just the paths to the resized images, instead of wrapping them in the full srcset tags. Then a developer could use those to build the srcset themselves however they wanted, if they had more esoteric requirements.

use_original definitely sounds like a tidier solution, though.

Thanks for the work so far, it's a really useful extension.

cdowdy commented 7 years ago

@saltpeanuts ok.. I've added an option to allow an original image to be passed through. Its still in dev mode but if you grab the master release you can play with it.

To use it with a width descriptor. Place the width of your image after use_original.

srcset:
  widthDensity: 'w'
  sizes: [ '100vw'  ]
  use_original: 800
  modifications:
    small: 
      w: 175 
      fit': stretch
    medium:
      w: 350
      fit: stretch
    xlarge:
      w: 1400
      fit: stretch

will give you

<img sizes="100vw"
     srcset="/img/original-file.jpg?w=175&s=32566d6ed62dc4d87a44c099033650d5 175w ,
       /img/original-file.jpg?w=350&s=dd1374de85dd1e5c6eafb394309cc950 350w ,
       /files/original-file.jpg 800w ,
       /img/original-file.jpg?w=1400&s=5f48fd674c5ad3214f63b1150c16c611 1400w "
     src="/img/original-file.jpg?w=200&s=32566d6ed62dc4d87a44c099033650d5"
     alt="">;

To use it with density descriptor place the density after use_original. If you have the density in your resolutions settings already just remove it and place it after use_original

srcset:
  widthDensity: 'x'
  resolutions: [ 1 2  ]
  use_original: 2.5
  modifications:
    small: 
      w: 175 
      fit': stretch
    medium:
      w: 350
      fit: stretch
<img sizes="100vw"
     srcset="/img/original-file.jpg?w=175&s=32566d6ed62dc4d87a44c099033650d5 1x ,
       /img/original-file.jpg?w=350&s=dd1374de85dd1e5c6eafb394309cc950 2x ,
       /files/original-file.jpg 2.5x"
     src="/img/original-file.jpg?w=200&s=32566d6ed62dc4d87a44c099033650d5"
     alt="">;

If you use it let me know if you run into any bugs/edge cases/ooooo cory you done messed up situations haha.

Thanks!

saltpeanuts commented 7 years ago

Working fine for me, I haven't managed to break it so far.

Thanks Cory.