sksamuel / scrimage

Scala image processing library

Github

Scrimage

Build Status

This readme is for the 2.0.x versions. For 1.4.x please see the old README1.4.md.

Scrimage is a consistent, idiomatic, and immutable scala library for manipulating and processing of images. The aim of the this library is to provide a quick and easy way to do the kinds of image operations that are most common, such as scaling, rotating, converting between formats and applying filters. It is not intended to provide functionality that might be required by a more "serious" image processing application - such as face recognition or movement tracking.

A typical use case for this library would be creating thumbnails of images uploaded by users in a web app, or resizing a set of images to have a consistent size, or optimizing PNG uploads by users to apply maximum compression, or applying a grayscale filter in a print application.

Scrimage mostly builds on the functionality provided by java.awt.* along with selected other third party libraries.

Image Operations

These operations all operate on an existing image, returning a copy of that image. The more complicated operations have a link to more detailed documentation.

Operation Description
autocrop Removes any "excess" background, returning just the image proper
blank Creates a new image but without initializing the data buffer to any specific values.
bound Ensures that the image is no larger than specified dimensions. If the original is bigger, it will be scaled down, otherwise the original is returned.

This is useful when you want to ensure images do need exceed a certain size but you don't want to scale up if smaller.
copy Creates a new clone of this image with a new pixel buffer. Any operations on the copy do not write back to the original.
cover Resizes the canvas to the given dimensions and scales the original image so that it is the minimum size needed to cover the new dimensions without leaving any background visible.

This operation is useful if you want to generate an avatar/thumbnail style image from a larger image where having no background is more important than cropping part of the image. Think a facebook style profile thumbnail.
fill Creates a new image and initializes the data buffer to the given color.
filter Returns a new image with the given filter applied. See the filters section for examples of the filters available. Filters can be chained and are applied in sequence.
fit Resizes the canvas to the given dimensions and scales the original image so that it is the maximum possible size inside the canvas while maintaining aspect ratio.

This operation is useful if you want a group of images to all have the same canvas dimensions while maintaining the original aspect ratios. Think thumbnails on a site like amazon where they are padded with white background.
flip Flips the image either horizontally or vertically.
max Returns an image that is as large as possible to fit into the specified dimensions whilst maintaining aspect ratio and without adding padding. Note: The difference between this and fit, is that fit will pad out the canvas to the specified dimensions, whereas max will not
overlay Returns a new image which is the original image plus a specified one overlaid on top
pad Resizes the canvas by adding a number of pixels around the edges in a given color.
resize Resizes the canvas to the given dimensions. This does not scale the image but simply changes the dimensions of the canvas on which the image is sitting. Specifying a larger size will pad the image with a background color and specifying a smaller size will crop the image. This is the operation most people want when they think of crop.
rotate Rotates the image clockwise or anti-clockwise.
scale Scales the image to given dimensions. This operation will change both the canvas and the image. This is what most people think of when they want a "resize" operation.
translate Returns a new image with the original image translated (moved) the specified number of pixels
trim Removes a specified amount of pixels from each edge, essentially sugar to a crop operation.
underlay Returns a new image which is the original image overload on top of the specified image

Quick Examples

Reading an image, scaling it to 50% using the Bicubic method, and writing out as PNG

val in = ... // input stream
val out = ... // output stream
Image.fromStream(in).scale(0.5, Bicubic).output(out) // an implicit PNG writer is in scope by default

Reading an image from a java File, applying a blur filter, then flipping it on the horizontal axis, then writing out as a Jpeg

val inFile = ... // input File
val outFile = ... // output File
Image.fromFile(inFile).filter(BlurFilter).flipX.output(outFile)(JpegWriter()) // specified Jpeg

Padding an image with a 20 pixel border around the edges in red

val in = ... // input stream
val out = ... // output stream
Image.fromStream(in).pad(20, Color.Red)

