All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlgraphics.image.loader.ImageManager Maven / Gradle / Ivy

There is a newer version: 6.0.36
Show newest version
/*
 * 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.
 */

/* $Id: ImageManager.java 1804124 2017-08-04 14:13:54Z ssteiner $ */

package org.apache.xmlgraphics.image.loader;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import javax.xml.transform.Source;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.image.loader.cache.ImageCache;
import org.apache.xmlgraphics.image.loader.pipeline.ImageProviderPipeline;
import org.apache.xmlgraphics.image.loader.pipeline.PipelineFactory;
import org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry;
import org.apache.xmlgraphics.image.loader.spi.ImagePreloader;
import org.apache.xmlgraphics.image.loader.util.ImageUtil;
import org.apache.xmlgraphics.image.loader.util.Penalty;
import org.apache.xmlgraphics.io.XmlSourceUtil;

/**
 * ImageManager is the central starting point for image access.
 */
public class ImageManager {

    /** logger */
    protected static final Log log = LogFactory.getLog(ImageManager.class);

    /** Holds all registered interface implementations for the image package */
    private ImageImplRegistry registry;

    /** Provides session-independent information */
    private ImageContext imageContext;

    /** The image cache for this instance */
    private ImageCache cache = new ImageCache();

    private PipelineFactory pipelineFactory = new PipelineFactory(this);

    /**
     * Main constructor.
     * @param context the session-independent context information
     */
    public ImageManager(ImageContext context) {
        this(ImageImplRegistry.getDefaultInstance(), context);
    }

    /**
     * Constructor for testing purposes.
     * @param registry the implementation registry with all plug-ins
     * @param context the session-independent context information
     */
    public ImageManager(ImageImplRegistry registry, ImageContext context) {
        this.registry = registry;
        this.imageContext = context;
    }

    /**
     * Returns the ImageImplRegistry in use by the ImageManager.
     * @return the ImageImplRegistry
     */
    public ImageImplRegistry getRegistry() {
        return this.registry;
    }

    /**
     * Returns the ImageContext in use by the ImageManager.
     * @return the ImageContext
     */
    public ImageContext getImageContext() {
        return this.imageContext;
    }

    /**
     * Returns the ImageCache in use by the ImageManager.
     * @return the ImageCache
     */
    public ImageCache getCache() {
        return this.cache;
    }

    /**
     * Returns the PipelineFactory in use by the ImageManager.
     * @return the PipelineFactory
     */
    public PipelineFactory getPipelineFactory() {
        return this.pipelineFactory;
    }

    /**
     * Returns an ImageInfo object containing its intrinsic size for a given URI. The ImageInfo
     * is retrieved from an image cache if it has been requested before.
     * @param uri the URI of the image
     * @param session the session context through which to resolve the URI if the image is not in
     *                the cache
     * @return the ImageInfo object created from the image
     * @throws ImageException If no suitable ImagePreloader can be found to load the image or
     *          if an error occurred while preloading the image.
     * @throws IOException If an I/O error occurs while preloading the image
     */
    public ImageInfo getImageInfo(String uri, ImageSessionContext session)
                throws ImageException, IOException {
        if (getCache() != null) {
            return getCache().needImageInfo(uri, session, this);
        } else {
            return preloadImage(uri, session);
        }
    }

