Giter Club home page Giter Club logo

image_processing's Introduction

ImageProcessing

Provides higher-level image processing helpers that are commonly needed when handling image uploads.

This gem can process images with either ImageMagick/GraphicsMagick or libvips libraries. ImageMagick is a good default choice, especially if you are migrating from another gem or library that uses ImageMagick. Libvips is a newer library that can process images very rapidly (often multiple times faster than ImageMagick).

Goal

The goal of this project is to have a single gem that contains all the helper methods needed to resize and process images.

Currently, existing attachment gems (like Paperclip, CarrierWave, Refile, Dragonfly, ActiveStorage, and others) implement their own custom image helper methods. But why? That's not very DRY, is it?

Let's be honest. Image processing is a dark, mysterious art. So we want to combine every great idea from all of these separate gems into a single awesome library that is constantly updated with best-practice thinking about how to resize and process images.

Installation

  1. Install ImageMagick and/or libvips:

In a Mac terminal:

  $ brew install imagemagick vips

In a debian/ubuntu terminal:

  $ sudo apt install imagemagick libvips
  1. Add the gem to your Gemfile:
gem "image_processing", "~> 1.0"

Usage

Processing is performed through ImageProcessing::Vips or ImageProcessing::MiniMagick modules. Both modules share the same chainable API for defining the processing pipeline:

require "image_processing/mini_magick"

processed = ImageProcessing::MiniMagick
  .source(file)
  .resize_to_limit(400, 400)
  .convert("png")
  .call

processed #=> #<Tempfile:/var/folders/.../image_processing20180316-18446-1j247h6.png>

This allows easy branching when generating multiple derivates:

require "image_processing/vips"

pipeline = ImageProcessing::Vips
  .source(file)
  .convert("png")

large  = pipeline.resize_to_limit!(800, 800)
medium = pipeline.resize_to_limit!(500, 500)
small  = pipeline.resize_to_limit!(300, 300)

The processing is executed on #call or when a processing method is called with a bang (!).

processed = ImageProcessing::MiniMagick
  .convert("png")
  .resize_to_limit(400, 400)
  .call(image)

# OR

processed = ImageProcessing::MiniMagick
  .source(image) # declare source image
  .convert("png")
  .resize_to_limit(400, 400)
  .call

# OR

processed = ImageProcessing::MiniMagick
  .source(image)
  .convert("png")
  .resize_to_limit!(400, 400) # bang method

You can inspect the pipeline options at any point before executing it:

pipeline = ImageProcessing::MiniMagick
  .source(image)
  .loader(page: 1)
  .convert("png")
  .resize_to_limit(400, 400)
  .strip

pipeline.options
# => {:source=>#<File:/path/to/source.jpg>,
#     :loader=>{:page=>1},
#     :saver=>{},
#     :format=>"png",
#     :operations=>[[:resize_to_limit, [400, 400]], [:strip, []]],
#     :processor_class=>ImageProcessing::MiniMagick::Processor}

The source object needs to responds to #path, or be a String, a Pathname, or a Vips::Image/MiniMagick::Tool object. Note that the processed file is always saved to a new location, in-place processing is not supported.

ImageProcessing::Vips.source(File.open("source.jpg"))
ImageProcessing::Vips.source("source.jpg")
ImageProcessing::Vips.source(Pathname.new("source.jpg"))
ImageProcessing::Vips.source(Vips::Image.new_from_file("source.jpg"))

When #call is called without options, the result of processing is a Tempfile object. You can save the processing result to a specific location by passing :destination to #call, or pass save: false to retrieve the raw Vips::Image/MiniMagick::Tool object.

pipeline = ImageProcessing::Vips.source(image)

pipeline.call #=> #<Tempfile ...>
pipeline.call(save: false) #=> #<Vips::Image ...>
pipeline.call(destination: "/path/to/destination")

You can continue reading the API documentation for specific modules:

See the wiki for additional "How To" guides for common scenarios. The wiki is publicly editable, so you're encouraged to add your own guides.

Instrumentation

You can register an #instrumenter block for a given pipeline, which will wrap the pipeline execution, allowing you to record performance metrics.

pipeline = ImageProcessing::Vips.instrumenter do |**options, &processing|
  options[:source]     #=> #<File:...>
  options[:loader]     #=> { fail: true }
  options[:saver]      #=> { quality: 85 }
  options[:format]     #=> "png"
  options[:operations] #=> [[:resize_to_limit, 500, 500], [:flip, [:horizontal]]]
  options[:processor]  #=> ImageProcessing::Vips::Processor

  ActiveSupport::Notifications.instrument("process.image_processing", **options) do
    processing.call # calls the pipeline
  end
end

pipeline
  .source(image)
  .loader(fail: true)
  .saver(quality: 85)
  .convert("png")
  .resize_to_limit(500, 500)
  .flip(:horizontal)
  .call # calls instrumenter

Contributing

Our test suite requires both imagemagick and libvips libraries to be installed.

In a Mac terminal:

$ brew install imagemagick vips

In a debian/ubuntu terminal:

sudo apt install imagemagick libvips

Afterwards you can run tests with

$ bundle exec rake test

Feedback

We welcome your feedback! What would you like to see added to image_processing? How can we improve this gem? Open an issue and let us know!

Credits

The ImageProcessing::MiniMagick functionality was extracted from refile-mini_magick. The chainable interface was heavily inspired by HTTP.rb.

License

MIT

image_processing's People

Contributors

ahorek avatar brendon avatar chioreandan avatar etherbob avatar flori avatar gustavocaso avatar hmtanbir avatar janko avatar kamipo avatar menelle avatar metaskills avatar mokolabs avatar ninjapanzer avatar olleolleolle avatar p8 avatar paulgoetze avatar pfilipow avatar printercu avatar tagliala avatar voxik avatar y-yagi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