Enlarging the canvas of an image without scaling the image. Note: the resize methods change the canvas size, and the scale methods are used to scale/resize the actual image. This terminology is consistent with Photoshop.

val in = ... // input stream
val out = ... // output stream
Image.fromStream(in).resize(600,400)

Scaling an image to a specific size using a fast non-smoothed scale

val in = ... // input stream
val out = ... // output stream
Image.fromStream(in).scaleTo(300, 200, FastScale)

Writing out a heavily compressed Jpeg thumbnail

implicit val writer = JpegWriter().withCompression(50)
val in = ... // input stream
val out = ... // output stream
Image.fromStream(in).fit(180,120).output(new File("image.jpeg"))

Printing the sizes and ratio of the image

val in = ... // input stream
val out = ... // output stream
val image = Image.fromStream(in)
println(s"Width: ${image.width} Height: ${image.height} Ratio: ${image.ratio}")

Converting a byte array in JPEG to a byte array in PNG

val in : Array[Byte] = ... // array of bytes in JPEG say
val out = Image(in).write // default is PNG
val out2 = Image(in).bytes) // an implicit PNG writer is in scope by default with max compression

Coverting an input stream to a PNG with no compression

implicit val writer = PngWriter.NoCompression
val in : InputStream = ... // some input stream
val out = Image.fromStream(in).stream

Input / Output

Scrimage supports loading and saving of images in the common web formats (currently png, jpeg, gif, tiff). In addition it extends javas image.io support by giving you an easy way to compress / optimize / interlace the images when saving.

To load an image simply use the Image companion methods on an input stream, file, filepath (String) or a byte array. The format does not matter as the underlying reader will determine that. Eg,

val in = ... // a handle to an input stream
val image = Image.fromInputStream(in)

To save a method, Scrimage requires an ImageWriter. You can use this implicitly or explicitly. A PngWriter is in scope by default.

val image = ... // some image
image.output(new File("/home/sam/spaghetti.png")) // use implicit writer
image.output(new File("/home/sam/spaghetti.png"))(writer) // use explicit writer

To set your own implicit writer, just define it in scope and it will override the default:

implicit val writer = PngWriter.NoCompression
val image = ... // some image
image.output(new File("/home/sam/spaghetti.png")) // use custom implicit writer instead of default

If you want to override the configuration for a writer then you can do this when you create the writer. Eg:

implicit val writer = JpegWriter().withCompression(50).withProgressive(true)
val image = ... // some image
image.output(new File("/home/sam/compressed_spahgetti.png"))

Metadata

Scrimage builds on the metadata-extractor project to provide the ability to read metadata.

This can be done in two ways. Firstly, the metadata is attached to the image if it was available when you loaded the image from the Image.fromStream, Image.fromResource, or Image.fromFile methods. Then you can call image.metadata to get a handle to the metadata object.

Secondly, the metadata can be loaded without an Image being needed, by using the methods on ImageMetadata.

Once you have the metadata object, you can invoke directories or tags to see the information.

Format Detection

