/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.glevel.support; import com.bc.ceres.glevel.MultiLevelModel; import com.bc.ceres.glevel.MultiLevelSource; import javax.media.jai.PlanarImage; import javax.media.jai.RenderedOp; import javax.media.jai.operator.ConstantDescriptor; import javax.media.jai.operator.ScaleDescriptor; import java.awt.*; import java.awt.image.RenderedImage; /** * An abstract base class for {@link MultiLevelSource} implementations. * Level images are cached unless {@link #reset()} is called. * Subclasses are asked to implement {@link #createImage(int)}. */ public abstract class AbstractMultiLevelSource implements MultiLevelSource { private final MultiLevelModel multiLevelModel; private final RenderedImage[] levelImages; protected AbstractMultiLevelSource(MultiLevelModel multiLevelModel) { this.multiLevelModel = multiLevelModel; this.levelImages = new RenderedImage[multiLevelModel.getLevelCount()]; } @Override public MultiLevelModel getModel() { return multiLevelModel; } /** * Gets the {@code RenderedImage} at the given resolution level. Unless {@link #reset()} is called, * the method will always return the same image instance at the same resolution level. * If a level image is requested for the first time, the method calls * {@link #createImage(int)} in order to retrieve the actual image instance. * * @param level The resolution level. * @return The {@code RenderedImage} at the given resolution level. */ @Override public synchronized RenderedImage getImage(int level) { checkLevel(level); RenderedImage levelImage = levelImages[level]; if (levelImage == null) { levelImage = createImage(level); levelImages[level] = levelImage; } return levelImage; } @Override public Shape getImageShape(int level) { return null; } /** * Called by {@link #getImage(int)} if a level image is requested for the first time. * Note that images created via this method will be {@link PlanarImage#dispose disposed} * when {@link #reset} is called on this multi-level image source. See {@link #getImage(int)}. * <p/> * The dimension of the level image created must be the same as that obtained from * {@link #getImageDimension(int, int, double)} for the scale associated with the * given resolution level. * * @param level The resolution level. * @return An instance of a {@code RenderedImage} for the given resolution level. */ protected abstract RenderedImage createImage(int level); /** * Removes all cached level images and also disposes * any {@link javax.media.jai.PlanarImage PlanarImage}s among them.</p> * <p/> * <p>Overrides should always call {@code super.reset()}.<p/> */ @Override public synchronized void reset() { for (int level = 0; level < levelImages.length; level++) { RenderedImage levelImage = levelImages[level]; if (levelImage instanceof PlanarImage) { PlanarImage planarImage = (PlanarImage) levelImage; planarImage.dispose(); } levelImages[level] = null; } } /** * Utility method which checks if a given level is valid. * * @param level The resolution level. * @throws IllegalArgumentException if {@code level < 0 || level >= getModel().getLevelCount()} */ protected synchronized void checkLevel(int level) { if (level < 0 || level >= getModel().getLevelCount()) { throw new IllegalArgumentException("level=" + level); } } /** * Computes the dimension of an image at a certain level. The image dimension computed is the * same as that obtained from {@code javax.media.jai.operator.ScaleDescriptor.create(...)}. * * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. * @param scale The scale at the level of interest. * @return the dimension of the image at the level of interest. * @deprecated since Ceres 0.14, lower-level resolutions of image pyramids are computed in the JPEG2000-style, * that is {@code newSize=(int)ceil(scale * size)} (ceiling integer), * while JAI is {@code newSize=(int)ceil(scale * size - 0.5)} (rounding to nearest integer). * Please use {@link DefaultMultiLevelSource#getImageRectangle(int, int, int, int, double)} instead. */ @Deprecated public static Dimension getImageDimension(int width, int height, double scale) { final float scaleFactor = (float) (1.0 / scale); final RenderedOp c = ConstantDescriptor.create((float) width, (float) height, new Float[]{0.0f}, null); final RenderedOp s = ScaleDescriptor.create(c, scaleFactor, scaleFactor, 0.0f, 0.0f, null, null); return new Dimension(s.getWidth(), s.getHeight()); } /** * Computes the rectangle of an image at a certain level. The image rectangle computed is the * same as that obtained from {@code javax.media.jai.operator.ScaleDescriptor.create(...)}. * * @param minX The image's minimum X coordinate in pixels at level zero. * @param minY The image's minimum Y coordinate in pixels at level zero. * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. * @param scale The scale at the level of interest. * @return the dimension of the image at the level of interest. * @deprecated since Ceres 0.14, lower-level resolutions of image pyramids are computed in the JPEG2000-style, * that is {@code newSize=(int)ceil(scale * size)} (ceiling integer), * while JAI is {@code newSize=(int)ceil(scale * size - 0.5)} (rounding to nearest integer). * Please use {@link DefaultMultiLevelSource#getImageRectangle(int, int, int, int, double)} instead. */ @Deprecated public static Rectangle getImageRectangle(int minX, int minY, int width, int height, double scale) { final float scaleFactor = (float) (1.0 / scale); final RenderedOp c = ConstantDescriptor.create((float) width, (float) height, new Float[]{0.0f}, null); final RenderedOp s1 = ScaleDescriptor.create(c, 1.0F, 1.0F, (float) minX, (float) minY, null, null); final RenderedOp s2 = ScaleDescriptor.create(s1, scaleFactor, scaleFactor, 0.0F, 0.0F, null, null); return new Rectangle(s2.getMinX(), s2.getMinY(), s2.getWidth(), s2.getHeight()); } }