image_processing: one chainable Ruby pipeline for ImageMagick and libvips
Web-friendly image processing macros for libvips and ImageMagick
At a glance
- What is it?
- The image_processing gem gives Ruby applications a single chainable API for resizing and converting images, backed by either ImageMagick or libvips. It exists so attachment libraries stop each shipping their own private helpers, and it ships with instrumentation hooks for measuring the work.
- Who is it for?
- image_processing is a good fit for a Rails or Ruby service that already accepts uploads and currently rolls its own resize helpers, especially when you want to move from ImageMagick to libvips without rewriting call sites, since both backends expose the same chainable API.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: every attachment gem ships its own private resize helpers
Upload handling in Ruby has a recurring duplication problem, and the README names it directly. Attachment gems such as Paperclip, CarrierWave, Refile, Dragonfly, and Active Storage each implement their own custom image helper methods, which the project argues is not very DRY. The premise behind image_processing is that image processing is a dark and mysterious art, and that the sensible response is one library gathering the good ideas from all of those separate gems. The intended users are Ruby developers running web applications that accept user images: profile pictures, upload forms, e-commerce product photos. The problem the gem solves is not the act of resizing, which both ImageMagick and libvips already do well. It is the layer above: a Ruby API that stays stable while the underlying native library underneath changes. That framing explains the project's structure, its two interchangeable backends, and its emphasis on instrumentation.
One chainable API, two native backends
Processing runs through the ImageProcessing::Vips module or the ImageProcessing::MiniMagick module, and the README states plainly that both share the same chainable API for defining the processing pipeline. Each chain call adds an operation, and nothing executes until you say so. ImageMagick is described as a good default choice, especially when migrating from another gem or library that already uses ImageMagick. Libvips is described as newer and able to process images very rapidly, often multiple times faster than ImageMagick, which the README links to the libvips speed and memory notes. Choosing a backend is therefore mostly a migration decision rather than a capability decision, which is the design's central claim. The chainable interface was heavily inspired by HTTP.rb, and the MiniMagick functionality was extracted from the refile-mini_magick project. Module specific API documentation lives under doc/vips.md and doc/minimagick.md, and the project wiki carries additional how to guides that anyone can edit.
Installing the native libraries and the gems
The gem is a thin Ruby layer over native binaries, so installation has two halves. First the native library, on macOS through Homebrew:
$ brew install imagemagick # if using ImageMagick
$ brew install vips # if using libvipsand on debian or ubuntu through apt:
$ sudo apt install imagemagick # if using ImageMagick
$ sudo apt install libvips # if using libvipsThen the gems themselves go into the Gemfile:
gem "image_processing", "~> 2.0"
gem "mini_magick", "~> 5.0" # if using ImageMagick
gem "ruby-vips", "~> 2.0" # if using libvipsThe version constraints are worth reading as a pair. image_processing sits on the 2.0 series, and you pick the driver gem that matches your native library. You can install either driver or both, and the test suite wants both native libraries present regardless of which backend you ship. Note that the repository publishes no GitHub releases, so the gem version on RubyGems rather than a tagged release is what you track, and the CHANGELOG.md file in the repository root is where version level changes are recorded.
Your first pipeline, from source to tempfile
A complete pipeline reads like a sentence. You require the driver, name a source, chain operations, and call it:
require "image_processing/mini_magick"
processed = ImageProcessing::MiniMagick
.source(file)
.resize_to_limit(400, 400)
.convert("png")
.callThe result of that call, when no options are given, is a Tempfile object. The source may be a String, a Pathname, anything that responds to #path, or an already constructed Vips::Image or MiniMagick::Tool object, and the README lists all four forms:
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"))One constraint deserves emphasis because it catches people out: the processed file is always saved to a new location, and in-place processing is not supported. If your application currently mutates uploads on disk, this is the behaviour you will have to design around. You can also take control of the destination, or skip saving entirely and get the raw object back:
pipeline = ImageProcessing::Vips.source(image)
pipeline.call #=> #<Tempfile ...>
pipeline.call(save: false) #=> #<Vips::Image ...>
pipeline.call(destination: "/path/to/destination")That last form is the one to reach for when a job worker needs to write into object storage or a specific path rather than a temporary file that gets cleaned up.
Bang methods and reusable pipelines for multiple derivatives
Execution happens on #call, or when a processing method is called with a bang suffix. Both spellings produce the same output:
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 methodThe bang form is what makes branching cheap. A single pipeline can produce several derivatives from one source, which is the standard pattern for a thumbnail set:
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)Because the pipeline is a plain object, it can be inspected before anything runs. Calling pipeline.options returns the accumulated hash, including the source, loader, saver, format, the ordered operations, and the processor class:
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}That inspectability is genuinely useful in tests and in debugging a pipeline that produced the wrong derivative, and it is the practical answer to the complaint that image processing is a dark art.
Loader and saver options for pages, quality, and failure handling
Two option groups sit on either side of the operations. The loader controls how the source is read, and the saver controls how the result is written. The loader option shown in the README example is .loader(page: 1), which selects a page when the source is a multi page document. The saver carries output settings such as quality, and in the instrumentation example it is set with .saver(quality: 85). There is also a loader option that matters for robustness in a web context: .loader(fail: true), which makes the read step raise instead of continuing when it cannot parse the input. That setting appears in the instrumentation example, so it is part of the documented surface. The README does not enumerate every loader or saver key, so for the full set you need the module documentation for ImageProcessing::Vips and ImageProcessing::MiniMagick. What is documented is enough to cover the common case: page selection for multi page sources, output quality for lossy formats, and a strict mode for untrusted uploads.
Instrumentation for measuring pipeline execution
For teams that care how long image processing takes, the gem ships an instrumenter hook. You register a block for a given pipeline and it wraps the execution, letting you record performance metrics. The block receives the resolved options and the processing callable:
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
endThe README shows the instrumenter being attached to a pipeline and the call at the end triggering it. The example wires the event into ActiveSupport::Notifications, so in a Rails application this is a normal instrumentation event rather than a custom reporting system. Note the dependency this implies: the gem itself does not require Rails, but the shipped example leans on ActiveSupport, so using that exact pattern pulls ActiveSupport into scope. The hook is generic enough to plug into any metrics backend of your own, since the block simply receives the options and the processing callable.
Running the test suite and the state of the project
Contributing means running the tests, and the README is explicit about the prerequisite: the test suite requires both imagemagick and libvips libraries to be installed. Once they are present:
$ bundle exec rake testThe repository root holds a Rakefile, a Gemfile, an image_processing.gemspec, lib/, test/, and doc/, along with a CODE_OF_CONDUCT.md file. The project is not archived, and the last push was on 2026-09-29, so work on it is recent even though the repository publishes no GitHub releases and version tracking happens through the gem and its CHANGELOG. Licensing is MIT, which for a library that sits inside your request path means you can use it in a proprietary application without the copyleft obligations that a differently licensed image gem might impose, provided the notice is retained. One thing the README does not document is a supported Ruby version matrix or a deprecation schedule for the chainable API, so a team planning a long term dependency should read the CHANGELOG.md before standardising on it.
Editorial conclusion
image_processing is a good fit for a Rails or Ruby service that already accepts uploads and currently rolls its own resize helpers, especially when you want to move from ImageMagick to libvips without rewriting call sites, since both backends expose the same chainable API. It is a poor fit if you need in-place editing, since processed files are always written to a new location, and it is a poor fit for projects that want zero native dependencies, because ImageMagick or libvips must be installed on the machine. Before adopting it, pin image_processing to the 2.0 series, install both imagemagick and libvips because the test suite requires them, and run bundle exec rake test to see whether your own Ruby version and native libraries agree with the suite.
Frequently asked questions
How do I install the image_processing gem?
Install the native library first, either imagemagick or libvips, using brew or apt as shown in the README, then add gem "image_processing", "~> 2.0" to your Gemfile along with mini_magick "~> 5.0" or ruby-vips "~> 2.0" for the backend you want.
Should I use ImageMagick or libvips with image_processing?
Both backends share the same chainable API, so the choice is mostly about migration and speed. The README calls ImageMagick the good default when you are moving from another gem that already uses it, and notes that libvips is newer and can process images much faster, often multiple times faster than ImageMagick.
Can image_processing modify an image in place?
No. The README states that the processed file is always saved to a new location and that in-place processing is not supported. You can pass destination to #call to control the output path, or pass save: false to get the raw Vips::Image or MiniMagick::Tool object back.
How do I generate several thumbnail sizes from one source?
Build a pipeline once with source and convert, then call a bang method such as resize_to_limit! for each size. The README example creates large, medium and small derivatives from a single ImageProcessing::Vips pipeline.
What licence is the image_processing gem released under?
The project is released under the MIT licence, recorded in the README and in the LICENSE.txt file at the repository root. There are no GitHub releases, so version changes are tracked through the gem version and the CHANGELOG.md file.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/janko-image-processing)