/* * GeoImagePyramid.java * * Created on December 4, 2006, 12:33 AM * */ package ika.geo; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.net.URL; import java.util.Vector; /** * * @author Bernhard Jenny, Institute of Cartography, ETH Zurich */ public class GeoImagePyramid extends GeoImage { /** * A vector holding the images of the pyramid. The first image at position 0 * is the original unscaled image, images with decreasing size follow. */ transient protected Vector images = new Vector(4); // FIXME /** * The smallest image will be a little smaller than * MIN_IMAGE_SIZE x MIN_IMAGE_SIZE pixels. */ private static final int MIN_IMAGE_SIZE = 256; /** * The next smaller image in the pyramid measures origSize x IMAGE_SCALE. */ private static final double IMAGE_SCALE = 0.5; /** * Create a new instance of GeoImagePyramid. * @param image A reference to an image to display. The image is not copied, * instead a reference is retained. * @param x Top left corner of this image. * @param y Top left corner of this image. * @param pixelSize Size of a pixel. */ public GeoImagePyramid(BufferedImage image, double x, double y, double pixelSize) { super(image, x, y, pixelSize); this.createPyramid(); } /** * Create a new instance of GeoImagePyramid. The lower left corner of the image is * placed at 0/0, the size of a pixel in world coordinates equals 1. * @param image A reference to an image to display. The image is not copied, * instead a reference is retained. */ public GeoImagePyramid(BufferedImage image, URL url) { super(image, url); this.createPyramid(); } /** * Creates the image pyramid. Stores all images in this.images, including * the original unscaled image already stored in super.image. */ private void createPyramid() { // clear any previous image pyramid this.images.clear(); // add the unscaled image to the pyramid vector. this.images.add(this.image); // setup an affine transformation for downscaling AffineTransform tx = new AffineTransform(); tx.scale(IMAGE_SCALE, IMAGE_SCALE); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC); // repeatedly downscale the image and store the images in the pyramid. BufferedImage lastImage = this.image; while (lastImage.getHeight() > MIN_IMAGE_SIZE || lastImage.getWidth() > MIN_IMAGE_SIZE) { lastImage = op.filter(lastImage, null); this.images.add(lastImage); } } /** * Draw the image in a map. */ @Override public void drawNormalState(RenderParams rp) { if (this.image == null) { return; } // find the image to display final int imageID = this.findImageToDraw(rp.scale); BufferedImage img = (BufferedImage)this.images.get(imageID); // compute the scale for the image. // This should be done in a better way, by deriving the scale from the bounding box. FIXME double imageScale = Math.pow(1/IMAGE_SCALE, imageID); this.drawImage(img, rp, imageScale); } @Override public void drawSelectedState(RenderParams rp) { if (!this.isSelected()) { return; } Rectangle2D.Double bounds = (Rectangle2D.Double)this.getBounds2D(rp.scale); if (bounds != null) { rp.g2d.draw(bounds); } } @Override public void transform(AffineTransform affineTransform) { super.transform(affineTransform); this.createPyramid(); } /** * Returns the index of an image to draw for a certain map scale. The index * points into this.images. * @param mapScale The current scale of the map. */ private int findImageToDraw(double mapScale) { // compute the size of one image pixel in screen coordinates. double pixelSize = Math.min(this.getCellSize(), this.getCellSize()); // FIXME double pixelSizeOnScreen = pixelSize * mapScale; // if the original image has to be enlarged for display, draw the original. if (pixelSizeOnScreen >= 1) { return 0; } // search through the pyramid for the appropriate image. // not elegant, but it works. int imageID = -1; while (imageID < images.size() && pixelSizeOnScreen < 1) { pixelSizeOnScreen /= IMAGE_SCALE; imageID++; } return Math.min(imageID, images.size()-1); } }