1 /*
   2  * Copyright (c) 1997, 2018, 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 
  26 package java.awt.image;
  27 
  28 import java.util.Hashtable;
  29 import java.awt.image.ImageConsumer;
  30 import java.awt.image.ImageFilter;
  31 
  32 /**
  33  * The {@code BufferedImageFilter} class subclasses an
  34  * {@code ImageFilter} to provide a simple means of
  35  * using a single-source/single-destination image operator
  36  * ({@link BufferedImageOp}) to filter a {@code BufferedImage}
  37  * in the Image Producer/Consumer/Observer
  38  * paradigm. Examples of these image operators are: {@link ConvolveOp},
  39  * {@link AffineTransformOp} and {@link LookupOp}.
  40  *
  41  * @see ImageFilter
  42  * @see BufferedImage
  43  * @see BufferedImageOp
  44  */
  45 
  46 public class BufferedImageFilter extends ImageFilter implements Cloneable {
  47     BufferedImageOp bufferedImageOp;
  48     ColorModel model;
  49     int width;
  50     int height;
  51     byte[] bytePixels;
  52     int[] intPixels;
  53 
  54     /**
  55      * Constructs a {@code BufferedImageFilter} with the
  56      * specified single-source/single-destination operator.
  57      * @param op the specified {@code BufferedImageOp} to
  58      *           use to filter a {@code BufferedImage}
  59      * @throws NullPointerException if op is null
  60      */
  61     public BufferedImageFilter (BufferedImageOp op) {
  62         super();
  63         if (op == null) {
  64             throw new NullPointerException("Operation cannot be null");
  65         }
  66         bufferedImageOp = op;
  67     }
  68 
  69     /**
  70      * Returns the {@code BufferedImageOp}.
  71      * @return the operator of this {@code BufferedImageFilter}.
  72      */
  73     public BufferedImageOp getBufferedImageOp() {
  74         return bufferedImageOp;
  75     }
  76 
  77     /**
  78      * Filters the information provided in the
  79      * {@link ImageConsumer#setDimensions(int, int) setDimensions } method
  80      * of the {@link ImageConsumer} interface.
  81      * <p>
  82      * Note: This method is intended to be called by the
  83      * {@link ImageProducer} of the {@code Image} whose pixels are
  84      * being filtered. Developers using this class to retrieve pixels from
  85      * an image should avoid calling this method directly since that
  86      * operation could result in problems with retrieving the requested
  87      * pixels.
  88      *
  89      * @param width the width to which to set the width of this
  90      *        {@code BufferedImageFilter}
  91      * @param height the height to which to set the height of this
  92      *        {@code BufferedImageFilter}
  93      * @see ImageConsumer#setDimensions
  94      */
  95     public void setDimensions(int width, int height) {
  96         if (width <= 0 || height <= 0) {
  97             imageComplete(STATICIMAGEDONE);
  98             return;
  99         }
 100         this.width  = width;
 101         this.height = height;
 102     }
 103 
 104     /**
 105      * Filters the information provided in the
 106      * {@link ImageConsumer#setColorModel(ColorModel) setColorModel} method
 107      * of the {@code ImageConsumer} interface.
 108      * <p>
 109      * If {@code model} is {@code null}, this
 110      * method clears the current {@code ColorModel} of this
 111      * {@code BufferedImageFilter}.
 112      * <p>
 113      * Note: This method is intended to be called by the
 114      * {@code ImageProducer} of the {@code Image}
 115      * whose pixels are being filtered.  Developers using this
 116      * class to retrieve pixels from an image
 117      * should avoid calling this method directly since that
 118      * operation could result in problems with retrieving the
 119      * requested pixels.
 120      * @param model the {@link ColorModel} to which to set the
 121      *        {@code ColorModel} of this {@code BufferedImageFilter}
 122      * @see ImageConsumer#setColorModel
 123      */
 124     public void setColorModel(ColorModel model) {
 125         this.model = model;
 126     }
 127 
 128     private void convertToRGB() {
 129         int size = width * height;
 130         int[] newpixels = new int[size];
 131         if (bytePixels != null) {
 132             for (int i = 0; i < size; i++) {
 133                 newpixels[i] = this.model.getRGB(bytePixels[i] & 0xff);
 134             }
 135         } else if (intPixels != null) {
 136             for (int i = 0; i < size; i++) {
 137                 newpixels[i] = this.model.getRGB(intPixels[i]);
 138             }
 139         }
 140         bytePixels = null;
 141         intPixels = newpixels;
 142         this.model = ColorModel.getRGBdefault();
 143     }
 144 
 145     /**
 146      * Filters the information provided in the {@code setPixels}
 147      * method of the {@code ImageConsumer} interface which takes
 148      * an array of bytes.
 149      * <p>
 150      * Note: This method is intended to be called by the
 151      * {@code ImageProducer} of the {@code Image} whose pixels
 152      * are being filtered.  Developers using
 153      * this class to retrieve pixels from an image should avoid calling
 154      * this method directly since that operation could result in problems
 155      * with retrieving the requested pixels.
 156      * @throws IllegalArgumentException if width or height are less than
 157      * zero.
 158      * @see ImageConsumer#setPixels(int, int, int, int, ColorModel, byte[],
 159                                     int, int)
 160      */
 161     public void setPixels(int x, int y, int w, int h,
 162                           ColorModel model, byte[] pixels, int off,
 163                           int scansize) {
 164         // Fix 4184230
 165         if (w < 0 || h < 0) {
 166             throw new IllegalArgumentException("Width ("+w+
 167                                                 ") and height ("+h+
 168                                                 ") must be > 0");
 169         }
 170         // Nothing to do
 171         if (w == 0 || h == 0) {
 172             return;
 173         }
 174         if (y < 0) {
 175             int diff = -y;
 176             if (diff >= h) {
 177                 return;
 178             }
 179             off += scansize * diff;
 180             y += diff;
 181             h -= diff;
 182         }
 183         if (y + h > height) {
 184             h = height - y;
 185             if (h <= 0) {
 186                 return;
 187             }
 188         }
 189         if (x < 0) {
 190             int diff = -x;
 191             if (diff >= w) {
 192                 return;
 193             }
 194             off += diff;
 195             x += diff;
 196             w -= diff;
 197         }
 198         if (x + w > width) {
 199             w = width - x;
 200             if (w <= 0) {
 201                 return;
 202             }
 203         }
 204         int dstPtr = y*width + x;
 205         if (intPixels == null) {
 206             if (bytePixels == null) {
 207                 bytePixels = new byte[width*height];
 208                 this.model = model;
 209             } else if (this.model != model) {
 210                 convertToRGB();
 211             }
 212             if (bytePixels != null) {
 213                 for (int sh = h; sh > 0; sh--) {
 214                     System.arraycopy(pixels, off, bytePixels, dstPtr, w);
 215                     off += scansize;
 216                     dstPtr += width;
 217                 }
 218             }
 219         }
 220         if (intPixels != null) {
 221             int dstRem = width - w;
 222             int srcRem = scansize - w;
 223             for (int sh = h; sh > 0; sh--) {
 224                 for (int sw = w; sw > 0; sw--) {
 225                     intPixels[dstPtr++] = model.getRGB(pixels[off++]&0xff);
 226                 }
 227                 off    += srcRem;
 228                 dstPtr += dstRem;
 229             }
 230         }
 231     }
 232     /**
 233      * Filters the information provided in the {@code setPixels}
 234      * method of the {@code ImageConsumer} interface which takes
 235      * an array of integers.
 236      * <p>
 237      * Note: This method is intended to be called by the
 238      * {@code ImageProducer} of the {@code Image} whose
 239      * pixels are being filtered.  Developers using this class to
 240      * retrieve pixels from an image should avoid calling this method
 241      * directly since that operation could result in problems
 242      * with retrieving the requested pixels.
 243      * @throws IllegalArgumentException if width or height are less than
 244      * zero.
 245      * @see ImageConsumer#setPixels(int, int, int, int, ColorModel, int[],
 246                                     int, int)
 247      */
 248     public void setPixels(int x, int y, int w, int h,
 249                           ColorModel model, int[] pixels, int off,
 250                           int scansize) {
 251         // Fix 4184230
 252         if (w < 0 || h < 0) {
 253             throw new IllegalArgumentException("Width ("+w+
 254                                                 ") and height ("+h+
 255                                                 ") must be > 0");
 256         }
 257         // Nothing to do
 258         if (w == 0 || h == 0) {
 259             return;
 260         }
 261         if (y < 0) {
 262             int diff = -y;
 263             if (diff >= h) {
 264                 return;
 265             }
 266             off += scansize * diff;
 267             y += diff;
 268             h -= diff;
 269         }
 270         if (y + h > height) {
 271             h = height - y;
 272             if (h <= 0) {
 273                 return;
 274             }
 275         }
 276         if (x < 0) {
 277             int diff = -x;
 278             if (diff >= w) {
 279                 return;
 280             }
 281             off += diff;
 282             x += diff;
 283             w -= diff;
 284         }
 285         if (x + w > width) {
 286             w = width - x;
 287             if (w <= 0) {
 288                 return;
 289             }
 290         }
 291 
 292         if (intPixels == null) {
 293             if (bytePixels == null) {
 294                 intPixels = new int[width * height];
 295                 this.model = model;
 296             } else {
 297                 convertToRGB();
 298             }
 299         }
 300         int dstPtr = y*width + x;
 301         if (this.model == model) {
 302             for (int sh = h; sh > 0; sh--) {
 303                 System.arraycopy(pixels, off, intPixels, dstPtr, w);
 304                 off += scansize;
 305                 dstPtr += width;
 306             }
 307         } else {
 308             if (this.model != ColorModel.getRGBdefault()) {
 309                 convertToRGB();
 310             }
 311             int dstRem = width - w;
 312             int srcRem = scansize - w;
 313             for (int sh = h; sh > 0; sh--) {
 314                 for (int sw = w; sw > 0; sw--) {
 315                     intPixels[dstPtr++] = model.getRGB(pixels[off++]);
 316                 }
 317                 off += srcRem;
 318                 dstPtr += dstRem;
 319             }
 320         }
 321     }
 322 
 323     /**
 324      * Filters the information provided in the {@code imageComplete}
 325      * method of the {@code ImageConsumer} interface.
 326      * <p>
 327      * Note: This method is intended to be called by the
 328      * {@code ImageProducer} of the {@code Image} whose pixels
 329      * are being filtered.  Developers using
 330      * this class to retrieve pixels from an image should avoid calling
 331      * this method directly since that operation could result in problems
 332      * with retrieving the requested pixels.
 333      * @param status the status of image loading
 334      * @throws ImagingOpException if there was a problem calling the filter
 335      * method of the {@code BufferedImageOp} associated with this
 336      * instance.
 337      * @see ImageConsumer#imageComplete
 338      */
 339     public void imageComplete(int status) {
 340         WritableRaster wr;
 341         switch(status) {
 342         case IMAGEERROR:
 343         case IMAGEABORTED:
 344             // reinitialize the params
 345             model  = null;
 346             width  = -1;
 347             height = -1;
 348             intPixels  = null;
 349             bytePixels = null;
 350             break;
 351 
 352         case SINGLEFRAMEDONE:
 353         case STATICIMAGEDONE:
 354             if (width <= 0 || height <= 0) break;
 355             if (model instanceof DirectColorModel) {
 356                 if (intPixels == null) break;
 357                 wr = createDCMraster();
 358             }
 359             else if (model instanceof IndexColorModel) {
 360                 int[] bandOffsets = {0};
 361                 if (bytePixels == null) break;
 362                 DataBufferByte db = new DataBufferByte(bytePixels,
 363                                                        width*height);
 364                 wr = Raster.createInterleavedRaster(db, width, height, width,
 365                                                     1, bandOffsets, null);
 366             }
 367             else {
 368                 convertToRGB();
 369                 if (intPixels == null) break;
 370                 wr = createDCMraster();
 371             }
 372             BufferedImage bi = new BufferedImage(model, wr,
 373                                                  model.isAlphaPremultiplied(),
 374                                                  null);
 375             bi = bufferedImageOp.filter(bi, null);
 376             WritableRaster r = bi.getRaster();
 377             ColorModel cm = bi.getColorModel();
 378             int w = r.getWidth();
 379             int h = r.getHeight();
 380             consumer.setDimensions(w, h);
 381             consumer.setColorModel(cm);
 382             if (cm instanceof DirectColorModel) {
 383                 DataBufferInt db = (DataBufferInt) r.getDataBuffer();
 384                 consumer.setPixels(0, 0, w, h,
 385                                    cm, db.getData(), 0, w);
 386             }
 387             else if (cm instanceof IndexColorModel) {
 388                 DataBufferByte db = (DataBufferByte) r.getDataBuffer();
 389                 consumer.setPixels(0, 0, w, h,
 390                                    cm, db.getData(), 0, w);
 391             }
 392             else {
 393                 throw new InternalError("Unknown color model "+cm);
 394             }
 395             break;
 396         }
 397         consumer.imageComplete(status);
 398     }
 399 
 400     private WritableRaster createDCMraster() {
 401         WritableRaster wr;
 402         DirectColorModel dcm = (DirectColorModel) model;
 403         boolean hasAlpha = model.hasAlpha();
 404         int[] bandMasks = new int[3+(hasAlpha ? 1 : 0)];
 405         bandMasks[0] = dcm.getRedMask();
 406         bandMasks[1] = dcm.getGreenMask();
 407         bandMasks[2] = dcm.getBlueMask();
 408         if (hasAlpha) {
 409             bandMasks[3] = dcm.getAlphaMask();
 410         }
 411         DataBufferInt db = new DataBufferInt(intPixels, width*height);
 412         wr = Raster.createPackedRaster(db, width, height, width,
 413                                        bandMasks, null);
 414         return wr;
 415     }
 416 
 417 }