1 /*
   2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.imageio.plugins.common;
  26 
  27 import java.awt.Point;
  28 import java.awt.Rectangle;
  29 import java.awt.image.RenderedImage;
  30 import java.awt.image.ColorModel;
  31 import java.awt.image.SampleModel;
  32 import java.awt.image.Raster;
  33 import java.awt.image.WritableRaster;
  34 import java.util.Enumeration;
  35 import java.util.Hashtable;
  36 import java.util.Iterator;
  37 import java.util.Vector;
  38 
  39 public abstract class SimpleRenderedImage implements RenderedImage {
  40     /** The X coordinate of the image's upper-left pixel. */
  41     protected int minX;
  42 
  43     /** The Y coordinate of the image's upper-left pixel. */
  44     protected int minY;
  45 
  46     /** The image's width in pixels. */
  47     protected int width;
  48 
  49     /** The image's height in pixels. */
  50     protected int height;
  51 
  52     /** The width of a tile. */
  53     protected int tileWidth;
  54 
  55     /** The height of a tile. */
  56     protected int tileHeight;
  57 
  58     /** The X coordinate of the upper-left pixel of tile (0, 0). */
  59     protected int tileGridXOffset = 0;
  60 
  61     /** The Y coordinate of the upper-left pixel of tile (0, 0). */
  62     protected int tileGridYOffset = 0;
  63 
  64     /** The image's SampleModel. */
  65     protected SampleModel sampleModel;
  66 
  67     /** The image's ColorModel. */
  68     protected ColorModel colorModel;
  69 
  70     /** The image's sources, stored in a Vector. */
  71     protected Vector<RenderedImage> sources = new Vector<RenderedImage>();
  72 
  73     /** A Hashtable containing the image properties. */
  74     protected Hashtable<String,Object> properties = new Hashtable<String,Object>();
  75 
  76     /** Returns the X coordinate of the leftmost column of the image. */
  77     public int getMinX() {
  78         return minX;
  79     }
  80 
  81     /**
  82      * Returns the X coordinate of the column immediatetely to the
  83      * right of the rightmost column of the image.  getMaxX() is
  84      * implemented in terms of getMinX() and getWidth() and so does
  85      * not need to be implemented by subclasses.
  86      */
  87     public final int getMaxX() {
  88         return getMinX() + getWidth();
  89     }
  90 
  91     /** Returns the X coordinate of the uppermost row of the image. */
  92     public int getMinY() {
  93         return minY;
  94     }
  95 
  96     /**
  97      * Returns the Y coordinate of the row immediately below the
  98      * bottom row of the image.  getMaxY() is implemented in terms of
  99      * getMinY() and getHeight() and so does not need to be
 100      * implemented by subclasses.
 101      */
 102     public final int getMaxY() {
 103         return getMinY() + getHeight();
 104     }
 105 
 106     /** Returns the width of the image. */
 107     public int getWidth() {
 108         return width;
 109     }
 110 
 111     /** Returns the height of the image. */
 112     public int getHeight() {
 113         return height;
 114     }
 115 
 116     /** Returns a Rectangle indicating the image bounds. */
 117     public Rectangle getBounds() {
 118         return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight());
 119     }
 120 
 121     /** Returns the width of a tile. */
 122     public int getTileWidth() {
 123         return tileWidth;
 124     }
 125 
 126     /** Returns the height of a tile. */
 127     public int getTileHeight() {
 128         return tileHeight;
 129     }
 130 
 131     /**
 132      * Returns the X coordinate of the upper-left pixel of tile (0, 0).
 133      */
 134     public int getTileGridXOffset() {
 135         return tileGridXOffset;
 136     }
 137 
 138     /**
 139      * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
 140      */
 141     public int getTileGridYOffset() {
 142         return tileGridYOffset;
 143     }
 144 
 145     /**
 146      * Returns the horizontal index of the leftmost column of tiles.
 147      * getMinTileX() is implemented in terms of getMinX()
 148      * and so does not need to be implemented by subclasses.
 149      */
 150     public int getMinTileX() {
 151         return XToTileX(getMinX());
 152     }
 153 
 154     /**
 155      * Returns the horizontal index of the rightmost column of tiles.
 156      * getMaxTileX() is implemented in terms of getMaxX()
 157      * and so does not need to be implemented by subclasses.
 158      */
 159     public int getMaxTileX() {
 160         return XToTileX(getMaxX() - 1);
 161     }
 162 
 163     /**
 164      * Returns the number of tiles along the tile grid in the
 165      * horizontal direction.  getNumXTiles() is implemented in terms
 166      * of getMinTileX() and getMaxTileX() and so does not need to be
 167      * implemented by subclasses.
 168      */
 169     public int getNumXTiles() {
 170         return getMaxTileX() - getMinTileX() + 1;
 171     }
 172 
 173     /**
 174      * Returns the vertical index of the uppermost row of tiles.  getMinTileY()
 175      * is implemented in terms of getMinY() and so does not need to be
 176      * implemented by subclasses.
 177      */
 178     public int getMinTileY() {
 179         return YToTileY(getMinY());
 180     }
 181 
 182     /**
 183      * Returns the vertical index of the bottom row of tiles.  getMaxTileY()
 184      * is implemented in terms of getMaxY() and so does not need to
 185      * be implemented by subclasses.
 186      */
 187     public int getMaxTileY() {
 188         return YToTileY(getMaxY() - 1);
 189     }
 190 
 191     /**
 192      * Returns the number of tiles along the tile grid in the vertical
 193      * direction.  getNumYTiles() is implemented in terms
 194      * of getMinTileY() and getMaxTileY() and so does not need to be
 195      * implemented by subclasses.
 196      */
 197     public int getNumYTiles() {
 198         return getMaxTileY() - getMinTileY() + 1;
 199     }
 200 
 201     /** Returns the SampleModel of the image. */
 202     public SampleModel getSampleModel() {
 203         return sampleModel;
 204     }
 205 
 206     /** Returns the ColorModel of the image. */
 207     public ColorModel getColorModel() {
 208         return colorModel;
 209     }
 210 
 211     /**
 212      * Gets a property from the property set of this image.  If the
 213      * property name is not recognized,
 214      * <code>java.awt.Image.UndefinedProperty</code> will be returned.
 215      *
 216      * @param name the name of the property to get, as a
 217      * <code>String</code>.  @return a reference to the property
 218      * <code>Object</code>, or the value
 219      * <code>java.awt.Image.UndefinedProperty.</code>
 220      */
 221     public Object getProperty(String name) {
 222         name = name.toLowerCase();
 223         Object value = properties.get(name);
 224         return value != null ? value : java.awt.Image.UndefinedProperty;
 225     }
 226 
 227     /**
 228      * Returns a list of the properties recognized by this image.  If
 229      * no properties are available, <code>null</code> will be
 230      * returned.
 231      *
 232      * @return an array of <code>String</code>s representing valid
 233      *         property names.
 234      */
 235     public String[] getPropertyNames() {
 236         String[] names = null;
 237 
 238         if(properties.size() > 0) {
 239             names = new String[properties.size()];
 240             int index = 0;
 241 
 242             Enumeration<String> e = properties.keys();
 243             while (e.hasMoreElements()) {
 244                 String name = e.nextElement();
 245                 names[index++] = name;
 246             }
 247         }
 248 
 249         return names;
 250     }
 251 
 252     /**
 253      * Returns an array of <code>String</code>s recognized as names by
 254      * this property source that begin with the supplied prefix.  If
 255      * no property names match, <code>null</code> will be returned.
 256      * The comparison is done in a case-independent manner.
 257      *
 258      * <p> The default implementation calls
 259      * <code>getPropertyNames()</code> and searches the list of names
 260      * for matches.
 261      *
 262      * @return an array of <code>String</code>s giving the valid
 263      * property names.
 264      */
 265     public String[] getPropertyNames(String prefix) {
 266         String propertyNames[] = getPropertyNames();
 267         if (propertyNames == null) {
 268             return null;
 269         }
 270 
 271         prefix = prefix.toLowerCase();
 272 
 273         Vector<String> names = new Vector<String>();
 274         for (int i = 0; i < propertyNames.length; i++) {
 275             if (propertyNames[i].startsWith(prefix)) {
 276                 names.addElement(propertyNames[i]);
 277             }
 278         }
 279 
 280         if (names.size() == 0) {
 281             return null;
 282         }
 283 
 284         // Copy the strings from the Vector over to a String array.
 285         String prefixNames[] = new String[names.size()];
 286         int count = 0;
 287         for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
 288             prefixNames[count++] = it.next();
 289         }
 290 
 291         return prefixNames;
 292     }
 293 
 294     // Utility methods.
 295 
 296     /**
 297      * Converts a pixel's X coordinate into a horizontal tile index
 298      * relative to a given tile grid layout specified by its X offset
 299      * and tile width.
 300      */
 301     public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
 302         x -= tileGridXOffset;
 303         if (x < 0) {
 304             x += 1 - tileWidth; // Force round to -infinity
 305         }
 306         return x/tileWidth;
 307     }
 308 
 309     /**
 310      * Converts a pixel's Y coordinate into a vertical tile index
 311      * relative to a given tile grid layout specified by its Y offset
 312      * and tile height.
 313      */
 314     public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
 315         y -= tileGridYOffset;
 316         if (y < 0) {
 317             y += 1 - tileHeight; // Force round to -infinity
 318         }
 319         return y/tileHeight;
 320     }
 321 
 322     /**
 323      * Converts a pixel's X coordinate into a horizontal tile index.
 324      * This is a convenience method.  No attempt is made to detect
 325      * out-of-range coordinates.
 326      *
 327      * @param x the X coordinate of a pixel.
 328      * @return the X index of the tile containing the pixel.
 329      */
 330     public int XToTileX(int x) {
 331         return XToTileX(x, getTileGridXOffset(), getTileWidth());
 332     }
 333 
 334     /**
 335      * Converts a pixel's Y coordinate into a vertical tile index.
 336      * This is a convenience method.  No attempt is made to detect
 337      * out-of-range coordinates.
 338      *
 339      * @param y the Y coordinate of a pixel.
 340      * @return the Y index of the tile containing the pixel.
 341      */
 342     public int YToTileY(int y) {
 343         return YToTileY(y, getTileGridYOffset(), getTileHeight());
 344     }
 345 
 346     /**
 347      * Converts a horizontal tile index into the X coordinate of its
 348      * upper left pixel relative to a given tile grid layout specified
 349      * by its X offset and tile width.
 350      */
 351     public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) {
 352         return tx*tileWidth + tileGridXOffset;
 353     }
 354 
 355     /**
 356      * Converts a vertical tile index into the Y coordinate of
 357      * its upper left pixel relative to a given tile grid layout
 358      * specified by its Y offset and tile height.
 359      */
 360     public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) {
 361         return ty*tileHeight + tileGridYOffset;
 362     }
 363 
 364     /**
 365      * Converts a horizontal tile index into the X coordinate of its
 366      * upper left pixel.  This is a convenience method.  No attempt is made
 367      * to detect out-of-range indices.
 368      *
 369      * @param tx the horizontal index of a tile.
 370      * @return the X coordinate of the tile's upper left pixel.
 371      */
 372     public int tileXToX(int tx) {
 373         return tx*tileWidth + tileGridXOffset;
 374     }
 375 
 376     /**
 377      * Converts a vertical tile index into the Y coordinate of its
 378      * upper left pixel.  This is a convenience method.  No attempt is made
 379      * to detect out-of-range indices.
 380      *
 381      * @param ty the vertical index of a tile.
 382      * @return the Y coordinate of the tile's upper left pixel.
 383      */
 384     public int tileYToY(int ty) {
 385         return ty*tileHeight + tileGridYOffset;
 386     }
 387 
 388     public Vector<RenderedImage> getSources() {
 389         return null;
 390     }
 391 
 392     /**
 393      * Returns the entire image in a single Raster.  For images with
 394      * multiple tiles this will require making a copy.
 395      *
 396      * <p> The returned Raster is semantically a copy.  This means
 397      * that updates to the source image will not be reflected in the
 398      * returned Raster.  For non-writable (immutable) source images,
 399      * the returned value may be a reference to the image's internal
 400      * data.  The returned Raster should be considered non-writable;
 401      * any attempt to alter its pixel data (such as by casting it to
 402      * WritableRaster or obtaining and modifying its DataBuffer) may
 403      * result in undefined behavior.  The copyData method should be
 404      * used if the returned Raster is to be modified.
 405      *
 406      * @return a Raster containing a copy of this image's data.
 407      */
 408     public Raster getData() {
 409         Rectangle rect = new Rectangle(getMinX(), getMinY(),
 410                                        getWidth(), getHeight());
 411         return getData(rect);
 412     }
 413 
 414     /**
 415      * Returns an arbitrary rectangular region of the RenderedImage
 416      * in a Raster.  The rectangle of interest will be clipped against
 417      * the image bounds.
 418      *
 419      * <p> The returned Raster is semantically a copy.  This means
 420      * that updates to the source image will not be reflected in the
 421      * returned Raster.  For non-writable (immutable) source images,
 422      * the returned value may be a reference to the image's internal
 423      * data.  The returned Raster should be considered non-writable;
 424      * any attempt to alter its pixel data (such as by casting it to
 425      * WritableRaster or obtaining and modifying its DataBuffer) may
 426      * result in undefined behavior.  The copyData method should be
 427      * used if the returned Raster is to be modified.
 428      *
 429      * @param bounds the region of the RenderedImage to be returned.
 430      */
 431     public Raster getData(Rectangle bounds) {
 432         // Get the image bounds.
 433         Rectangle imageBounds = getBounds();
 434 
 435         // Check for parameter validity.
 436         if(bounds == null) {
 437             bounds = imageBounds;
 438         } else if(!bounds.intersects(imageBounds)) {
 439             throw new IllegalArgumentException("The provided region doesn't intersect with the image bounds.");
 440         }
 441 
 442         // Determine tile limits for the prescribed bounds.
 443         int startX = XToTileX(bounds.x);
 444         int startY = YToTileY(bounds.y);
 445         int endX = XToTileX(bounds.x + bounds.width - 1);
 446         int endY = YToTileY(bounds.y + bounds.height - 1);
 447 
 448         // If the bounds are contained in a single tile, return a child
 449         // of that tile's Raster.
 450         if ((startX == endX) && (startY == endY)) {
 451             Raster tile = getTile(startX, startY);
 452             return tile.createChild(bounds.x, bounds.y,
 453                                     bounds.width, bounds.height,
 454                                     bounds.x, bounds.y, null);
 455         } else {
 456             // Recalculate the tile limits if the data bounds are not a
 457             // subset of the image bounds.
 458             if(!imageBounds.contains(bounds)) {
 459                 Rectangle xsect = bounds.intersection(imageBounds);
 460                 startX = XToTileX(xsect.x);
 461                 startY = YToTileY(xsect.y);
 462                 endX = XToTileX(xsect.x + xsect.width - 1);
 463                 endY = YToTileY(xsect.y + xsect.height - 1);
 464             }
 465 
 466             // Create a WritableRaster of the desired size
 467             SampleModel sm =
 468                 sampleModel.createCompatibleSampleModel(bounds.width,
 469                                                         bounds.height);
 470 
 471             // Translate it
 472             WritableRaster dest =
 473                 Raster.createWritableRaster(sm, bounds.getLocation());
 474 
 475             // Loop over the tiles in the intersection.
 476             for (int j = startY; j <= endY; j++) {
 477                 for (int i = startX; i <= endX; i++) {
 478                     // Retrieve the tile.
 479                     Raster tile = getTile(i, j);
 480 
 481                     // Create a child of the tile for the intersection of
 482                     // the tile bounds and the bounds of the requested area.
 483                     Rectangle tileRect = tile.getBounds();
 484                     Rectangle intersectRect =
 485                         bounds.intersection(tile.getBounds());
 486                     Raster liveRaster = tile.createChild(intersectRect.x,
 487                                                          intersectRect.y,
 488                                                          intersectRect.width,
 489                                                          intersectRect.height,
 490                                                          intersectRect.x,
 491                                                          intersectRect.y,
 492                                                          null);
 493 
 494                     // Copy the data from the child.
 495                     dest.setRect(liveRaster);
 496                 }
 497             }
 498 
 499             return dest;
 500         }
 501     }
 502 
 503     /**
 504      * Copies an arbitrary rectangular region of the RenderedImage
 505      * into a caller-supplied WritableRaster.  The region to be
 506      * computed is determined by clipping the bounds of the supplied
 507      * WritableRaster against the bounds of the image.  The supplied
 508      * WritableRaster must have a SampleModel that is compatible with
 509      * that of the image.
 510      *
 511      * <p> If the raster argument is null, the entire image will
 512      * be copied into a newly-created WritableRaster with a SampleModel
 513      * that is compatible with that of the image.
 514      *
 515      * @param dest a WritableRaster to hold the returned portion of
 516      *        the image.
 517      * @return a reference to the supplied WritableRaster, or to a
 518      *         new WritableRaster if the supplied one was null.
 519      */
 520     public WritableRaster copyData(WritableRaster dest) {
 521         // Get the image bounds.
 522         Rectangle imageBounds = getBounds();
 523 
 524         Rectangle bounds;
 525         if (dest == null) {
 526             // Create a WritableRaster for the entire image.
 527             bounds = imageBounds;
 528             Point p = new Point(minX, minY);
 529             SampleModel sm =
 530                 sampleModel.createCompatibleSampleModel(width, height);
 531             dest = Raster.createWritableRaster(sm, p);
 532         } else {
 533             bounds = dest.getBounds();
 534         }
 535 
 536         // Determine tile limits for the intersection of the prescribed
 537         // bounds with the image bounds.
 538         Rectangle xsect = imageBounds.contains(bounds) ?
 539             bounds : bounds.intersection(imageBounds);
 540         int startX = XToTileX(xsect.x);
 541         int startY = YToTileY(xsect.y);
 542         int endX = XToTileX(xsect.x + xsect.width - 1);
 543         int endY = YToTileY(xsect.y + xsect.height - 1);
 544 
 545         // Loop over the tiles in the intersection.
 546         for (int j = startY; j <= endY; j++) {
 547             for (int i = startX; i <= endX; i++) {
 548                 // Retrieve the tile.
 549                 Raster tile = getTile(i, j);
 550 
 551                 // Create a child of the tile for the intersection of
 552                 // the tile bounds and the bounds of the requested area.
 553                 Rectangle tileRect = tile.getBounds();
 554                 Rectangle intersectRect =
 555                     bounds.intersection(tile.getBounds());
 556                 Raster liveRaster = tile.createChild(intersectRect.x,
 557                                                      intersectRect.y,
 558                                                      intersectRect.width,
 559                                                      intersectRect.height,
 560                                                      intersectRect.x,
 561                                                      intersectRect.y,
 562                                                      null);
 563 
 564                 // Copy the data from the child.
 565                 dest.setRect(liveRaster);
 566             }
 567         }
 568 
 569         return dest;
 570     }
 571 }