    /**
     * Preloads an image, i.e. the format of the image is identified and some basic information
     * (MIME type, intrinsic size and possibly other values) are loaded and returned as an
     * ImageInfo object. Note that the image is not fully loaded normally. Only with certain formats
     * the image is already fully loaded and references added to the ImageInfo's custom objects
     * (see {@link ImageInfo#getOriginalImage()}).
     * 

* The reason for the preloading: Apache FOP, for example, only needs the image's intrinsic * size during layout. Only when the document is rendered to the final format does FOP need * to load the full image. Like this a lot of memory can be saved. * @param uri the original URI of the image * @param session the session context through which to resolve the URI * @return the ImageInfo object created from the image * @throws ImageException If no suitable ImagePreloader can be found to load the image or * if an error occurred while preloading the image. * @throws IOException If an I/O error occurs while preloading the image */ public ImageInfo preloadImage(String uri, ImageSessionContext session) throws ImageException, IOException { Source src = session.needSource(uri); ImageInfo info = preloadImage(uri, src); session.returnSource(uri, src); return info; } /** * Preloads an image, i.e. the format of the image is identified and some basic information * (MIME type, intrinsic size and possibly other values) are loaded and returned as an * ImageInfo object. Note that the image is not fully loaded normally. Only with certain formats * the image is already fully loaded and references added to the ImageInfo's custom objects * (see {@link ImageInfo#getOriginalImage()}). *

* The reason for the preloading: Apache FOP, for example, only needs the image's intrinsic * size during layout. Only when the document is rendered to the final format does FOP need * to load the full image. Like this a lot of memory can be saved. * @param uri the original URI of the image * @param src the Source object to load the image from * @return the ImageInfo object created from the image * @throws ImageException If no suitable ImagePreloader can be found to load the image or * if an error occurred while preloading the image. * @throws IOException If an I/O error occurs while preloading the image */ public ImageInfo preloadImage(String uri, Source src) throws ImageException, IOException { Iterator iter = registry.getPreloaderIterator(); while (iter.hasNext()) { ImagePreloader preloader = (ImagePreloader) iter.next(); ImageInfo info = preloader.preloadImage(uri, src, imageContext); if (info != null) { return info; } } throw new ImageException("The file format is not supported. No ImagePreloader found for " + uri); } private Map prepareHints(Map hints, ImageSessionContext sessionContext) { Map newHints = new java.util.HashMap(); if (hints != null) { newHints.putAll(hints); //Copy in case an unmodifiable map is passed in } if (!newHints.containsKey(ImageProcessingHints.IMAGE_SESSION_CONTEXT) && sessionContext != null) { newHints.put(ImageProcessingHints.IMAGE_SESSION_CONTEXT, sessionContext); } if (!newHints.containsKey(ImageProcessingHints.IMAGE_MANAGER)) { newHints.put(ImageProcessingHints.IMAGE_MANAGER, this); } return newHints; } /** * Loads an image. The caller can indicate what kind of image flavor is requested. When this * method is called the code looks for a suitable ImageLoader and, if necessary, builds * a conversion pipeline so it can return the image in exactly the form the caller needs. *

* Optionally, it is possible to pass in Map of hints. These hints may be used by ImageLoaders * and ImageConverters to act on the image. See {@link ImageProcessingHints} for common hints * used by the bundled implementations. You can, of course, define your own hints. * @param info the ImageInfo instance for the image (obtained by * {@link #getImageInfo(String, ImageSessionContext)}) * @param flavor the requested image flavor. * @param hints a Map of hints to any of the background components or null * @param session the session context * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image getImage(ImageInfo info, ImageFlavor flavor, Map hints, ImageSessionContext session) throws ImageException, IOException { hints = prepareHints(hints, session); Image img = null; ImageProviderPipeline pipeline = getPipelineFactory().newImageConverterPipeline( info, flavor); if (pipeline != null) { img = pipeline.execute(info, hints, session); } if (img == null) { throw new ImageException( "Cannot load image (no suitable loader/converter combination available) for " + info); } XmlSourceUtil.closeQuietly(session.getSource(info.getOriginalURI())); return img; } /** * Loads an image. The caller can indicate what kind of image flavors are requested. When this * method is called the code looks for a suitable ImageLoader and, if necessary, builds * a conversion pipeline so it can return the image in exactly the form the caller needs. * The array of image flavors is ordered, so the first image flavor is given highest priority. *

* Optionally, it is possible to pass in Map of hints. These hints may be used by ImageLoaders * and ImageConverters to act on the image. See {@link ImageProcessingHints} for common hints * used by the bundled implementations. You can, of course, define your own hints. * @param info the ImageInfo instance for the image (obtained by * {@link #getImageInfo(String, ImageSessionContext)}) * @param flavors the requested image flavors (in preferred order). * @param hints a Map of hints to any of the background components or null * @param session the session context * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image getImage(ImageInfo info, ImageFlavor[] flavors, Map hints, ImageSessionContext session) throws ImageException, IOException { hints = prepareHints(hints, session); Image img = null; ImageProviderPipeline[] candidates = getPipelineFactory().determineCandidatePipelines( info, flavors); ImageProviderPipeline pipeline = choosePipeline(candidates); if (pipeline != null) { img = pipeline.execute(info, hints, session); } if (img == null) { throw new ImageException( "Cannot load image (no suitable loader/converter combination available) for " + info); } XmlSourceUtil.closeQuietly(session.getSource(info.getOriginalURI())); return img; } /** * Loads an image with no hints. See * {@link #getImage(ImageInfo, ImageFlavor, Map, ImageSessionContext)} for more * information. * @param info the ImageInfo instance for the image (obtained by * {@link #getImageInfo(String, ImageSessionContext)}) * @param flavor the requested image flavor. * @param session the session context * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image getImage(ImageInfo info, ImageFlavor flavor, ImageSessionContext session) throws ImageException, IOException { return getImage(info, flavor, ImageUtil.getDefaultHints(session), session); } /** * Loads an image with no hints. See * {@link #getImage(ImageInfo, ImageFlavor[], Map, ImageSessionContext)} for more * information. * @param info the ImageInfo instance for the image (obtained by * {@link #getImageInfo(String, ImageSessionContext)}) * @param flavors the requested image flavors (in preferred order). * @param session the session context * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image getImage(ImageInfo info, ImageFlavor[] flavors, ImageSessionContext session) throws ImageException, IOException { return getImage(info, flavors, ImageUtil.getDefaultHints(session), session); } /** * Closes the resources associated to the given image. This method should be * used only when none of the {@code getImage} methods is called by the * client application. * * @param uri the URI of the image * @param session the session context that was used to resolve the URI */ public void closeImage(String uri, ImageSessionContext session) { XmlSourceUtil.closeQuietly(session.getSource(uri)); } /** * Converts an image. The caller can indicate what kind of image flavors are requested. When * this method is called the code looks for a suitable combination of ImageConverters so it * can return the image in exactly the form the caller needs. * The array of image flavors is ordered, so the first image flavor is given highest priority. *

* Optionally, it is possible to pass in Map of hints. These hints may be used by * ImageConverters to act on the image. See {@link ImageProcessingHints} for common hints * used by the bundled implementations. You can, of course, define your own hints. * @param image the image to convert * @param flavors the requested image flavors (in preferred order). * @param hints a Map of hints to any of the background components or null * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image convertImage(Image image, ImageFlavor[] flavors, Map hints) throws ImageException, IOException { hints = prepareHints(hints, null); ImageInfo info = image.getInfo(); Image img = null; for (ImageFlavor flavor : flavors) { if (image.getFlavor().equals(flavor)) { //Shortcut (the image is already in one of the requested formats) return image; } } ImageProviderPipeline[] candidates = getPipelineFactory().determineCandidatePipelines( image, flavors); ImageProviderPipeline pipeline = choosePipeline(candidates); if (pipeline != null) { img = pipeline.execute(info, image, hints, null); } if (img == null) { throw new ImageException( "Cannot convert image " + image + " (no suitable converter combination available)"); } return img; } /** * Converts an image with no hints. See * {@link #convertImage(Image, ImageFlavor[], Map)} for more * information. * @param image the image to convert * @param flavors the requested image flavors (in preferred order). * @return the fully loaded image * @throws ImageException If no suitable loader/converter combination is available to fulfill * the request or if an error occurred while loading the image. * @throws IOException If an I/O error occurs */ public Image convertImage(Image image, ImageFlavor[] flavors) throws ImageException, IOException { return convertImage(image, flavors, null); } /** * Chooses the best {@link ImageProviderPipeline} from a set of candidates. * @param candidates the candidates * @return the best pipeline */ public ImageProviderPipeline choosePipeline(ImageProviderPipeline[] candidates) { ImageProviderPipeline pipeline = null; int minPenalty = Integer.MAX_VALUE; int count = candidates.length; if (log.isTraceEnabled()) { log.trace("Candidate Pipelines:"); for (int i = 0; i < count; i++) { if (candidates[i] == null) { continue; } log.trace(" " + i + ": " + candidates[i].getConversionPenalty(getRegistry()) + " for " + candidates[i]); } } for (int i = count - 1; i >= 0; i--) { if (candidates[i] == null) { continue; } Penalty penalty = candidates[i].getConversionPenalty(getRegistry()); if (penalty.isInfinitePenalty()) { continue; //Exclude candidate on infinite penalty } if (penalty.getValue() <= minPenalty) { pipeline = candidates[i]; minPenalty = penalty.getValue(); } } if (log.isDebugEnabled()) { log.debug("Chosen pipeline: " + pipeline); } return pipeline; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy