/* * 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 java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; /** * A default implementation for a the {@link MultiLevelModel} interface. */ public class DefaultMultiLevelModel implements MultiLevelModel { public final static int DEFAULT_MAX_LEVEL_PIXEL_COUNT = 256 * 256; private final int levelCount; private final AffineTransform[] imageToModelTransforms; private final AffineTransform[] modelToImageTransforms; private Rectangle2D modelBounds; /** * Constructs a a new model for a multi level source. * The number of levels is computed by {@link #getLevelCount(int, int)}. * The image bounds are computed by {@link #getModelBounds(java.awt.geom.AffineTransform, int, int)}. * * @param imageToModelTransform The affine transformation from image to model coordinates. * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. */ public DefaultMultiLevelModel(AffineTransform imageToModelTransform, int width, int height) { this(getLevelCount(width, height), imageToModelTransform, getModelBounds(imageToModelTransform, width, height)); } /** * Constructs a a new model for a multi level source. * The image bounds are computed by {@link #getModelBounds(java.awt.geom.AffineTransform, int, int)}. * * @param levelCount The number of levels. * @param imageToModelTransform The affine transformation from image to model coordinates. * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. */ public DefaultMultiLevelModel(int levelCount, AffineTransform imageToModelTransform, int width, int height) { this(levelCount, imageToModelTransform, getModelBounds(imageToModelTransform, width, height)); } /** * Constructs a a new model for a multi level source. * * @param levelCount The number of levels. * @param imageToModelTransform The affine transformation from image to model coordinates. * @param modelBounds The image bounds in model coordinates. */ public DefaultMultiLevelModel(int levelCount, AffineTransform imageToModelTransform, Rectangle2D modelBounds) { this.levelCount = levelCount; final AffineTransform modelToImageTransform; try { modelToImageTransform = imageToModelTransform.createInverse(); } catch (NoninvertibleTransformException e) { throw new IllegalArgumentException("imageToModelTransform", e); } this.imageToModelTransforms = new AffineTransform[levelCount]; this.modelToImageTransforms = new AffineTransform[levelCount]; this.imageToModelTransforms[0] = new AffineTransform(imageToModelTransform); this.modelToImageTransforms[0] = new AffineTransform(modelToImageTransform); setModelBounds(modelBounds); } @Override public int getLevelCount() { return levelCount; } @Override public int getLevel(double scale) { int level = (int) Math.floor(log2(scale)); if (level < 0) { level = 0; } else if (level >= levelCount) { level = levelCount - 1; } return level; } @Override public double getScale(int level) { checkLevel(level); return pow2(level); } @Override public final AffineTransform getImageToModelTransform(int level) { checkLevel(level); AffineTransform transform = imageToModelTransforms[level]; if (transform == null) { transform = new AffineTransform(imageToModelTransforms[0]); final double s = getScale(level); transform.scale(s, s); imageToModelTransforms[level] = transform; } return new AffineTransform(transform); } @Override public final AffineTransform getModelToImageTransform(int level) { checkLevel(level); AffineTransform transform = modelToImageTransforms[level]; if (transform == null) { try { transform = getImageToModelTransform(level).createInverse(); modelToImageTransforms[level] = transform; } catch (NoninvertibleTransformException e) { throw new IllegalStateException(e); } } return new AffineTransform(transform); } protected static double pow2(double v) { return Math.pow(2.0, v); } protected static double log2(double v) { return Math.log(v) / Math.log(2.0); } protected void checkLevel(int level) { if (level < 0 || level >= getLevelCount()) { throw new IllegalArgumentException("level"); } } @Override public Rectangle2D getModelBounds() { if (modelBounds != null) { return (Rectangle2D) modelBounds.clone(); } else { return null; } } public void setModelBounds(Rectangle2D modelBounds) { if (modelBounds != null) { this.modelBounds = (Rectangle2D) modelBounds.clone(); } else { this.modelBounds = null; } } /** * Computes the image bounding box in model coordinates. * * @param i2mTransform The affine transformation from image to model coordinates. * @param levelZeroImage The image at level zero. * @return The number of levels. */ public static Rectangle2D getModelBounds(AffineTransform i2mTransform, RenderedImage levelZeroImage) { return i2mTransform.createTransformedShape(new Rectangle(levelZeroImage.getMinX(), levelZeroImage.getMinY(), levelZeroImage.getWidth(), levelZeroImage.getHeight())).getBounds2D(); } /** * Computes the image bounding box in model coordinates. * * @param i2mTransform The affine transformation from image to model coordinates. * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. * @return The number of levels. */ public static Rectangle2D getModelBounds(AffineTransform i2mTransform, int width, int height) { return i2mTransform.createTransformedShape(new Rectangle(0, 0, width, height)).getBounds2D(); } /** * Computes the number of levels using the {@link #DEFAULT_MAX_LEVEL_PIXEL_COUNT} constant. * * @param width The width of the image in pixels at level zero. * @param height The height of the image in pixels at level zero. * @return The number of levels. */ public static int getLevelCount(int width, int height) { int levelCount = 1; double scale = 1.0; while ((scale * width) * (scale * height) >= DEFAULT_MAX_LEVEL_PIXEL_COUNT) { levelCount++; scale *= 0.5; } return levelCount; } }