kentongray / scrimage

Scala library for image processing

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Scrimage

Scrimage is a Scala image 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 most people need, 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 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 has a consistent, idiomatic scala, and mostly immutable API that builds on the java.awt.Image methods. I say mostly immutable because for some operations creating a copy of the underlying image would prove expensive (think an in place filter on a 8000 x 6000 image where you do not care about keeping the original in memory). For these kinds of operations Scrimage supports a MutableImage instance where operations that can be performed in place mutate the original instead of returning a copy.

Build Status Coverage Status

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
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.
empty Creates a new image but without initializing the data buffer to any specific values.
filled Creates a new image and initializes the data buffer to the given color.
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.
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.
pad Resizes the canvas by adding a number of pixels around the image 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.
trim Removes a specified amount of pixels from each edge, essentially sugar to a crop operation.

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(in).scale(0.5, Bicubic).write(out) // Png is 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(inFile).filter(BlurFilter).flipX.write(outFile, Format.Jpeg) // specified Jpeg

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

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

Enlarging the canvas of an image without scaling the image (resize method changes canvas size, scale method scales image)

val in = ... // input stream
val out = ... // output stream
Image(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(in).scale(300, 200, FastScale)

Writing out a heavily compressed Jpeg thumbnail

val in = ... // input stream
val out = ... // output stream
Image(in).fit(180,120).writer(Format.JPEG).withCompression(0.5).write(out)

Printing the sizes and ratio of the image

val in = ... // input stream
val out = ... // output stream
val image = Image(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).write(Format.PNG) // to be explicit about the output format

Coverting an input stream to a maximum compressed PNG

val in : InputStream = ... // some input stream
val out : OutputStream = ... // some output stream
val compressed = Image(in).writer(Format.PNG).withMaxCompression.write(out)

Input / Output

Scrimage supports loading and saving of images in the common web formats (currently png, jpeg, gif, tiff). In addition it extends jav'sa 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 apply 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(in)

To save a method, Scrimage provides an ImageWriter for each format it supports. An ImageWriter supports saving to a File, filepath (String), byte array, or OutputStream. The quickest way to use an ImageWriter is to call write() on an image, which will get a handle to an ImageWriter with the default configuration and use it for you. Eg,

val image = ... // some image
image.write(new File("/home/sam/spaghetti.png"))

If you want to override the configuration for a writer then you will need to get a handle to the writer itself using the writer() method which returns an ImageWriter instance. From here you can then configure it before writing. A common example would be optimising a PNG to use compression (uses a modified version of PngTastic behind the scenes). Eg,

val image = ... // some image
image.writer(Format.PNG).withCompression(9).write(new File("/home/sam/compressed_spahgetti.png"))

Note the writers are immutable and are created per image.

Async

In version 1.1.0 support for asynchronous operations was added. This is achieved using the AsyncImage class. First, get an instance of AsyncImage from an Image or other source:

val in = ... // input stream
val a = AsyncImage(in)

Then any operations that act on that image return a Future[Image] instead of a standard Image. They will operate on the scala.concurrent implicit execution context.

... given an async image
val filtered = a.filter(VintageFilter) // filtered has type Future[Image]

A more complicated example would be to load all images instead a directory, apply a grayscale filter, and then re-save them out as optimized PNGs.

val dir = new File("/home/sam/images")
dir.listFiles().foreach(file => AsyncImage(file).filter(GrayscaleFilter).onSuccess {
case image => image.writer(Format.PNG).withMaxCompression.write(file)
})

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 two dependencies. One is the core library, and one is the image filters. They are split because the image filters is a large jar, and most people just want the basic resize/scale/load/save functionality. Only include the filters dependency if you need the image filters, otherwise just the core one is needed.

The latest release is now available for Scala 2.11 as well as Scala 2.10

If using SBT then you want:

libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-core" % "1.3.21"

libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-filters" % "1.3.21"

Maven for Scala 2.10

<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-core_2.10</artifactId>
    <version>1.3.21</version>
</dependency>
<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-filters_2.10</artifactId>
    <version>1.3.21</version>
</dependency>

or for Scala 2.11

<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-core_2.11</artifactId>
    <version>1.3.21</version>
</dependency>
<dependency>
    <groupId>com.sksamuel.scrimage</groupId>
    <artifactId>scrimage-filters_2.11</artifactId>
    <version>1.3.21</version>
</dependency>

Scrimage is available for both 2.10 and 2.11 RC1 onwards. 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 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.

About

Scala library for image processing

License:Apache License 2.0