blob: b529dee76a52c8ea4ed299d7bd3905210c9118ce [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
<document>
<header>
<title>Image Loader Framework</title>
</header>
<body>
<section id="overview">
<title>Overview</title>
<p>
Apache XML Graphics Commons contains a unified framework for loading and
processing images (bitmap and vector). The package name is
<code>org.apache.xmlgraphics.image.loader</code>. Key features:
</p>
<ul>
<li>
Unified basic API for all supported image types.
</li>
<li>
Image "Preloading": It allows automatic detection on the image
content type and can extract the intrinsic size (in pixels and length
units) of the image without loading the whole image into memory in
most cases. <a href="ext:xmlgraphics.apache.org/fop">Apache FOP</a>
uses this as it only needs the size of the image to do the layout.
The image is only fully read at the rendering stage.
</li>
<li>
Image conversion facility: Images can be converted into different
representations depending on the needs of the consumer.
</li>
<li>
Supported formats: All bitmap formats for which there are codecs
for the ImageIO API (like JPEG, PNG, GIF etc.), EPS, EMF. These
formats are bundled. Other formats such as SVG and WMF are available
through plug-ins hosted elsewhere.
</li>
<li>
Supported in-memory representations:
<ul>
<li>RenderedImage/BufferedImage</li>
<li>raw/undecoded (JPEG, EPS, CCITT group 3/4)</li>
<li>Java2D (Images painted through Graphics2D)</li>
<li>XML DOM (for SVG, MathML etc.)</li>
<li>Additional representations can be added as necessary.</li>
</ul>
</li>
<li>
Custom image loaders and converters can be dynamically plugged in.
Automatic plug-in detection through the application classpath.
</li>
<li>
An image cache speeds up the processing for images that are requested
multiple times.
</li>
</ul>
</section>
<section id="tutorial">
<title>Tutorial</title>
<section id="manager-setup">
<title>Setting up the manager</title>
<p>
Before we can start to work with the package we need to set up the
<code>ImageManager</code>. It provides convenience methods to load
and convert images and holds the image cache.
</p>
<p>
The <code>ImageManager</code> needs an <code>ImageContext</code>.
This interface provides the <code>ImageManager</code> with important
context and configuration data. Currently this is only the source
resolution. The <code>ImageManager</code> and
<code>ImageContext</code> are intended to be shared within an
application.
</p>
<source><![CDATA[
import org.apache.xmlgraphics.image.loader.ImageContext;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.impl.DefaultImageContext;
[..]
ImageManager imageManager = new ImageManager(new DefaultImageContext());
]]></source>
<note>
In this example, <code>DefaultImageContext</code> is used. You may
need to write your own implementation of <code>ImageContext</code> for
your use case.
</note>
</section>
<section id="preloading">
<title>Preloading an image</title>
<p>
In order to load an image, it needs to be "preloaded" first, i.e. the image content
type is detected and the intrinsic size of the image is determined. The result of this
process is an <code>ImageInfo</code> instance which contains the URI, MIME type and
intrinsic size. In most cases, this is done without loading the whole image (see
SPI section below for information on exceptions to this rule).
</p>
<p>
Preloading is normally done through the <code>ImageManager</code>'s
<code>getImageInfo()</code> method. For this operation
an <code>ImageSessionContext</code> needs to be provided. It is responsible for
supplying JAXP <code>Source</code> objects, URI resolution and providing other
information needed for the image operations. In simple cases you can simply use
<code>DefaultImageSessionContext</code>, but often you will want to write your own
implementation of <code>ImageSessionContext</code>. In that case, it's recommended to
subclass <code>AbstractImageSessionContext</code> which lets you avoid rewriting a
lot of code for providing <code>Source</code> objects.
</p>
<p>
</p>
<source><![CDATA[
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageSessionContext;
import org.apache.xmlgraphics.image.loader.impl.DefaultImageSessionContext;
[..]
ImageSessionContext sessionContext = new DefaultImageSessionContext(
imageManager.getImageContext(), null);
ImageInfo info = imageManager.getImageInfo(uri, sessionContext);
]]></source>
</section>
<section id="loading">
<title>Loading an image</title>
<p>
Once the image is "preloaded", it can be fully loaded in the form/flavor that is
needed by the consuming application. The required flavor is indicated through the
<code>ImageFlavor</code> class. If you want the image as a bitmap image in memory,
you could request an <code>ImageFlavor.RENDERED_IMAGE</code>. Again, the
<code>ImageSessionContext</code> will be needed.
</p>
<source><![CDATA[
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
[..]
Image img = this.imageManager.getImage(
info, ImageFlavor.RENDERED_IMAGE, sessionContext);
ImageRendered imageRend = (ImageRendered)img;
RenderedImage ri = imageRend.getRenderedImage();
//...and do anything with the RenderedImage
]]></source>
<p>
In this example above, we simply acquire the image as a RenderedImage instance.
If the original image was a vector graphic image (SVG, WMF etc.), it's automatically
converted to a bitmap image. Note: The resolution of the created image is controlled
by the target resolution returned by the <code>ImageSessionContext</code>.
</p>
<p>
Of course, the framework can only provide images in the formats, it has image loaders
or image converters for. An example: It is possible to load EPS images, but they
can only be provided in raw form. In order to provide it as a bitmap image, a PostScript
interpreter would be needed to interpret the PostScript code. This interpreter would
be integrated using an <code>ImageConverter</code> implementation (see SPI section below).
If the requested form of the image cannot be provided you will get an
<code>ImageException</code> on which you'll have to react as needed.
</p>
<p>
In <a href="ext:xmlgraphics.apache.org/fop">Apache FOP</a>, each renderer supports
a different set of image flavors that can be embedded in the target format. For example:
The PDF renderer can deal with Java2D image, bitmaps, XML, native JPEG and CCITT images.
The PCL renderer, however, can only consume bitmap images. So, if you can accept
more than one flavor, the package allows you to specify all of them in an ordered list
(the first in the list is the preferred format). The package will then try to return
the best representation possible. Here's a code example:
</p>
<source><![CDATA[
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
[..]
final ImageFlavor[] flavors = new ImageFlavor[]
{ImageFlavor.GRAPHICS2D,
ImageFlavor.BUFFERED_IMAGE,
ImageFlavor.RENDERED_IMAGE};
Image img = manager.getImage(
info, flavors, sessionContext);
if (img instanceof ImageGraphics2D) {
//handle Java2D/Graphics2D image
} else if (img instanceof ImageRendered) {
//handle BufferedImage and RenderedImage
//(BufferedImage is a subclass of RenderedImage)
} else {
throw new IllegalStateException("Unexpected flavor");
}
]]></source>
<note>
While each <code>BufferedImage</code> is also a <code>RenderedImage</code>,
it can be more efficient to also specify <code>ImageFlavor.BUFFERED_IMAGE</code> in
the flavor array.
</note>
</section>
</section>
<section id="tipsntricks">
<title>Tips &amp; Tricks</title>
<p>
If you are loading bitmap images and you get an error like
<code>"Cannot load image (no suitable loader/converter combination available) for
myimage.tif (image/tiff)</code>,
you maybe be missing the necessary ImageIO codec to decode the image. A number of
well-written codecs can be found in
<a href="https://jai-imageio.dev.java.net/">JAI Image I/O Tools Project</a>. Just download
the distribution and add the JAR to the classpath. ImageIO will automatically pick up
the new codecs and they will subsequently be available to the image framework.
</p>
</section>
<section id="spi">
<title>Service Provider Interface (SPI, Plug-ins)</title>
<p>
The whole image framework is designed to be highly extensible. There are various
extension points where new functionality can be added. The three main SPI interfaces are:
</p>
<ul>
<li><code>ImagePreloader</code>: detects the content type and preloads an image</li>
<li><code>ImageLoader</code> and <code>ImageLoaderFactory</code>: loads images</li>
<li><code>ImageConverter</code>: converts images from one representation into another</li>
</ul>
<p>
If you plan to write an implementation of one of the above interfaces, please also take
a look at the existing implementations for reference.
</p>
<p>
Throughout the SPI, you'll find a <code>Map</code> parameter (hints) in the most important
methods. That's a way to supply additional information to the implementation by the
caller. For example, the source and target resolutions from the image (session) context
is stored in the hints. The implementation should not rely on the presence of specialized
information and should always have sensible defaults to rely on in this case.
</p>
<section id="ImagePreloader">
<title>ImagePreloader</title>
<p>
The first task is identifying whether the implementation supports the given image.
If the image is loaded using an ImageInputStream it is important to always reset the
stream position to the beginning of the file at the end of the
<code>preloadImage()</code> method, because all registered preloaders are check in turn
until one implementation signals that it supports the format. In that case, it has to
extract only the minimal information from the image necessary to identify the image's
intrinsic size. For most formats, this is doable without loading the whole image into
memory.
</p>
<p>
However, for some formats (like MathML or WMF), loading the whole image at preloading
time is hard to avoid since the image's size can only be determined that way. In such
a case, the <code>ImagePreloader</code> implementations shall pass the loaded
document to the respective <code>ImageLoader</code> through the custom objects that
can be attached to the <code>ImageInfo</code> object. If the preloader loads the whole
document, it shall close the given <code>Source</code> object (calling
<code>ImageUtil.closeQuietly(Source)</code>).
</p>
<p>
The priority the implementation reports is used to sort all registered implementations.
This is to fine-tune the inner workings and to optimize performance since some formats
are usually used more frequently than others.
</p>
<note>
Normally, if you implement an <code>ImagePreloader</code> you will also need to implement
the respective <code>ImageLoader/ImageLoaderFactory</code>, or vice versa.
</note>
</section>
<section id="ImageLoader">
<title>ImageLoader and ImageLoaderFactory</title>
<p>
The factory interface has been created to allow checking if some library that an
implementation depends on is really in the classpath so it can report back that the
<code>ImageLoader</code> is not funtional. The factory also reports what kind of
image formats it supports and which image flavors it can return. There can be a
complex relationship between the two. It is recommmended, however, to write smaller
implementations rather than big, almighty ones.
</p>
<p>
The usage penalty is used when constructing image conversion pipelines. There can be
multiple ways to provide an image in one of the supported flavors and this value helps
to make the best decision.
</p>
<p>
While the factory basically just provides information and creates new
<code>ImageLoader</code> instances, the image loaders are doing the actual leg work
of decoding the images. The image flavor returned by the loader must match the
flavor that is returned by <code>getTargetFlavor()</code>.
</p>
</section>
<section id="ImageConverter">
<title>ImageConverter</title>
<p>
The image converter is responsible to transform one image representation into another.
Bundled implementations support these conversions: Java2D to bitmap, bitmap to Java2D
and RenderedImage to "raw" PNG. Ideas for additional image converters could be:
PDF to Java2D, EPS to Java2D or MathML to SVG or Java2D.
</p>
<p>
Each ImageConverter comes with a usage penalty which is used when constructing
conversion pipelines so the pipeline with the least penalty value can be chosen. This
is necessary as the consuming application my support multiple image flavors and there
can be multiple ways to convert an image in one of the requested image flavors.
Internally, <a href="http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Dijkstra's
shortest path algorithm</a> is used to find the best path using the penalties as "way
lengths".
</p>
</section>
</section>
</body>
</document>