Coverage Report - org.argouml.gefext.DeferredBufferedImage
 
Classes in this File Line Coverage Branch Coverage Complexity
DeferredBufferedImage
65%
54/83
37%
6/16
1.4
 
 1  
 /* $Id: DeferredBufferedImage.java 17820 2010-01-12 18:42:20Z linus $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2009 Contributors - see below
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    tfmorris
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 2008 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies. This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason. IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.gefext;
 40  
 
 41  
 import java.awt.AlphaComposite;
 42  
 import java.awt.Color;
 43  
 import java.awt.Composite;
 44  
 import java.awt.Graphics2D;
 45  
 import java.awt.Rectangle;
 46  
 import java.awt.image.BufferedImage;
 47  
 import java.awt.image.ColorModel;
 48  
 import java.awt.image.Raster;
 49  
 import java.awt.image.RenderedImage;
 50  
 import java.awt.image.SampleModel;
 51  
 import java.awt.image.WritableRaster;
 52  
 import java.util.Vector;
 53  
 
 54  
 import org.apache.log4j.Logger;
 55  
 import org.tigris.gef.base.Editor;
 56  
 
 57  
 /**
 58  
  * Rendered GEF image which uses a band buffer to minimize memory usage for 
 59  
  * high resolution images.  Image is rendered on demand in multiple passes
 60  
  * as getData is called.
 61  
  * <p>
 62  
  * NOTE: Only the methods which are called by ImageIO.write have been tested
 63  
  * and proven to work (getMinX/Y, getWidth, getHeight, & getData).  This is 
 64  
  * <em>not</em> a general purpose RenderedImage implementation, so you are on
 65  
  * your own with any others.
 66  
  * 
 67  
  * @author Tom Morris <tfmorris@gmail.com>
 68  
  * @since ArgoUML 0.26
 69  
  */
 70  
 public class DeferredBufferedImage implements RenderedImage {
 71  
     
 72  1
     private static final Logger LOG = 
 73  
         Logger.getLogger(DeferredBufferedImage.class);
 74  
     
 75  
     /**
 76  
      * RGB values for background color for image. Set as transparent. Chosen
 77  
      * because it's unlikely to be selected by the user, and leaves the diagram
 78  
      * readable if viewed without transparency.
 79  
      */
 80  
     public static final int TRANSPARENT_BG_COLOR = 0x00efefef;
 81  
 
 82  
     /** 
 83  
      * Background color
 84  
      */
 85  1
     public static final Color BACKGROUND_COLOR =
 86  
             new Color(TRANSPARENT_BG_COLOR, true);
 87  
     
 88  
     private static final int BUFFER_HEIGHT = 32;
 89  
  
 90  
     private int x, y;
 91  
     private int width;
 92  
     private int height;
 93  
     private int scale;
 94  
     private BufferedImage image;
 95  
     private Editor editor;
 96  
     
 97  
     private int scaledBufferHeight;
 98  
     private int y1, y2;
 99  
     
 100  
     /**
 101  
      * Construct a new DeferredBufferedImage which will use GEF to render the
 102  
      * current diagram on demand.
 103  
      * 
 104  
      * @param drawingArea bounding rectangle of the area to be drawn
 105  
      * @param imageType Type of image to be created (e.g. TYPE_INT_ARGB). Must
 106  
      *                be on an image type which is supported by BufferedImage
 107  
      * @param ed GEF editor to be used for rendering the image
 108  
      * @param scaleFactor Integer scale factor to multiply by when rendering
 109  
      *                image.
 110  
      */
 111  
     public DeferredBufferedImage(Rectangle drawingArea, int imageType,
 112  2
             Editor ed, int scaleFactor) {
 113  
         
 114  2
         editor = ed;
 115  2
         scale = scaleFactor;
 116  
 
 117  2
         x = drawingArea.x;
 118  2
         y = drawingArea.y;
 119  2
         width = drawingArea.width;
 120  2
         height = drawingArea.height;
 121  
 
 122  
         // Scale everything up
 123  2
         x = x  * scale;
 124  2
         y = y  * scale;
 125  2
         width = width  * scale;
 126  2
         height = height  * scale;
 127  2
         scaledBufferHeight = BUFFER_HEIGHT * scale;
 128  
         
 129  
         // Create our bandbuffer which is just a small slice of the image
 130  
         // TODO: We used a fixed height buffer now, but we could be smarter and
 131  
         // compute a height which would fit in some memory budget, allowing us
 132  
         // to use taller buffers with narrower images, minimizing the overhead
 133  
         // of multiple rendering passes
 134  2
         image = new BufferedImage(width, scaledBufferHeight, imageType);
 135  
 
 136  
         // Initialize band buffer bounds
 137  2
         y1 = y;
 138  2
         y2 = y1;
 139  2
     }
 140  
     
 141  
 
 142  
     public Raster getData() {
 143  0
         LOG.debug("getData with no params");
 144  0
         return getData(new Rectangle(x, y, width, height));
 145  
     }
 146  
     
 147  
     public Raster getData(Rectangle clip) {
 148  
 //        LOG.debug("getData Rectangle = " + clip);
 149  160
         if (!isRasterValid(clip)) {
 150  6
             LOG.debug("Raster not valid, computing new raster");
 151  6
             computeRaster(clip);
 152  
         }
 153  160
         Rectangle oClip = offsetWindow(clip);
 154  160
         Raster ras = image.getData(oClip);
 155  160
         Raster translatedRaster = ras.createTranslatedChild(clip.x, clip.y);
 156  
 //        LOG.debug("getData returning raster = " + translatedRaster);
 157  160
         return translatedRaster;
 158  
     }
 159  
 
 160  
     /**
 161  
      * @param clip clipping rectangle to test
 162  
      * @return true if clip rectangle is completely contained in image raster
 163  
      */
 164  
     private boolean isRasterValid(Rectangle clip) {
 165  160
         if (clip.height > scaledBufferHeight) {
 166  0
             throw new IndexOutOfBoundsException(
 167  
                     "clip rectangle must fit in band buffer");
 168  
         }
 169  160
         return (clip.y >= y1 && (clip.y + clip.height - 1) < y2);
 170  
     }
 171  
 
 172  
     /**
 173  
      * Rasterize the next slice in the band buffer. Raster will be the full
 174  
      * width of the image and based vertically at the Y value of the current
 175  
      * clip rectangle.
 176  
      * 
 177  
      * @param clip clip rectangle of interest
 178  
      */
 179  
     private void computeRaster(Rectangle clip) {
 180  6
         LOG.debug("Computing raster for rectangle " + clip);
 181  
         
 182  
         // Create a new graphics context so we start with fresh transforms
 183  6
         Graphics2D graphics = image.createGraphics();
 184  6
         graphics.scale(1.0 * scale, 1.0 * scale);
 185  
 
 186  
         // Fill with our background color
 187  6
         graphics.setColor(BACKGROUND_COLOR);
 188  6
         Composite c = graphics.getComposite();
 189  6
         graphics.setComposite(AlphaComposite.Src);
 190  6
         graphics.fillRect(0, 0, width, scaledBufferHeight);
 191  6
         graphics.setComposite(c);
 192  
 
 193  
         // Translate & clip graphic to match region of interest
 194  6
         graphics.setClip(0, 0, width, scaledBufferHeight);
 195  6
         graphics.translate(0, -clip.y / scale);
 196  6
         y1 = clip.y;
 197  6
         y2 = y1 + scaledBufferHeight;
 198  
 
 199  
         // Ask GEF to print a band of the diagram (translated & clipped)
 200  6
         editor.print(graphics);
 201  
         
 202  
         // Make sure it isn't caching anything that should be written
 203  6
         graphics.dispose();
 204  6
     }
 205  
 
 206  
     /**
 207  
      * Return a new clip rectangle which is offset by the base of our band
 208  
      * buffer.
 209  
      * 
 210  
      * @param clip clipping rectangle
 211  
      * @return adjusted clipping rectangle
 212  
      */
 213  
     private Rectangle offsetWindow(Rectangle clip) {
 214  160
         int baseY = clip.y - y1;
 215  160
         return new Rectangle(clip.x, baseY, clip.width, 
 216  
                 Math.min(clip.height, scaledBufferHeight - baseY));
 217  
     }
 218  
 
 219  
 
 220  
     /**
 221  
      * Not implemented
 222  
      * 
 223  
      * {@inheritDoc}
 224  
      * @see java.awt.image.RenderedImage#copyData(java.awt.image.WritableRaster)
 225  
      */
 226  
     public WritableRaster copyData(WritableRaster outRaster) {
 227  0
         throw new UnsupportedOperationException();
 228  
         // This needs to iterate to fill entire output raster if implemented
 229  
 //        return image.copyData(outRaster);
 230  
     }
 231  
 
 232  
     /**
 233  
      * Not implemented.
 234  
      * {@inheritDoc}
 235  
      * 
 236  
      * @see java.awt.image.RenderedImage#getSources()
 237  
      */
 238  
     public Vector<RenderedImage> getSources() {
 239  0
         return null;
 240  
     }
 241  
 
 242  
     public ColorModel getColorModel() {
 243  164
         return image.getColorModel();
 244  
     }
 245  
 
 246  
     public int getMinX() {
 247  2
         LOG.debug("getMinX = 0");
 248  2
         return 0;
 249  
     }
 250  
 
 251  
     public int getMinY() {
 252  2
         LOG.debug("getMinY = 0");
 253  2
         return 0;
 254  
     }
 255  
 
 256  
     public int getMinTileX() {
 257  0
         LOG.debug("getMinTileX = 0");
 258  0
         return 0;
 259  
     }
 260  
 
 261  
     public int getMinTileY() {
 262  0
         LOG.debug("getMinTileY = 0");
 263  0
         return 0;
 264  
     }
 265  
     
 266  
     public int getNumXTiles() {
 267  0
         LOG.debug("getNumXTiles = 1");
 268  0
         return 1;
 269  
     }
 270  
     
 271  
     /**
 272  
      * Untested. Not guaranteed to work. 
 273  
      * {@inheritDoc}
 274  
      * 
 275  
      * @see java.awt.image.RenderedImage#getNumYTiles()
 276  
      */
 277  
     public int getNumYTiles() {
 278  0
         int tiles = (getHeight() + scaledBufferHeight - 1) / scaledBufferHeight;
 279  0
         LOG.debug("getNumYTiles = " + tiles);
 280  0
         return tiles;
 281  
     }
 282  
 
 283  
     public Object getProperty(String name) {
 284  0
         return image.getProperty(name);
 285  
     }
 286  
 
 287  
     public String[] getPropertyNames() {
 288  0
         return image.getPropertyNames();
 289  
     }
 290  
 
 291  
     public SampleModel getSampleModel() {
 292  6
         return image.getSampleModel();
 293  
     }
 294  
 
 295  
 
 296  
     /**
 297  
      * Untested. Not guaranteed to work. 
 298  
      * {@inheritDoc}
 299  
      * 
 300  
      * @see java.awt.image.RenderedImage#getNumYTiles()
 301  
      */
 302  
 
 303  
     public Raster getTile(int tileX, int tileY) {
 304  0
         LOG.debug("getTile x=" + tileX + " y = " + tileY);
 305  0
         if (tileX < getMinTileX() 
 306  
                 || tileX >= getMinTileX() + getNumXTiles()
 307  
                 || tileY < getMinTileY() 
 308  
                 || tileY >= getMinTileY() + getNumYTiles()) {
 309  0
             throw new IndexOutOfBoundsException();
 310  
         }
 311  
         // FIXME: Boundary condition at end of image for non-integral
 312  
         // multiples of BUFFER_HEIGHT
 313  0
         Rectangle tileBounds = new Rectangle(0, (tileY - getMinTileY()
 314  
                 * scaledBufferHeight), width, scaledBufferHeight);
 315  0
         return getData(tileBounds);
 316  
     }
 317  
 
 318  
     public int getTileGridXOffset() {
 319  0
         LOG.debug("getTileGridXOffset = 0");
 320  0
         return 0;
 321  
     }
 322  
 
 323  
     public int getTileGridYOffset() {
 324  0
         LOG.debug("getTileGridYOffset = 0");
 325  0
         return 0;
 326  
     }
 327  
 
 328  
     public int getTileWidth() {
 329  0
         LOG.debug("getTileWidth = " + width);
 330  0
         return width;
 331  
     }
 332  
     
 333  
     public int getTileHeight() {
 334  0
         LOG.debug("getTileHeight = " + scaledBufferHeight);
 335  0
         return scaledBufferHeight;
 336  
     }
 337  
 
 338  
     public int getWidth() {
 339  2
         LOG.debug("getWidth = " + width);
 340  2
         return width;
 341  
     }
 342  
     
 343  
     public int getHeight() {
 344  2
         LOG.debug("getHeight = " + height);
 345  2
         return height;
 346  
     }
 347  
 }