janko / image_processing

High-level image processing wrapper for libvips and ImageMagick/GraphicsMagick
MIT License
868 stars 77 forks source link

Can we still use cmd blocks with the new API? #16

Closed mokolabs closed 6 years ago

mokolabs commented 6 years ago

Hey, Janko. The new API looks awesome.

Quick question, tho. I'm passing in custom image magick commands via the cmd block:

resize_to_fill(image, 1024, 1024) do |cmd|
  cmd.interlace('plane')
  cmd.quality 85
  cmd.sharpen "0x1.0"
end

How can I do this with the new API?

Thanks!

mokolabs commented 6 years ago

And, btw, if cmd blocks are no longer supported for the minimagick implementation (which would be a bummer), is there another way that I can save progressive JPEGs with 85% quality and support for mild sharpening?

mokolabs commented 6 years ago

Oh, I think I now see the answer in the README. Sorry... I looked there, but somehow missed it before.

I would use #method_missing or #append, right?

janko commented 6 years ago

The new API looks awesome.

Thank you ❤️

How can I do this with the new API?

First, note that the MiniMagick module still hasn't been converted to the new API, though I have the implementation finished locally and will push it out probably tomorrow.

The chainable API will automatically delegate any undefined methods to the internal convert command (just like the new Vips module is delegating any undefined methods to the internal Vips::Image object), so the equivalent of

resize_to_fill(image, 1024, 1024) do |cmd|
  cmd.interlace('plane')
  cmd.quality 85
  cmd.sharpen "0x1.0"
end

would with the new API be

ImageProcessing::MiniMagick
  .interlace("plane")
  .quality(85)
  .sharpen("0x1.0")
  .resize_to_fill(1024, 1024)
  .call(image)

And yes, that's the #method_missing section.

janko commented 6 years ago

My bad, it seems that I already pushed out the code to master 😃. I will then probably release it some time tomorrow.

mokolabs commented 6 years ago

Thanks for the quick reply.

While I have you... I'm working on a new app that's using Shrine and minimagick-based image_processing, but I have been experimenting with vips support.

In ruby-vips, you can do this:

file = Vips::Image.new_from_file("apple.jpg")

processed = ImageProcessing::Vips
  .source(file)
  .autorot
  .resize_to_limit(1024, 1024)
  .sharpen(sigma: 1, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8)
  .call(save: false)

processed.write_to_file("apple2.jpg", Q: 85, interlace: true)

Can we set those same quality and interlacing options using vips image_processing?

mokolabs commented 6 years ago

Actually, I guess I'm already doing it with your code.

But, to slightly refactor my question, in the context of generating versions and using the processing plugin for Shrine, can I use #write_to_file when I generate versions in the process :store block?

Will it just work? Or do I need to tweak things a bit?

janko commented 6 years ago

@mokolabs You can use the Vips::Image#write_to_file when generating versions in Shrine, as it doesn't care how the processing has been done, it only cares that you return a list of IO objects at the end of the process block:

# ... processing ...

File.open("apple2.jpg", "rb") # file object to return at the end of the processing block

Btw, I plan to extend the new API to accept path strings as source and destination, after which you'll be able to do:

ImageProcessing::Vips
  .source("apple.jpg")
  .autorot
  .resize_to_limit(1024, 1024)
  .sharpen(sigma: 1, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8)
  .saver(Q: 85, interlace: true)
  .call(destination: "apple2.jpg")

The autorotation also won't be necessary, as I just pushed a change which makes it automatic (but still opt-outable).

mokolabs commented 6 years ago

Awesome.

Yeah, that code came from a test script where I was comparing minimagick with vips.

So, for the app using Shrine, I don't need to save the file to a different destination, but looks like that #saver method call is what I needed.

I'm seeing a 6X speed increase with the vips code in the Shrine app. Wow!

Thanks!

janko commented 6 years ago

@mokolabs Yeah, libvips has really impressive speed! I also like that it has automatic caching, so when you're generating versions you don't need to generate the next version from the previous version for performance, you can generate all versions from the original image, and that will have roughly the same performance in total. This is not the case with ImageMagick, where there is a big performance difference between the two ways.

janko commented 6 years ago

While I have you, I noticed that both vips-process and carrierwave-vips gems are applying sharpening by default, just like you're doing above. I don't know what does it do exactly, but do you think this is something that the image_processing gem should do automatically?

mokolabs commented 6 years ago

Yeah, I think that would be nice.

In general, I've always sharpened resized images -- either via ImageMagick or even manually in Photoshop using unsharp mask.

The sharpen settings for libvips were originally designed for print, though, so they have a different nomenclature than what you might see in typically screen applications.

This is what I'm using right now, but it's not perfect:

.sharpen(sigma: 1.5, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8)

But it looks like carrier wave is using a sharpen mask. Maybe that would yield better results?

https://github.com/eltiare/carrierwave-vips/blob/807c7e544aea3c44a061b7caf37fc8c330139e09/lib/carrierwave/vips.rb

janko commented 6 years ago

@mokolabs If you'll have time, do send a PR for this.

Ideally both MiniMagick and Vips module should share the same mask, though I'm not sure if the definitions map one-to-one. I was thinking we could add a new :sharpen option to the #resize_* methods, which would enable overriding the default sharpen mask, or skipping sharpening altogether if false is passed in.

janko commented 6 years ago

I'll close this ticket, as the original question has been addressed.

mokolabs commented 6 years ago

@janko-m Yeah, a :sharpen option sounds nice.

I'll see if I can get a carrier-wave-style sharpen mask working first.

mokolabs commented 6 years ago

One tweak to my earlier suggestion: we should only enable sharpening for JPEGs by default. :)

Sharpening PNGs and GIFs may dramatically increase file sizes -- since these are lossless formats sharpening adds more information to images and that means bigger files.

mokolabs commented 6 years ago

Okay, here's a working version of a carrier-wave-style sharpen mask:

mask = Vips::Image.new_from_array [
       [-1, -1, -1],
       [-1, 24, -1],
       [-1, -1, -1]], 16

ImageProcessing::Vips
  .source(image)
  .resize_to_fit(1024, 1024)
  .saver(Q: quality, interlace: true)
  .conv(mask)
  .call

I don't really understanding the image mask values, but these settings produce a nice, subtle sharpening effect on resized images.

Here's a Wikipedia page on how these masks work in the abstract: https://en.wikipedia.org/wiki/Kernel_(image_processing)