If you are interested in detecting the format of an image (which you don't need to do when simply loading an image, as Scrimage will figure it out for you) then you can use the FormatDetector. The detector recognises PNG, JPEG and GIF.

FormatDetector.detect(bytes) // returns an Option[Format] with the detected format if any
FormatDetector.detect(in) // same thing from an input stream

IPhone Orientation

Apple iPhone's have this annoying "feature" where an image taken when the phone is rotated is not saved as a rotated file. Instead the image is always saved as landscape with a flag set to whether it was portrait or not. Scrimage will detect this flag, if it is present on the file, and correct the orientation for you automatically. Most image readers do this, such as web browsers, but you might have noticed some things do not, such as intellij.

Note: This will only work if you use Image.fromStream, Image.fromResource, or Image.fromFile, as otherwise the metadata will not be available.

X11 Colors

There is a full list of X11 defined colors in the X11Colorlist class. This can be imported import X11Colorlist._ and used when you want to programatically specify colours, and gives more options than the standard 20 or so that are built into java.awt.Colo.

Migration from 1.4.x to 2.0.0

The major difference in 2.0.0 is the way the outputting works. See the earlier input/output section on how to update your code to use the new writers.

Changelist:

  • Changed output methods to use typeclass approach
  • Removal of Mutableimage and replacement of AsyncImage with ParImage
  • Introduction of "Pixel" abstraction for methods that operate directly on pixels
  • Addition of metadata
  • Addition of io packag

Benchmarks

Some noddy benchmarks comparing the speed of rescaling an image. I've compared the basic getScaledInstance method in java.awt.Image with ImgScalr and Scrimage. ImgScalr delegates to awt.Graphics2D for its rendering. Scrimage adapts the methods implemented by Morten Nobel.

The code is inside src/test/scala/com/sksamuel/scrimage/ScalingBenchmark.scala.

The results are for 100 runs of a resize to a fixed width / height.

Library Fast High Quality (Method)
java.awt.Image.getScaledInstance 11006ms 17134ms (Area Averaging)
ImgScalr 57ms 5018ms (ImgScalr.Quality)
Scrimage 113ms 2730ms (Bicubic)

As you can see, ImgScalr is the fastest for a simple rescale, but Scrimage is much faster than the rest for a high quality scale.

Including Scrimage in your project

Scrimage is available on maven central. There are several dependencies. One is the scrimage-core library which is required. The others are scrimage-filters and scrimage-io-extra. They are split because the image filters is a large jar, and most people just want the basic resize/scale/load/save functionality. Thescrimage-io-extra package brings in readers/writers for less common formats such as BMP Tiff or PCX.

Note: The canvas operations are now part of the core library since 2.0.1

Scrimage is cross compiled for scala 2.12, 2.11 and 2.10.

If using SBT then you want:

libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-core" % "2.x.x"

libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-io-extra" % "2.x.x"

libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-filters" % "2.x.x"

Maven:

<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-core_2.11</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-io-extra_2.11</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-filters_2.11</artifactId>
    <version>2.1.0</version>
</dependency>

If you're using maven you'll have to adjust the artifact id with the correct scala version. SBT will do this automatically when you use %% like in the example above.

Filters

Scrimage comes with a wide array (or Iterable ;) of filters. Most of these filters I have not written myself, but rather collected from other open source imaging libraries (for compliance with licenses and / or attribution - see file headers), and either re-written them in Scala, wrapped them in Scala, fixed bugs or improved them.

Some filters have options which can be set when creating the filters. All filters are immutable. Most filters have sensible default options as default parameters.

Click on the small images to see an enlarged example.

Filter Example 1 Example 2 Example 3
blur
border
brightness
bump
chrome
color_halftone
contour
contrast
despeckle
diffuse
dither
edge
emboss
errordiffusion
gamma
gaussian
glow
grayscale
hsb
invert
lensblur
lensflare
minimum
maximum
motionblur
noise
offset
oil
pixelate
pointillize_square
posterize
prewitt
quantize
rays
ripple
roberts
rylanders
sepia
smear_circles
snow
sobels
solarize
sparkle
summer
swim
television
threshold
tritone
twirl
unsharp
vignette
vintage

Composites

Scrimage comes with the usual composites built in. This grid shows the effect of compositing palm trees over a US mailbox. The first column is the composite with a value of 0.5f, and the second column with 1f. Note, if you reverse the order of the images then the effects would be reversed.

The code required to perform a composite is simply.

val composed = image1.composite(new XYZComposite(alpha), image2)

Click on an example to see it full screen.

Composite Alpha 0.5f Alpha 1f
average
blue
color
colorburn
colordodge
diff
green
grow
hue
hard
heat
lighten
negation
luminosity
multiply
negation
normal
overlay
red
reflect
saturation
screen
subtract

License

This software is licensed under the Apache 2 license, quoted below.

Copyright 2013-2015 Stephen Samuel

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.