Both the CAE and CoreMedia Studio support the specification and rendering of named variants of images. These variants are specified by a string which describes the transformation steps necessary to compute the variant. This feature is used extensively for rendering images, obviating the need to store image variants and renditions as distinct blobs within the CMS.
The transformation strings are stored in a map-like data structure within the image document settings. For example, an image document may contain the following variants:
Variant Name | Transformation String |
---|---|
"landscape_ratio4x3" | "crop;x=0;y=0;width=2285;height=1714" |
"landscape_ratio5x2" | "crop;x=478;y=581;width=1807;height=725" |
"portrait_ratio1x1" | "crop;x=570;y=0;width=1715;height=1714" |
Table 4.6. Example of image transformation strings
A transformation is specified by a string with a syntax conforming to the hierarchical part of URIs (see RFC 3986: URI Generic Syntax). It is basically a sequence of path segments separated by slashes ('/'), each defining a single transformation operation. Each operation is applied to the binary data step by step, from left to right. The segment path denotes the name of the operation, and optional path parameters denote operation parameters.
An operation has a name, an optional alias, and an optional set of parameters. Each parameter may have a default value associated with it. Parameters are identified by name rather than ordinal position in the argument list.
For example, a;x=1;y=2/b/c;r=q
is interpreted as the operation sequence
a(x="1",y="2"), b(), c(r="q")
.
Here is a slightly more complex example of an image transformation string:
rotate;angle=23/brightness;amount=70/box;width=121;height=121;upscale=false
Transformation operators and parameters may have shorter alias names, and parameters may have default values. Exploiting these, the example above might be rewritten as:
r;a=23/b;a=70/bo;w=121;h=121
Image Operations
Image transformations are implemented in the package
com.coremedia.transform.image and subpackages.
com.coremedia.transform.image.ImageOperations specifies a set of frequently needed image
manipulation operations. These operate on an image representation specified by the type
parameter Image. The package
com.coremedia.transform.image.java2d contains an implementation of these operations
based on the javax.imageio
package which is part of the Java runtime environment.
The following operations are currently implemented. For details, see com.coremedia.transform.image.ImageOperations.
scale(alias: s)
: Scales the image.fit (f)
: Fits the image into a rectangle.box (bo)
: Scales the image to the target size, preserving the aspect ratio. An empty area on the sides will be filled with the background color, specified in the AARRGGBB (alpha red green blue) format. The default (0) is fully transparent.crop (c)
: Uses only a specified area of the image, altering its dimensions.flip (m)
: Mirrors the image horizontally or vertically.rotate (r)
: Rotates the image around its geometrical center. A background color can be specified to fill the corners (see "box" operation).gamma (g)
: Applies gamma correction.brightness (b)
: Changes the brightness.convert
: Produces an output image in the specified format.gif, png and jpeg
: shortcuts forconvert
with the respective format.jpeg
accepts a quality parameter in the range 0.0 to 1.0, where 0.0 represents the lowest and 1.0 the highest quality.defaultJpegQuality (djq)
: Sets the JPEG compression quality to be used should the output image be a JPEG and no explicit quality parameter has been given. There is a configuration parameterdefaultJpegCompressionQuality
that allows you to specify this value if this operation is not executed.removeMetadata (rm)
: Removes any metadata that might be associated with the image, such as EXIF or IPTC information. There is a configuration parameterpreserveMetadata
that allows you to specify whether metadata should be kept if this operation is not executed.progressiveMode (p)
: Sets the threshold (image size in pixel) at which the image should be encoded in progressive (JPEG) resp. interlaced (GIF, PNG) mode for faster perceived image display. There is a configuration parameterdefaultProgressiveThreshold
that allows you to specify this value if this operation is not executed.unsharpMask (usm)
: Sharpen the image using an unsharp mask.
CMYK Images
JPEG images using the CMYK color model are converted to sRGB before further processing. The conversion utilizes a ICC color profile in order to map the CMYK colors to the sRGB color space as accurate as possible. When there is a suitable color profile embedded within the source image, that color profile is used for conversion. It his highly recommended to save a CMYK JPEG image with an embedded color profile before uploading it into the CMS.
If there is no embedded color profile, conversion falls back to a platform specific "generic" CMYK color profile. If the resulting colors are not acceptable there is the possibility to specify a custom ICC color profile for converting CMYK images w/o embedded color profile. All that is needed is to put a properties file
com/twelvemonkeys/imageio/color/icc_profiles.properties
into the classpath and define the key "GENERIC_CMYK"
with the path to your profile, e.g.
GENERIC_CMYK=/usr/share/color/icc/MyGenericCMYKProfile.icc
Writing CMYK images is not supported. Moreover, writing image metadata is not supported for images
originating from CMYK source images. Any metadata is removed before writing the image, as if
the removeMetadata (rm)
operation has been applied.
This is done in the Blueprint CAE anyway in order to generate small and compact images.
A General Blob Transformation Framework
Image transformations make use of a more general binary object transformation framework. Within this framework, it is possible to implement any transformation on blobs you may think of. Transforming images is just a special case, albeit an important one.
The transformation framework resides within the package
com.coremedia.transform
and subpackages which define the framework API and contain an implementation for image
transformations. The central interface is the
BlobTransformer
with the transformBlob
method:
public interface BlobTransformer { TransformedBlob transformBlob(Blob blob, String operations) throws IOException; boolean accepts(MimeType mimeType); }
CAE Component Beans
Within the CAE Spring application context, a bean implementing the BlobTransformer
interface is defined with the id blobTransformer
. It is capable of transforming
image blobs with the operations defined within the
ImageOperations
interface. This is done with the help of a
DispatchingBlobTransformer
bean with the id imageTransformer
. This is the place where you can add your own
image operations as described in the next section.
The blobTransformer
also caches the transformed images on disk using a
CachingBlobTransformer.
Moreover, it performs some load control so that many concurrent image transformation requests do
not blow up the heap (see
ThrottlingBlobTransformer).
Please refer to the Bean Definition Reference
for some more information.
Extending the Set of available Image Operations
The DispatchingBlobTransformer class is an implementation of the BlobTransformer interface. It consists of an InputAdapter, a list of so called processor objects, and an OutputAdapter. The InputAdapter converts the input blob into an internal representation (type parameter State) that is suitable for performing the desired transformations. The processors operate on this representation to perform their tasks. Finally, the OutputAdapter renders the internal representation back into an object implementing the Blob interface. This blob is then wrapped with a TransformedBlob which also remembers the original blob and the transformation string.
Processors are objects that perform the transformation operations. Processors implement one or more interfaces. Within these interfaces, methods providing the transformation operations are marked with the @Operation annotation.
By convention, the first parameter of methods implementing operations is the transformation state object (created by the InputAdapter). Operation methods manipulate this state object to perform their transformation task.
Any parameters of an operation are specified as additional method parameters and must be annotated with the @Param annotation. This annotation tells the DispatchingBlobTransformer about the name of the parameter (recall that operation parameters are specified by name rather than position). Furthermore, it allows you to specify a default value for the parameter, making it optional, and an alias as a shorthand name. The @Operation annotation may optionally specify an alias for the operation.
For each operation within the transformation string, a DispatchingBlobTransformer tries each of its processors in turn and invokes the first one in the list that implements the operation. This way it's easy to extend the set of operations understood by a DispatchingBlobTransformer by simply adding another processor to the list that implements some new operations. And it is also possible to override some specific operation with a custom implementation by adding a custom processor at an earlier position in the list.
Let's assume you would like to extend the set of predefined image operations with a
sharpen
operation. You would start implementing the processor interface as follows:
package com.mycompany.transform; import com.coremedia.transform.image.ImageTransformerState; import com.coremedia.transform.dispatch.Operation; import javax.imageio.IIOImage; public interface SharpenerOperations { @Operation(alias="sh") void sharpen(ImageTransformerState<IIOImage> state, @Param(name="centerWeight", alias="cw", defaultValue = "1.0") float cw, @Param(name="neighbourWeight", alias="nw", defaultValue = "0.0") float nw ); }
Then you would implement this interface using the javax.imageio
library:
package com.mycompany.transform; import com.coremedia.transform.image.ImageTransformerState; import javax.imageio.IIOImage; import java.awt.image.BufferedImage; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.util.Map; public class Sharpener implements SharpenerOperations { @Override public void sharpen(ImageTransformerState<IIOImage> state, float cw, float nw) { BufferedImage img = (BufferedImage) state.getImage().getRenderedImage(); float data[] = { nw, nw, nw, nw, cw, nw, nw, nw, nw }; Kernel kernel = new Kernel(3, 3, data); ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); img = convolve.filter(img, null); state.getImage().setRenderedImage(img); } }
The final step is to add this processor to the list of processors in the Spring configuration:
<customize:append id="imageProcessorCustomizer" bean="imageProcessors"> <list> <bean class="com.mycompany.transform.SharpenerImpl" /> </list> </customize:append>
Now you may use the sharpen operation within a transformation string:
r;a=23/g;a=2/b;a=70/sharpen;centerWeight=3.0;neighbourWeight=-0.25"/png
Exploiting operation and parameter aliases, the same transformation would read:
r;a=23/g;a=2/b;a=70/sh;cw=3.0;nw=-0.25"/png