Scrimage
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.+"
libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-io-extra" % "2.+"
libraryDependencies += "com.sksamuel.scrimage" %% "scrimage-filters" % "2.+"
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.
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.