# MiniMagick
[![Gem Version](https://img.shields.io/gem/v/mini_magick.svg)](http://rubygems.org/gems/mini_magick)
[![Gem Downloads](https://img.shields.io/gem/dt/mini_magick.svg)](http://rubygems.org/gems/mini_magick)
[![CI](https://github.com/minimagick/minimagick/actions/workflows/ci.yml/badge.svg)](https://github.com/minimagick/minimagick/actions/workflows/ci.yml)
[![Code Climate](https://codeclimate.com/github/minimagick/minimagick/badges/gpa.svg)](https://codeclimate.com/github/minimagick/minimagick)

A ruby wrapper for [ImageMagick](http://imagemagick.org/) command line.

## Why?

I was using [RMagick](https://github.com/rmagick/rmagick) and loving it, but it
was eating up huge amounts of memory. Even a simple script would use over 100MB
of RAM. On my local machine this wasn't a problem, but on my hosting server the
ruby apps would crash because of their 100MB memory limit.

## Solution!

Using MiniMagick the ruby processes memory remains small (it spawns
ImageMagick's command line program mogrify which takes up some memory as well,
but is much smaller compared to RMagick). See [Thinking of switching from
RMagick?](#thinking-of-switching-from-rmagick) below.

MiniMagick gives you access to all the command line options ImageMagick has
(found [here](http://www.imagemagick.org/script/command-line-options.php)).

## Requirements

ImageMagick command-line tool has to be installed. You can check if you have it
installed by running

```sh
$ magick -version
Version: ImageMagick 7.1.1-33 Q16-HDRI aarch64 22263 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP(5.0)
Delegates (built-in): bzlib fontconfig freetype gslib heic jng jp2 jpeg jxl lcms lqr ltdl lzma openexr png ps raw tiff webp xml zlib zstd
Compiler: gcc (4.2)
```

## Installation

Add the gem to your Gemfile:

```sh
$ bundle add mini_magick
```

## Information

* [API documentation](https://rubydoc.info/gems/mini_magick)

## Usage

Let's first see a basic example of resizing an image.

```rb
require "mini_magick"

image = MiniMagick::Image.open("input.jpg")
image.path #=> "/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/magick20140921-75881-1yho3zc.jpg"
image.resize "100x100"
image.format "png"
image.write "output.png"
```

`MiniMagick::Image.open` makes a copy of the image, and further methods modify
that copy (the original stays untouched). We then
[resize](http://www.imagemagick.org/script/command-line-options.php#resize)
the image, and write it to a file. The writing part is necessary because
the copy is just temporary, it gets garbage collected when we lose reference
to the image.

`MiniMagick::Image.open` also accepts URLs, and options passed in will be
forwarded to [open-uri](https://github.com/ruby/open-uri).

```rb
image = MiniMagick::Image.open("http://example.com/image.jpg")
image.contrast
image.write("from_internets.jpg")
```

On the other hand, if we want the original image to actually *get* modified,
we can use `MiniMagick::Image.new`.

```rb
image = MiniMagick::Image.new("input.jpg")
image.path #=> "input.jpg"
image.resize "100x100"
# Not calling #write, because it's not a copy
```

### Combine options

While using methods like `#resize` directly is convenient, if we use more
methods in this way, it quickly becomes inefficient, because it calls the
command on each methods call. `MiniMagick::Image#combine_options` takes
multiple options and from them builds one single command.

```rb
image.combine_options do |b|
  b.resize "250x200>"
  b.rotate "-90"
  b.flip
end # the command gets executed
```

As a handy shortcut, `MiniMagick::Image.new` also accepts an optional block
which is used to `combine_options`.

```rb
image = MiniMagick::Image.new("input.jpg") do |b|
  b.resize "250x200>"
  b.rotate "-90"
  b.flip
end # the command gets executed
```

The yielded builder is an instance of `MiniMagick::Tool`. To learn more
about its interface, see [Tools](#tools) below.

### Attributes

A `MiniMagick::Image` has various handy attributes.

```rb
image.type        #=> "JPEG"
image.width       #=> 250
image.height      #=> 300
image.dimensions  #=> [250, 300]
image.size        #=> 3451 (in bytes)
image.colorspace  #=> "DirectClass sRGB"
image.exif        #=> {"DateTimeOriginal" => "2013:09:04 08:03:39", ...}
image.resolution  #=> [75, 75]
image.signature   #=> "60a7848c4ca6e36b8e2c5dea632ecdc29e9637791d2c59ebf7a54c0c6a74ef7e"
```

If you need more control, you can also access [raw image
attributes](http://www.imagemagick.org/script/escape.php):

```rb
image["%[gamma]"] # "0.9"
```

To get the all information about the image, MiniMagick gives you a handy method
which returns the output from `magick input.jpg json:`:

```rb
image.data #=>
# {
#   "format": "JPEG",
#   "mimeType": "image/jpeg",
#   "class": "DirectClass",
#   "geometry": {
#     "width": 200,
#     "height": 276,
#     "x": 0,
#     "y": 0
#   },
#   "resolution": {
#     "x": "300",
#     "y": "300"
#   },
#   "colorspace": "sRGB",
#   "channelDepth": {
#     "red": 8,
#     "green": 8,
#     "blue": 8
#   },
#   "quality": 92,
#   "properties": {
#     "date:create": "2016-07-11T19:17:53+08:00",
#     "date:modify": "2016-07-11T19:17:53+08:00",
#     "exif:ColorSpace": "1",
#     "exif:ExifImageLength": "276",
#     "exif:ExifImageWidth": "200",
#     "exif:ExifOffset": "90",
#     "exif:Orientation": "1",
#     "exif:ResolutionUnit": "2",
#     "exif:XResolution": "300/1",
#     "exif:YResolution": "300/1",
#     "icc:copyright": "Copyright (c) 1998 Hewlett-Packard Company",
#     "icc:description": "sRGB IEC61966-2.1",
#     "icc:manufacturer": "IEC http://www.iec.ch",
#     "icc:model": "IEC 61966-2.1 Default RGB colour space - sRGB",
#     "jpeg:colorspace": "2",
#     "jpeg:sampling-factor": "1x1,1x1,1x1",
#     "signature": "1b2336f023e5be4a9f357848df9803527afacd4987ecc18c4295a272403e52c1"
#   },
#   ...
# }
```

### Pixels

With MiniMagick you can retrieve a matrix of image pixels, where each member of
the matrix is a 3-element array of numbers between 0-255, one for each range of
the RGB color channels.

```rb
image = MiniMagick::Image.open("image.jpg")
pixels = image.get_pixels
pixels[3][2][1] # the green channel value from the 4th-row, 3rd-column pixel
```

It can also be called after applying transformations:

```rb
image = MiniMagick::Image.open("image.jpg")
image.crop "20x30+10+5"
image.colorspace "Gray"
pixels = image.get_pixels
```

### Pixels To Image

Sometimes when you have pixels and want to create image from pixels, you can do this to form an image:

```rb
image = MiniMagick::Image.open('/Users/rabin/input.jpg')
pixels = image.get_pixels
depth = 8
dimension = [image.width, image.height]
map = 'rgb'
image = MiniMagick::Image.get_image_from_pixels(pixels, dimension, map, depth ,'jpg')
image.write('/Users/rabin/output.jpg')
```

In this example, the returned pixels should now have equal R, G, and B values.

### Configuration

Here are the available configuration options with their default values:

```rb
MiniMagick.configure do |config|
  config.timeout = nil # number of seconds IM commands may take
  config.errors = true # raise errors non nonzero exit status
  config.warnings = true # forward warnings to standard error
  config.tmdir = Dir.tmpdir # alternative directory for tempfiles
  config.logger = Logger.new($stdout) # where to log IM commands
  config.cli_prefix = nil # add prefix to all IM commands
  config.cli_env = {} # environment variables to set for IM commands
  config.restricted_env = false # when true, block IM commands from accessing system environment variables other than those in cli_env
end
```

For a more information, see
[Configuration](https://rubydoc.info/gems/mini_magick/MiniMagick/Configuration) API documentation.

### Composite

MiniMagick also allows you to
[composite](http://www.imagemagick.org/script/composite.php) images:

```rb
first_image  = MiniMagick::Image.new("first.jpg")
second_image = MiniMagick::Image.new("second.jpg")
result = first_image.composite(second_image) do |c|
  c.compose "Over"    # OverCompositeOp
  c.geometry "+20+20" # copy second_image onto first_image from (20, 20)
end
result.write "output.jpg"
```

### Layers/Frames/Pages

For multilayered images you can access its layers.

```rb
gif.frames #=> [...]
pdf.pages  #=> [...]
psd.layers #=> [...]

gif.frames.each_with_index do |frame, idx|
  frame.write("frame#{idx}.jpg")
end
```

### Image validation

You can test whether an image is valid by running it through `identify`:

```rb
image.valid?
image.validate! # raises MiniMagick::Invalid if image is invalid
```

### Logging

You can choose to log MiniMagick commands and their execution times:

```rb
MiniMagick.logger.level = Logger::DEBUG
```
```
D, [2016-03-19T07:31:36.755338 #87191] DEBUG -- : [0.01s] identify /var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/mini_magick20160319-87191-1ve31n1.jpg
```

In Rails you'll probably want to set `MiniMagick.logger = Rails.logger`.

## Tools

If you prefer not to use the `MiniMagick::Image` abstraction, you can use ImageMagick's command-line tools directly:

```rb
MiniMagick.convert do |convert|
  convert << "input.jpg"
  convert.resize("100x100")
  convert.negate
  convert << "output.jpg"
end #=> `magick input.jpg -resize 100x100 -negate output.jpg`

# OR

convert = MiniMagick.convert
convert << "input.jpg"
convert.resize("100x100")
convert.negate
convert << "output.jpg"
convert.call #=> `magick input.jpg -resize 100x100 -negate output.jpg`
```

This way of using MiniMagick is highly recommended if you want to maximize performance of your image processing. There are class methods for each CLI tool: `animate`, `compare`, `composite`, `conjure`, `convert`, `display`, `identify`, `import`, `mogrify` and `stream`. The `MiniMagick.convert` method will use `magick` on ImageMagick 7 and `convert` on ImageMagick 6.

### Appending

The most basic way of building a command is appending strings:

```rb
MiniMagick.convert do |convert|
  convert << "input.jpg"
  convert.merge! ["-resize", "500x500", "-negate"]
  convert << "output.jpg"
end
```

Note that it is important that every command you would pass to the command line
has to be separated with `<<`, e.g.:

```rb
# GOOD
convert << "-resize" << "500x500"

# BAD
convert << "-resize 500x500"
```

Shell escaping is also handled for you. If an option has a value that has
spaces inside it, just pass it as a regular string.

```rb
convert << "-distort"
convert << "Perspective"
convert << "0,0,0,0 0,45,0,45 69,0,60,10 69,45,60,35"
```
```
magick -distort Perspective '0,0,0,0 0,45,0,45 69,0,60,10 69,45,60,35'
```

### Methods

Instead of passing in options directly, you can use Ruby methods:

```rb
convert.resize("500x500")
convert.rotate(90)
convert.distort("Perspective", "0,0,0,0 0,45,0,45 69,0,60,10 69,45,60,35")
```

### Chaining

Every method call returns `self`, so you can chain them to create logical groups.

```rb
MiniMagick.convert do |convert|
  convert << "input.jpg"
  convert.clone(0).background('gray').shadow('80x5+5+5')
  convert.negate
  convert << "output.jpg"
end
```

### "Plus" options

```rb
MiniMagick.convert do |convert|
  convert << "input.jpg"
  convert.repage.+
  convert.distort.+("Perspective", "more args")
end
```
```
magick input.jpg +repage +distort Perspective 'more args'
```

### Stacks

```rb
MiniMagick.convert do |convert|
  convert << "wand.gif"

  convert.stack do |stack|
    stack << "wand.gif"
    stack.rotate(30)
    stack.foo("bar", "baz")
  end
  # or
  convert.stack("wand.gif", { rotate: 30, foo: ["bar", "baz"] })

  convert << "images.gif"
end
```
```
magick wand.gif \( wand.gif -rotate 90 -foo bar baz \) images.gif
```

### STDIN and STDOUT

If you want to pass something to standard input, you can pass the `:stdin`
option to `#call`:

```rb
identify = MiniMagick.identify
identify.stdin # alias for "-"
identify.call(stdin: image_content)
```

MiniMagick also has `#stdout` alias for "-" for outputting file contents to
standard output:

```rb
content = MiniMagick.convert do |convert|
  convert << "input.jpg"
  convert.auto_orient
  convert.stdout # alias for "-"
end
```

### Capturing STDERR

Some MiniMagick tools such as `compare` output the result of the command on
standard error, even if the command succeeded. The result of
`MiniMagick::Tool#call` is always the standard output, but if you pass it a
block, it will yield the stdout, stderr and exit status of the command:

```rb
compare = MiniMagick.compare
# build the command
compare.call do |stdout, stderr, status|
  # ...
end
```

## Configuring

### GraphicsMagick

As of MiniMagick 5+, [GraphicsMagick](http://www.graphicsmagick.org/) isn't
officially supported. This means its installation won't be auto-detected, and no
attempts will be made to handle differences in GraphicsMagick API or output.

However, you can still configure MiniMagick to use it:

```rb
MiniMagick.configure do |config|
  config.graphicsmagick = true
end
```

Some MiniMagick features won't be supported, such as global timeout,
`MiniMagick::Image#data` and `MiniMagick::Image#exif`.

### Limiting resources

ImageMagick supports a number of [environment variables] for controlling its
resource limits. For example, you can enforce memory or execution time limits by
setting the following:

```rb
MiniMagick.configure do |config|
  config.cli_env = {
    "MAGICK_MEMORY_LIMIT" => "128MiB",
    "MAGICK_MAP_LIMIT" => "64MiB",
    "MAGICK_TIME_LIMIT" => "30"
  }
end
```

For time limit you can also use the `timeout` configuration:

```rb
MiniMagick.configure do |config|
  config.timeout = 30 # 30 seconds
end
```

### Changing temporary directory

ImageMagick allows you to change the temporary directory to process the image file:

```rb
MiniMagick.configure do |config|
  config.tmpdir = File.join(Dir.tmpdir, "/my/new/tmp_dir")
end
```

The example directory `/my/new/tmp_dir` must exist and must be writable.

If not configured, it will default to `Dir.tmpdir`.

### Ignoring STDERR

If you're receiving warnings from ImageMagick that you don't care about, you
can avoid them being forwarded to standard error:

```rb
MiniMagick.configure do |config|
  config.warnings = false
end
```

### Avoiding raising errors

This gem raises an error when ImageMagick returns a nonzero exit code.
Sometimes, however, ImageMagick returns nonzero exit codes when the command
actually went ok. In these cases, to avoid raising errors, you can add the
following configuration:

```rb
MiniMagick.configure do |config|
  config.errors = false
end
```

You can also pass `errors: false` to individual commands:

```rb
MiniMagick.identify(errors: false) do |b|
  b.help
end
```

## Thinking of switching from RMagick?

Unlike RMagick, MiniMagick is a much thinner wrapper around ImageMagick.

* To piece together MiniMagick commands refer to the [Mogrify
  Documentation](https://imagemagick.org/script/mogrify.php). For instance
  you can use the `-flop` option as `image.flop`.
* Operations on a MiniMagick image tend to happen in-place as `image.trim`,
  whereas RMagick has both copying and in-place methods like `image.trim` and
  `image.trim!`.
* To open files with MiniMagick you use `MiniMagick::Image.open` as you would
  `Magick::Image.read`. To open a file and directly edit it, use
  `MiniMagick::Image.new`.

[environment variables]: https://imagemagick.org/script/resources.php#environment