image_processing's Issues

Utilize libvips' shrink-on-load when resizing

The thumbnail_image() function (or Vips::Image#thumbnail_image in ruby-vips) introduced in libvips 8.6 allows resizing an already loaded image, which initially fitted nicely into the ImageProcessing gem, because now resizing is just another processing step that can be added anywhere in the pipeline.

image = Vips::Image.new_from_file("/path/to/image")
# ...
image.thumbnail_image(width, height: height)
# ...
image.write_to_file("...")

However, after a discussion with the libvips author (libvips/libvips#1227 (comment)), I found out that thumbnail_image() is generally much slower (and in certain cases less correct) than using thumbnail() (or Vips::Image.thumbnail in ruby-vips) which resizes the image at load time.

image = Vips::Image.thumbnail("/path/to/image", width, height: height) # this is much faster
# ...
image.write_to_file("...")

I would like to find a way to support resizing on load with Vips::Image.thumbnail in ImageProcessing. My idea was that the existing #resize_* macros would work in that mode as well. I don't know what would be the best way to support this.

cc @mokolabs @hmistry @jrochkind

Should we rename the primary module names for 1.0?

So, before we finalize 1.0, I was thinking...

Should we rename the primary module names?

Right now, we have:
ImageProcessing::Vips
ImageProcessing::MiniMagick

But these names are inconsistent.

ImageProcessing::Vips shortens libvips to Vips. Maybe it was named after the vips homebrew package? Or maybe Vips is an informal name used within the libvips community? But, even so, the official project name and branding (what little there is) use the libvips name.

ImageProcessing::MiniMagick is, of course, named after the minimagick gem. But that seems like an implementation detail. The main point of this module is to interface with imagemagick.

So, deep down, if you think about it, these two modules are really about processing images using either libvips or imagemagick.

Wouldn't these be more accurate module names?

ImageProcessing::LibVips
ImageProcessing::ImageMagick

Or perhaps simplifying and abstracting the names would be nicer?

ImageProcessing::Vips
ImageProcessing::Magick

Like I said, just a thought. When naming things, I'm a big fan of clarity and simplicity. I know it would be a pain to change it, but it still might be worth it. :)

What do you think?

Using Image Buffers: JPG Quality Lost

In my project I've decided to use buffers vs writing to disk and reading back again. However, I noticed this today that the saver for the quality is lost when I use write_to_buffer. Seems to use a default quality that is very low.

    ImageProcessing::Vips
      .source(...)
      .saver(quality: 100)
      .call(save: false)
      .write_to_buffer('.jpg')

If I change this to use tempfiles, the quality works. Any advice?

Losing image EXIF information on convert?

I'm using shrine and image_processing to manage image uploads. I'm using the versions, processing, and add_metadata plugins. I've got a custom metadata function for exif information. It works fine when I don't convert images. But if I use convert, I seem to lose EXIF data. Is it possible that the convert method removes EXIF information? This is happening for me when I upload and convert TIFF images to JPEGs.

Code is as follows:

 add_metadata :exif do |io, context|
    if ['.jpg', '.jpeg'].include? File.extname(io.path).downcase
      begin
        Exif::Data.new(io.path)
      rescue
        nil
      end
    elsif ['.tiff', '.tif'].include? File.extname(io.path).downcase
      data = EXIFR::TIFF.new(io.path)
      data
    end
  end
  process(:store) do |io, context|
    if ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/tiff'].include? io.mime_type
      original = io.download

      if ['image/png', 'image/gif', 'image/tiff'].include? io.mime_type
        converted = convert(original, 'jpg')
      else
        converted = original
      end

      size_1200 = resize_to_limit!(converted , 1200, 1200) { |cmd| cmd.auto_orient }
      size_600 = resize_to_limit(size_1200,  600, 600)
      size_100 = resize_to_limit(size_600,  100, 100)

      {original: io, large: size_1200, medium: size_600, small: size_100}
    end
end

VIPS: strip does not work with libvips 8.9.0

Using saver(strip: true) no longer works due to a change in libvips 8.9.0:

pipeline = ImageProcessing::Vips.source(file).saver(quality: 90, interlace: true, strip: true)
versions[:s1200] = pipeline.resize_to_limit!(1200, 1200)
versions[:h600] = pipeline.resize_to_limit!(nil, 600)
versions[:h300] = pipeline.resize_to_limit!(nil, 300)

results in

VIPS-WARNING **: 16:58:48.677: can't remove metadata "orientation" on shared image
VIPS-WARNING **: 16:58:48.677: can't remove metadata "exif-ifd0-Orientation" on shared image

The change is caused by the breaking change mentioned in the 8.9.0 blog post: https://libvips.github.io/libvips/2019/12/11/What's-new-in-8.9.html

undefined method 'source' for ImageProcessing::MiniMagick:Module

This has got to be something simple, but I can't seem to figure out what I'm doing wrong.

processed = ImageProcessing::MiniMagick.source(old_presenter.photo)

results in

NoMethodError: undefined method source' for ImageProcessing::MiniMagick:Module`

Using version 0.9.0

Option to see final command

Would you be open to have an additional method on Processor to print out the final command instead of executing it with .call? Much like ActiveRecord::Relations's .to_sql.

It would make debugging a lot easier and I'm not sure if a dedicated logger would make much sense.

Unable to save image when nil is returned using #custom chain method

I just ran into an issue using the #custom method with ImageProcessing::Vips.

Basically, I've got a single instance of ImageProcessing::Vips that I'm using to process both JPEGs and PNGs. So far, it's working great overall.

But, after testing, I noticed that my PNGs are massive. It turns out that sharpening PNGs is a bad idea. Since it's a lossless format, when you sharpen you're just adding more noise and thus more pixel information -- which means bigger file sizes.

So I realized I should disable sharpening for PNGs and only use it on JPEGs.

Here's the code I'm using (which includes #custom):

ImageProcessing::Vips
  .source(image)
  .resize_to_fit(1024, 1024)
  .saver(Q: quality, interlace: true)
  .custom { |image|
    image + image.sharpen(sigma: 12.5, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8) if mime_type.match? /jpeg/}
  .call

According to the docs:

Yields the intermediary Vips::Image object. If the block return value is a Vips::Image object it will be used in further processing, otherwise if nil is returned the original Vips::Image object will be used.

In practice, though, if nil is returned -- which happens when I get this error:

WARN: NoMethodError: undefined methodwrite_to_file' for nil:NilClass`

The following alternate syntax fixed my problem:

ImageProcessing::Vips
  .source(image)
  .resize_to_fit(1024, 1024)
  .saver(Q: quality, interlace: true)
  .custom { |image|
     if mime_type.match? /jpeg/
       image.sharpen(sigma: 12.5, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8)
     else
       image
     end }
  .call

Image auto-orientation

ImageProcessing currently has a method for auto-orienting the image. However, this comment provides good argumentation why this option should be turned on by default.

I will leave this issue open for a while so that anyone can pitch their opinion about this. At the moment I think it would be a good default, with a configuration option for turning it off.

However, I would ideally like to avoid adding it to all ImageMagick calls, but somehow detect if this was already done. Even though there probably isn't any cost in adding -auto-orient.

GIFs converted to jpeg still animate

I am converting uploaded files and if you upload a GIF and call convert('jpg') the result is still animating but not properly.

My code:

open_image_pipeline(source: path) do |pipeline|
  pipline = pipeline.convert('jpg')
  pipeline = crop_image(pipeline)

  saver_options = { interlace: 'plane' }
  if degrade
    saver_options[:quality] = 60
  end

  pipeline = pipeline.saver(saver_options)
  pipline = pipeline.resize_to_limit(width, height)
end
def open_image_pipeline(source:)
  pipeline = ImageProcessing::MiniMagick.source(source)
  pipeline = yield(pipeline)
  pipeline.call
end

Are GIFs converted to jpeg supposed to animate in this case?

LoadError: Could not open library 'glib-2.0'

I'm not sure if that's a fault of image_processing gem (actually i think it's not but i have no idea where to ask this) but i'm not able to use vips lib via image_processing while it's possible to do this directly.

I've written a code dedicated to extracting first frame from animated gif to a jpg file. It looks like that:

    system 'wget https://umaar.com/assets/images/dev-tips/screenshot-capture.gif'

    ImageProcessing::Vips.source(File.open('screenshot-capture.gif'))
                         .loader(page: 1)
                         .convert('jpg')
                         .saver(background: 255, quality: 100)
                         .resize_to_limit!(100, 100)

And running this throws:

LoadError: Could not open library 'glib-2.0': Error loading shared library glib-2.0: No such file or directory.
Could not open library 'libglib-2.0.so': Error loading shared library libglib-2.0.so: No such file or directory
/prismo/vendor/bundle/ruby/2.5.0/gems/ffi-1.9.25/lib/ffi/library.rb:145:in `block in ffi_lib'
/prismo/vendor/bundle/ruby/2.5.0/gems/ffi-1.9.25/lib/ffi/library.rb:99:in `map'
/prismo/vendor/bundle/ruby/2.5.0/gems/ffi-1.9.25/lib/ffi/library.rb:99:in `ffi_lib'
/prismo/vendor/bundle/ruby/2.5.0/gems/ruby-vips-2.0.12/lib/vips.rb:28:in `<module:GLib>'
/prismo/vendor/bundle/ruby/2.5.0/gems/ruby-vips-2.0.12/lib/vips.rb:13:in `<main>'

On the other hand, i'm totally able to run any vips command in bash and it works just fine. I can't reproduce the ruby code above in pure bash command 1:1 (is there any way to tell image_processing to show me how the given methods chain converts to terminal command?). I have tested it using this dockerfile, this dockerfile and also using an official alpine vips package

I'm not sure what details could be handy in here but if there is anything i can do to help you help me, please let me know. Also, if that's nothing obvious, i can create a basic docker setup for this for you to make it easy to reproduce the issue.

Handling image profiles

@janko-m:

Also, do you think anything else needs to be done before releasing version 1.0?

On dragonfly_libvips, sharp and imgproxy I saw assigning some default CMYK and ICC image profiles. I don't really understand it, but that's one thing that I'm still wondering whether ImageProcessing should also implement.

@mokolabs:

Here's what the ImageOptim folks recommend on profiles:

Save images in the sRGB profile with gamma 2.2, but don't embed any profile in the image. That's the most compatible and most efficient solution.

Software that supports color profiles will assume that images without an embedded profile are in the sRGB profile. Software that doesn't support color profiles will use monitor's profile, which is most likely to be sRGB as well.

By default, ImageOptim removes all color profiles. It's an obvious win for file size, and a good way to get consistent colors across different browsers, operating systems and devices.

However, ImageOptim does not convert image pixels to the sRGB color space, because that's a slightly lossy operation. The color of the rare images that aren't in the sRGB color space will fade after the optimization (usually become a bit less saturated).

That seems like good advice to me.

We should assume most images will be in sRGB and just leave them alone. Developers can optionally strip profiles using the .strip method (though some may wish to keep them in place) or maybe do their own color profile conversion/injection.

For instance, in my case, I'm stripping profiles for all image versions except the largest image. The largest image keeps the original image profile.

That way, for example, if a photo was shot using the iPhone X (which can store its HDR images in the P3 colorspace) and then that photo is displayed on another device that supports the P3 colorspace (like the iMac Retina), the largest image will show the most accurate and vibrant colors.

The smaller versions won't include profiles, but it will be less important because those images will mostly be thumbnails anyway.

How to recover from 500 Error when resize_to_fit is incapable of completing?

Hello @janko and team!

I've been trying to navigate around a 500 Server Error but I'm now crying uncle and need to call in the big guns for direction :)

When an image is uploaded, I pass it through a series of a .resize_to_fit commands to generate several different sizes. Depending on the dimensions of the image being passed in, there are times when the smallest size returns the following Vips error:

Vips::Error - shrinkv: image has shrunk to nothing

I understand why this happening and I think Vips is right to issue the complaint, but my question is

how do "recover" from this error being returned? Is there a way I can run some sort of "check" prior to execution?

I've tried using the .valid_image? method, but that doesn't seem to be what I'm after (either that, or I'm not using it properly).

Here is the method I'm currently using, the offending resize_to_fit().call is the size_5 version as shown below:

def generate_thumbnails(file)
  pipeline = ImageProcessing::Vips
  size 1200 = pipeline.source(file).resize_to_fit(1200, 1200).call
  size 400 = pipeline.source(size_1200).resize_to_fit(400, 400).call
  size 200 = pipeline.source(size_400).resize_to_fit(200, 200).call
  size 125 = pipeline.source(size_200).resize_to_fit(125, 125).call 
  size 75 = pipeline.source(size_125).resize_to_fit(75, 75).call
  size 50 = pipeline.source(size_75).resize_to_fit(50, 50).call
  size 5 = pipeline.source(size_50).resize_to_fit(5, 5).call

  {  large: size_1200, 
     medium: size_400,
     thumb: size_200,
     small_thumb: size_125,
     small: size_75,
     mini: size_50,
     speck: size_5 }
end

For example, my initial thought is that if the smallest size is a no-go, then simply don't try to do it and assign the previous "size_50/mini" version instead. And then, in the view, I'll just visually shrink down the "now too large" speck version to the desired 5x5 size with the usual HTML/CSS/JS combo.

Thank you in advance for your help! Please let me know if there is any more information you need from my end!

-Blake

Using libvips < 8.6+ on Ubuntu ?

Hi,

thanks for sharing Shrine and ImageProcessing, i'm using them regularily in my applications. The added support for libvips in ImageProcessing is great, but i encountered a problem on my production server (Ubuntu 16.04).

Since i changed from MiniMagick to libvips, i get the following error on my production server :

Could not spawn process for application /home/deploy/hvmbs_app/current: The application encountered the following error: image_processing/vips requires libvips 8.6+ (RuntimeError)

vips -v
vips-8.2.2-Sat Jan 30 17:12:08 UTC 2016

Obviously my version of libvips is not sufficient for the requirements of ImageProcessing, but appearently the last stable release of libvips is 8.4+.

Is it possible to use a lower version of libvips with ImageProcessing ?

Thanks !

Converting PSD results in two files for PNG output

Freshly installed the dependencies through Homebrew, using latest version of the gem. I first noticed the issue in a Shrine uploader using versions, so I made this cut-down test script to narrow things down. In Shrine, the problem bubbles up to prevent the PNG version from being found (and thus moved and saved) during the promote phase.

require "image_processing/mini_magick"
include ImageProcessing::MiniMagick
original = File.open("/Users/waltd/Pictures/at-at-in-chicago.psd")
converted = convert(original, "png")
system "open #{File.dirname(converted.path)}"

The result is that in the folder there are two files, one suffixed with -0.png and the other with -1.png. I have tried this with a range of different PSDs, some of which I made, and some of which were client-provided. In all other formats that I have tried, the PNG file is correctly created as a single file.

At the lowest level, if I navigate to the file and use identify from ImageMagick to look at the source file, I see two different output lines for the file with the same filename and [0] and [1] after each. I've seen how Paperclip deals with this in conversions, by taking the output of identify and using the zeroth index if the return is not a single line. Is that possible here?

SVG support without extension

I have JPG, PNG, PDF and SVG files resides in uploads/ dir and I load JPG and PNG like this:

  file_path = 'uploads/jpg_file_without_extension'
  ImageProcessing::Vips
          .source(file_path)
          .resize_to_fit(size, size, crop: 'centre')
          .convert("jpg")
          .call(destination: "#{output}/#{size}.jpg")

and it works with JPG and PNG files.

However, When there is a SVG file resides in dir, I get this error:

Vips::Error: VipsForeignLoad: "uploads/f131ddc1f188" is not a known file format

When I append .svg at the end it works.

So my question is, How can I tell the gem that file is SVG? Any additional parameter exists for such scenario? (I already know which file is it in db)

Ah and one more thing, When Creating JPG from SVG, is it possible to specify background as white instead of black? I've done these in go app using vips bu can't figure it out with this gem.

False positive "Source format is multi-layer, but destination format is single-layer."

I receive ImageProcessing::Error: Source format is multi-layer, but destination format is single-layer. If you care only about the first layer, add .loader(page: 0)to your pipeline. If you want to process each layer, see https://github.com/janko/image_processing/wiki/Splitting-a-PDF-into-multiple-images or use.saver(allow_splitting: true)`.

I use carrierwave in my Ruby on Rails, but I didn't dive too deep to provide a reproducible script. But I can explain where the problem is:

Take a look here: https://github.com/janko/image_processing/blob/master/lib/image_processing/mini_magick.rb#L191

        # When a multi-layer format is being converted into a single-layer
        # format, ImageMagick will create multiple images, one for each layer.
        # We want to warn the user that this is probably not what they wanted.
        def disallow_split_layers!(destination_path)
          layers = Dir[destination_path.sub(/\.\w+$/, '-*\0')]

          if layers.any?
            layers.each { |path| File.delete(path) }
            raise Error, "Source format is multi-layer, but destination format is single-layer. If you care only about the first layer, add `.loader(page: 0)` to your pipeline. If you want to process each layer, see https://github.com/janko/image_processing/wiki/Splitting-a-PDF-into-multiple-images or use `.saver(allow_splitting: true)`."
          end
        end

Let's assume we have destination_path without an extension, such as /tmp/image_processing20200303-1-1ko9ubs.com-2020-binary-THUMBNAIL.
Then destination_path.sub(/\.\w+$/, '-*\0') replaces nothing and returns the original destination_path, so layers include itself: ["/tmp/image_processing20200303-1-1ko9ubs.com-2020-binary-THUMBNAIL"]. Layers are removed and the exception raises.

Invalid multibyte char (US-ASCII) error when running gemspec

For some reason I couldn’t run the tests after checking out this repo:

Because of the Δ‡ character in the gemspec’s author name it failed with an "invalid multibyte char (US-ASCII)" error.

Adding # encoding: utf-8 on top of the gemspec file fixes this.

I’m using C-Ruby v2.3.0. Might this be because there is no required Ruby version (>= 2.0) defined to handle utf-8 encoding by default (I never ran into this in gemspecs and I also have an ΓΆ in my name)?

Using a Tempfile source raises ImageProcessing::Error

Using a Tempfile source raises the "Source format is multi-layer, but destination format is single-layer..." exception from the #disallow_split_layers! method. Script to reproduce:

require 'image_processing/mini_magick'
require 'tempfile'

path = ARGV.first

pipeline = ImageProcessing::MiniMagick.thumbnail('200x')

tempfile = Tempfile.new(File.basename(path))

# Simulate downloading an image to a tempfile
FileUtils.cp(path, tempfile.path)

# Using the tempfile raises ImageProcessing::Error
p pipeline.call(tempfile)

# Using the tempfile path raises ImageProcessing::Error
# p pipeline.call(tempfile.path)

# Using the original path works
# p pipeline.call(path)

(Run with a path to a .jpg or .png file argument.)

The README specifies The source object needs to responds to #path so I would expect it to be usable with a tempfile source? Looking at the code I'm guessing the #disallow_split_layers! method is making an assumption about paths which breaks with the tempfile input?

Using .distort() with SRT string fails

I am trying to use the following imagemagick command through image_processing:

magick convert input.jpg \
  -set option:distort:viewport 295x195 \
  -distort SRT "1281,422 0.24 0 147,97" \
  output.jpg

I could not get it working through the rails variants in rails 6, which as far as I understood, use image_processing under the hood. So I tried it directly in image_processing first to see if and where things go south. What I tried was:

ImageProcessing::MiniMagick.source('input.jpg')
  .set('option:distort:viewport 295x195')
  .distort('SRT "1281,422 0.24 0 147,97"')
  .call

Which results in:

MiniMagick::Error: `convert input.jpg -auto-orient -set option:distort:viewport 295x195 
-distort SRT '1281,422 0.24 0 147,97' /var/folders/qt/whql0l6s29l6dcw_rwc4ds8r0000gp/T/image_processing20200403-29104-14c51ws.jpg`
failed with error:

convert: unable to open image 'SRT '1281,422 0.24 0 147,97'': No such file or directory @ error/blob.c/OpenBlob/3496.
convert: no decode delegate for this image format `24 0 147,97'' @ error/constitute.c/ReadImage/562.

from .../.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/mini_magick-4.9.5/lib/mini_magick/shell.rb:17:in `run'
from .../.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/mini_magick-4.9.5/lib/mini_magick/tool.rb:90:in `call'
from .../.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/image_processing-1.9.3/lib/image_processing/mini_magick.rb:55:in `save_image'

So I tried to check if I can reproduce the error in mini_magick with:

image = MiniMagick::Image.open('input.jpg')
  .set 'option:distort:viewport 295x195'
  .distort "SRT '1281.6433333333332,422.52808988764053 0.24 0 147.5,97.5'"
  .resize '295x195'
  .write 'result.jpg'

The command here does not fail, however the resulting output is different than the one coming from the command line, but this may be a different issue.

I'm not sure if I am doing something wrong or if this is really a bug, and if so, if this is the right place for this, but I thought I'd give it a try.

Pass an argument to all Magick invocations

Is it a way to add an argument, in my case -quiet, to all invocations ?

A user uploaded a file taken with the Samsung app, and the JPEG is considered as malformed by libjpeg, ending up with this error: mogrify: Invalid SOS parameters for sequential JPEG
It is a known bug, and graphicmagick's maintainers advise to use -quiet to ignore them (for example here: https://sourceforge.net/p/graphicsmagick/bugs/385/)

Is it a way to do this for all image_processing calls?

`auto_rotate` doesn't work as expected but it works on another language with same `libvips`.

I'm uploading an image and process it in Go app. This go app also using libvips. It auto rotates based on exif status.

However, When I switch to Ruby, autorotate stop working. My image is 90-degree rotated while in windows, i can see correctly. Also on Go-side everything is as expected.

What Am I missing here?

This is the last piece of code I tried.

Used libvips 8.8.0-rc3

      pipeline = ImageProcessing::Vips.source(file_path)
      loader = :jpg # or empty if no loader specified
      pipeline.loader(autorotate: true, autorot: false, loader: loader) unless loader.nil?
      pipeline.resize_to_fit(size, size, crop: 'centre')
              .convert('png')
              .call(destination: "img.png")

#saver should ignore unsupported options

So I'm using the saver method to set the quality level of my JPEGs in my Shrine app.

ImageProcessing::Vips
  .source(image)
  .autorot
  .resize_to_fit, 1024, 1024)
  .saver(Q: 85, interlace: true)
  .sharpen(sigma: 1.5, x1: 1.5, y2: 15, y3: 15, m1: 0.4, m2: 0.8)
  .call

The app itself is agnostic about supported image file types. You can upload a JPEG, PNG, or GIF. But when I run a PNG or GIF through my version processor in Shrine, I get this error:

Vips::Error: unable to call VipsForeignSavePngFile: unknown option Q

It would be better to discard unsupported options when #saver arguments get passed along to #vips_pngsave, etc.

That way, it would just do the right thing with each respective image format -- without any errors.

Mogrify unrecognized option resize-to-fit

Hi @janko-m ,

First of all, thanks for this great gem! (:

Maybe it is an issue related with ActiveStorage, but I decided to post here, since you are the man of Image Processing that added the macros to ActiveStorage. Sorry if this is not the right place for it.

Error:

`mogrify -resize-to-fit [345, 345] /var/folders/jc/793vxbvx11s61czx9crv26tm0000gn/T/mini_magick20180508-30453-oz88oi.jpg` failed with error: mogrify: unrecognized option `-resize-to-fit' @ error/mogrify.c/MogrifyImageCommand/5909.

I tried to simulate on console, but there I need to call processed to get the same error:

record = School.create!(name: Time.current.to_s)
image  = File.open('spec/support/fixtures/image.jpg')

record.avatar.attach(io: image, filename: 'image.jpg')

variant = record.avatar.variant(resize_to_fit: [10, 10])

Variant result:

=> #<ActiveStorage::Variant:0x00007febf9fac070
 @blob=
  #<ActiveStorage::Blob:0x00007febf9f8bc80
   id: 22,
   key: "1esfQGcWnN1zHnNxJFNe3Fzt",
   filename: "image.jpg",
   content_type: "image/jpeg",
   metadata: {"identified"=>true},
   byte_size: 2310,
   checksum: "LZbH9nXeGPWBxV/I1nXtNw==",
   created_at: Tue, 08 May 2018 13:17:41 -03 -03:00>,
 @variation=#<ActiveStorage::Variation:0x00007febf9fac048 @transformations={:resize_to_fit=>[10, 10]}>>

Now when a call processed, that I think is the same called inside my aplication with image_tag:

[103] pry(main)> variant.processed
  Disk Storage (0.2ms) Checked if file exists at key: variants/1esfQGcWnN1zHnNxJFNe3Fzt/f237c4e4085eba8ff922ab8f00a2c1c658d211aae7b02322a2c642be463731bc (no)
MiniMagick::Error: `mogrify -resize-to-fit [10, 10] /var/folders/jc/793vxbvx11s61czx9crv26tm0000gn/T/mini_magick20180508-30841-1yac7pq.jpg` failed with error:
mogrify: unrecognized option `-resize-to-fit' @ error/mogrify.c/MogrifyImageCommand/5909.
from /Users/wbotelhos/.rvm/gems/ruby-2.5.1@app/gems/mini_magick-4.8.0/lib/mini_magick/shell.rb:17:in `run

Could you, please, help me?
Thank you! <3

Using Vips::Image Sources (Not Files) & Sharpen

I'm doing some work with Lambda to layer and compose images from S3. As such, I am avoiding reading & writing to files using .source with Vips::Images and .call(save: false) in my pipelines. I found a very strange behavior because of it. Essentially heavily pixelated layers, seen below.

Layer Final
bride-squad-layer bride-squad-final

If the layer file were saved to disk and read back in, it would be fine. The data is identical, however I did find that the format of the Vips::Image when run thru things like resize_and_pad and resize_to_fill end up having a format of float vs uchar when read back from disk.

I was able to address this and get clean results by calling .cast('uchar') and the end of my pipelines. But, I did end up finding out that the source of the change from uchar to float came from the ImageProcessing::Vips::Processor::SHARPEN_MASK and auto sharpening #22 which BTW is a great feature! Below is an example of the layer image before and after sharpening within the thumbnail method.

#<Image 900x825 uchar, 4 bands, srgb>
#<Image 900x825 float, 4 bands, srgb>

So, any advice on this? I like the sharpening feature. Rather than disabling it I'd like to maybe do a ImageProcessing::Vips::Processor.const_set(:SHARPEN_MASK, ..) or something with one that is the same but leaves my image as uchar or should I just tack on the .cast('uchar') as needed on the end of my pipelines?

Thanks in advance!

Support for true streaming

Hello all,

git master libvips has a new feature which might be relevant to the image_processing gem: true streaming. There's a short note about it here:

https://libvips.github.io/libvips/2019/11/29/True-streaming-for-libvips.html

You can now connect libvips image processing pipelines directly to any source or destination and it'll process data in a fully streaming manner. For example, you could fetch bytes from one URI (perhaps an S3 bucket), process, and write to another URI (perhaps a different bucket) in parallel and with no buffering.

We don't have any benchmarks yet, but we hope that this will produce a useful drop in latency. It should be fun to experiment with anyway. I don't think (as far as I know) anyone has done this before.

This branch of ruby-vips adds support:

https://github.com/libvips/ruby-vips/tree/add-stream

You can see examples of the proposed API here:

https://github.com/libvips/ruby-vips/blob/add-stream/spec/stream_spec.rb

Sample:

      streami = Vips::Streami.new_from_descriptor 12
      image = Vips::Image.new_from_stream streami, '', access: :sequential
      streamo = Vips::Streamo.new_to_descriptor 13 
      image.write_to_stream streamo, '.png'

So that'll stream pixels between file descriptors 12 and 13 (perhaps they are pipes connected to S3 URIs), reading any supported image format, and writing as PNG.

You can do anything in the pipeline (blur, rotate, composite, etc.). There's thumbnail_stream which can efficiently thumbnail images directly from a stream source. You can also open streams multiple times, examine the header and adjust parameters without actually processing pixels.

Questions!

  1. Does this look interesting?
  2. How does image_processing hook up to storage systems like S3 at the moment?
  3. Is the proposed API enough, or do you need to be able to make a custom stream class? That would require a little more work on ruby-vips.
  4. I've no idea if support could be added without breaking your API. Perhaps you could allow URIs where you now allow filenames?
  5. Any comments, changes or fixes would be extremely welcome, of course.

Does the vips implementation support auto orientation?

I've got a question about image auto orientation.

So, right now in my app, during the :cache process, I'm checking for a non-standard orientation using FastImage. If the image does need to be reoriented, then I do that right in the :cache process.

That way, when the upload completes to the :cache, the image is already reoriented and it appears in the correct orientation immediately after upload. At the same time, the :store process doesn't need to do any reorientation since it already happened in the :cache process (because the :cache upload is passed along to the :store process after caching is complete).

Here's how the code looks:

process(:cache) do |io, context|
  if context[:version].blank?
    if FastImage.new(io).orientation > 1
      io = ImageProcessing::MiniMagick
             .source(io)
             .auto_orient
             .call
    end
  end
end

Is there an equivalent implementation that would work with vips?

I tried a couple of obvious things like .autorot, but I'm unsure if this method -- which appears to be in libvips -- is bubbling up through ruby-vips and then image_processing correctly. Or maybe I'm just doing it wrong.

It could be nice to have a standard method call for auto orientation that works with both libraries.

API for progressive JPG and other recommended web thumb best practices

Is making sure the JPG output is a progressive JPG part of the ImageProcessing API?

It makes so much sense to make sure your thumbnails are progressive JPGs. They are often (for mathematical reasons I don't understand) smaller in file size, but more importantly they load in a way better for UX under a slow connection.

Another, although less important, thing that makes a lot of sense for web thumb JPGs, is stripping EXIF metadata, and making sure they are translated to color-profile sRGB.

These are things that all the guides for creating JPG thumbs on the web recommend (including Google's), but I find often left out or not highlighted in documentation in ruby easy-thumbnail-gen libraries.

Add more documentation for 1.0?

If there's time, I'd like to take a pass at adding a bit more documentation for the 1.0 release.

Can I just open a branch and give it a go? Anything you guys would like to see?

(I enjoy technical writing and explaining things, so I think I could improve things.)

Add crop?

Hey, great work!

Would it make sense to add a crop/crop! method in mini_magick.rb? Maybe something like this?:

def crop(image, x, y, x_offset, y_offset)
  with_minimagick(image) do |img|
    img.combine_options do |cmd|
      yield cmd if block_given?
      cmd.crop "#{x}x#{y}+#{x_offset}+#{y_offset}!"
    end
  end
end
nondestructive_alias :crop, :crop!

I just found myself implementing it when using shrine for image uploading and playing around with some image cropping JS libs. Or do you think this is too specific to have it in here?

Let me know if I can help.

Access image properties

Awesome gem! πŸ†

A method to access image data would be useful. For example image dimensions, and perhaps exif data too.

For VIPS, something along these lines should work.

def dimensions
  { :width => vips_image.x_size, :height => vips_image.y_size }
end

But may make more sense with a general image_properties method, that would return a hash with more than just width/height.

What do you think?

Rails doesn't use Vips for variant

Hello!

Thank you very much for this gem and support of vips! Seems awesome!

I think I've missed something, but can't figure out what's wrong.
I've done like it was described:

  1. added gem 'image_processing', '~> 1.2' to the Gemfile
  2. added config.active_storage.variant_processor = :vips to application.rb
  3. when I use irb:
Loading production environment (Rails 5.2.1)
irb(main):001:0> pipeline = ImageProcessing::Vips

output says:
=> ImageProcessing::Vips

And still I get errors for variants:
I, [2018-11-21T18:08:00.568681 #1242] INFO Completed 500 Internal Server Error in 334ms (ActiveRecord: 0.3ms)
F, [2018-11-21T18:08:00.569907 #1242] FATAL
F, [2018-11-21T18:08:00.569956 #1242] FATAL MiniMagick::Error (mogrify -resize-and-pad [320, 240] /tmp/mini_magick20181121-1242-1ejlv6a.jpg failed with error:
mogrify: unrecognized option -resize-and-pad' @ error/mogrify.c/MogrifyImageCommand/5525. ): F, [2018-11-21T18:08:00.569983 #1242] FATAL F, [2018-11-21T18:08:00.570019 #1242] FATAL mini_magick (4.9.2) lib/mini_magick/shell.rb:17:in mini_magick (4.9.2) lib/mini_magick/tool.rb:90:in mini_magick (4.9.2) lib/mini_magick/tool.rb:38:in mini_magick (4.9.2) lib/mini_magick/image.rb:564:in mog activestorage (5.2.1) app/models/active_storage/variation.rb:59:in block (2 levels) in transform'

Seems like it still uses minimagick. Could you please help me?

Carrierwave Configuration

Hi @janko,
I notice that carrierwave depends on image_processing. How to configure carrierwave to process images with libvips?
Thank you!

HEIC file

Hi,
Can this gem convert a HEIC image file to a JPEG image file?

Gif corruption fix

I tried to upload a gif using shrine and using resize_to_fill! made the gif corrupted as it was resized.

In order to fix this i had to make this method:

  def process(io, context)
    case context[:action]
    when :store
      {
        original: custom_resize!(io, 920, 690),
        medium:  custom_resize!(io, 800, 600),
        thumb:  custom_resize!(io, 360, 270)
       }
    end
  end

  def custom_resize!(io, width, height)
    if io.content_type == 'image/gif'
      processed = with_minimagick(io.download) do |image|
        image.coalesce
        image.combine_options do |cmd|
          cmd.resize "#{width}x#{height}^"
          cmd.gravity "Center"
          cmd.background "rgba(255,255,255,0.0)" # transparent
          cmd.extent "#{width}x#{height}"
        end
      end
      processed
    else
      resize_to_fill!(io.download, width, height)
    end
  end

Is this the only way of doing this with the plugin? Just adding a block to resize_to_fill! exposes just the cmd not the image and coalesce does not work on the cmd.

Is it possible to expose the image too in that yield?

unified api

Wouldn't it be awesome if there is just one API and it automatically chooses the best implementation depending on the task at hand?

Feel free to close if you don't like the idea

Improve libvips support for Heroku?

I'm ultimately planning to deploy my new image_processing app to Heroku.

Since libvips isn't included in the default package list on Heroku stacks, I will need to install a custom buildpack before I deploy my app.

There are quite a few libvips buildpacks on Heroku already:
https://elements.heroku.com/search/buildpacks?q=libvips

But I'm not sure which is best. Plus they all seem outdated. The most popular uses version 7.42.3, but that's over five years old. The newest buildpack uses 8.0.0, but that's almost 3 years old.

I'd like to see...

  • An updated buildpack with a current version of libvips
  • A new "How To" guide on the wiki with complete steps for installing the buildpack on Heroku

Why do this? We should make it super easy to use libvips with image_processing on Heroku. Right now, our story is "libvips support is amazing, but it's kind of a pain to deploy to Heroku". If we create a new buildpack, the story will be "libvips is amazing and it's easy to deploy to Heroku using our custom buildpack".

The buildpack, of course, should be its own Github project. I'm happy to host it, but I'm certainly not an expert on Ubuntu package management and library compilation, so I'd love some help from others who may know more about that stuff than me. (I can also help with testing and documentation).

Thoughts?

Add automatic image sharpening

Images pretty often get a little blurry when resized, so programs such as Photoshop will often apply some sharpening afterwards to make the images a little crisper. I think we should do the same here (continuing the discussion from https://github.com/janko-m/image_processing/issues/16#issuecomment-374677906).

ImageMagick

From this article it seems that for ImageMagick we should use the following:

unsharp "0.25x0.25+8+0.065"

libvips

For libvips, the vips_sharpen() documentation recommends the following defaults:

sharpen(sigma: 0.5, x1: 2, y2: 10, y3: 20, m1: 0, m2: 3)

The carrierwave-vips and vips-process gems both use a "sharpen mask" which they apply using vips_conv(). I'm copy-pasting @mokolabs' port to the newer libvips version from https://github.com/janko-m/image_processing/issues/16#issuecomment-375037420:

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

conv(mask)

@mokolabs Any ideas which ones of these two we should use? I'm leaning towards using vips_sharpen() as that's what it was designed for. But if it produces worse results than the vips_conv() approach then we might need to tweak the parameters. Hopefully the parameters should be fine if it's recommended in the documentation.

Can we still use cmd blocks with the new API?

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!

can't convert ImageProcessing::Pipeline to String (ImageProcessing::Pipeline#to_str gives ImageProcessing::Pipeline)

After updating from image_processing version 0.4.5 to 0.10.2, I'm getting this error:

can't convert ImageProcessing::Pipeline to String (ImageProcessing::Pipeline#to_str gives ImageProcessing::Pipeline)

in this code:

include ImageProcessing::MiniMagick

...

image = resize_to_fill!(image, width, height, gravity: gravity)

...

versions[:resized] = File.open(image.path, 'rb')

I'm also seeing this warning:

[IMAGE_PROCESSING DEPRECATION WARNING] This API is deprecated and will be removed in ImageProcessing 1.0. Please use the new chainable API.

I think this crash is happening in File.open(image.path, 'rb'). What would be the correct fix for this?

Active Storage cropping issue

Using Rails 6.0 and 'image_processing', '~> 1.9.3'

When trying to show a cropped variant of an image with:

url_for(user.avatar.variant(crop: crop_geometry)&.processed)
I get an error:

You must have ImageMagick or GraphicsMagick installed

I thought that image_processing gem included any dependencies relatives to this. Should I install some missing dependencies or what? Thanks

Image rotation

Nice gem! Would be even better if one could rotate images. πŸ˜„

Something like this should do it for VIPS (at least for ruby-vips 0.3.8, may be different in more recent versions).

# Note that this only handles rotations of 90, 180 and 270, 
# but that should be sufficient for most use-cases. Rotating to other numers
# get's really weird and is a very rare type of image operation.
def rotate(degrees, original_vips_image)
      normalized_degrees = degrees % 360 # normalize on 360ΒΊ (will also turn -90 into 270, which is neat)
      n = normalized_degrees / 90 # will round to integer, i.e. n will be [0..4]

      vips_image = original_vips_image
      n.times { vips_image = vips_image.rot90 }
      vips_image
end

Let me know what you think!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.