1/*
2 * Copyright (c) 1997, 2014, 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
26package java.awt.image;
27
28import java.util.Hashtable;
29import java.awt.image.ImageConsumer;
30import 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
46public 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 final 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}
418