1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one
3 *  or more contributor license agreements.  See the NOTICE file
4 *  distributed with this work for additional information
5 *  regarding copyright ownership.  The ASF licenses this file
6 *  to you under the Apache License, Version 2.0 (the
7 *  "License"); you may not use this file except in compliance
8 *  with the License.  You may obtain a copy of the License at
9 *
10 *    http://www.apache.org/licenses/LICENSE-2.0
11 *
12 *  Unless required by applicable law or agreed to in writing,
13 *  software distributed under the License is distributed on an
14 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 *  KIND, either express or implied.  See the License for the
16 *  specific language governing permissions and limitations
17 *  under the License.
18 */
19/**
20 * @author Oleg V. Khaschansky
21 * @version $Revision$
22 */
23
24package java.awt.image;
25
26import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
27import org.apache.harmony.awt.internal.nls.Messages;
28
29/**
30 * The BufferedImageFilter class provides filtering operations to the
31 * BufferedImage objects using operators which implement BufferedImageOp
32 * interface.
33 *
34 * @since Android 1.0
35 */
36public class BufferedImageFilter extends ImageFilter implements Cloneable {
37
38    /**
39     * The Constant accessor.
40     */
41    private static final AwtImageBackdoorAccessor accessor = AwtImageBackdoorAccessor.getInstance();
42
43    /**
44     * The op.
45     */
46    private BufferedImageOp op;
47
48    /**
49     * The raster.
50     */
51    private WritableRaster raster;
52
53    /**
54     * The i data.
55     */
56    private int iData[];
57
58    /**
59     * The b data.
60     */
61    private byte bData[];
62
63    /**
64     * The width.
65     */
66    private int width;
67
68    /**
69     * The height.
70     */
71    private int height;
72
73    /**
74     * The cm.
75     */
76    private ColorModel cm;
77
78    /**
79     * The forced rgb.
80     */
81    private boolean forcedRGB = false;
82
83    /**
84     * The transfer type.
85     */
86    private int transferType = DataBuffer.TYPE_UNDEFINED;
87
88    /**
89     * Instantiates a new BufferedImageFilter with the specified BufferedImageOp
90     * operator.
91     *
92     * @param op
93     *            the specified BufferedImageOp operator.
94     * @throws NullPointerException
95     *             if BufferedImageOp is null.
96     */
97    public BufferedImageFilter(BufferedImageOp op) {
98        if (op == null) {
99            throw new NullPointerException(Messages.getString("awt.05")); //$NON-NLS-1$
100        }
101        this.op = op;
102    }
103
104    /**
105     * Gets the BufferedImageOp operator associated with this
106     * BufferedImageFilter object.
107     *
108     * @return the BufferedImageOp associated with this BufferedImageFilter
109     *         object.
110     */
111    public BufferedImageOp getBufferedImageOp() {
112        return op;
113    }
114
115    @Override
116    public void setDimensions(int width, int height) {
117        this.width = width;
118        this.height = height;
119        // Stop image consuming if no pixels expected.
120        if (width <= 0 || height <= 0) {
121            consumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
122            reset();
123        }
124    }
125
126    @Override
127    public void setColorModel(ColorModel model) {
128        if (this.cm != null && this.cm != model && raster != null) {
129            forceRGB();
130        } else {
131            this.cm = model;
132        }
133    }
134
135    @Override
136    public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off,
137            int scansize) {
138        setPixels(x, y, w, h, model, pixels, off, scansize, true);
139    }
140
141    @Override
142    public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off,
143            int scansize) {
144        setPixels(x, y, w, h, model, pixels, off, scansize, false);
145    }
146
147    @Override
148    public void imageComplete(int status) {
149        if (status == STATICIMAGEDONE || status == SINGLEFRAMEDONE) {
150            BufferedImage bim = new BufferedImage(cm, raster, cm.isAlphaPremultiplied, null);
151            bim = op.filter(bim, null);
152            DataBuffer dstDb = bim.getRaster().getDataBuffer();
153            ColorModel dstCm = bim.getColorModel();
154            int dstW = bim.getWidth();
155            int dstH = bim.getHeight();
156
157            consumer.setDimensions(dstW, dstH);
158
159            if (dstDb.getDataType() == DataBuffer.TYPE_INT) {
160                consumer.setColorModel(dstCm);
161                consumer.setPixels(0, 0, dstW, dstH, dstCm, accessor.getDataInt(dstDb), 0, dstW);
162            } else if (dstDb.getDataType() == DataBuffer.TYPE_BYTE) {
163                consumer.setColorModel(dstCm);
164                consumer.setPixels(0, 0, dstW, dstH, dstCm, accessor.getDataByte(dstDb), 0, dstW);
165            } else {
166                int dstData[] = bim.getRGB(0, 0, dstW, dstH, null, 0, dstW);
167                dstCm = ColorModel.getRGBdefault();
168                consumer.setColorModel(dstCm);
169                consumer.setPixels(0, 0, dstW, dstH, dstCm, dstData, 0, dstW);
170            }
171        } else if (status == IMAGEERROR || status == IMAGEABORTED) {
172            reset();
173        }
174
175        consumer.imageComplete(status);
176    }
177
178    /**
179     * Sets the pixels.
180     *
181     * @param x
182     *            the x.
183     * @param y
184     *            the y.
185     * @param w
186     *            the w.
187     * @param h
188     *            the h.
189     * @param model
190     *            the model.
191     * @param pixels
192     *            the pixels.
193     * @param off
194     *            the off.
195     * @param scansize
196     *            the scansize.
197     * @param isByteData
198     *            the is byte data.
199     */
200    private void setPixels(int x, int y, int w, int h, ColorModel model, Object pixels, int off,
201            int scansize, boolean isByteData) {
202        // Check bounds
203        // Need to copy only the pixels that will fit into the destination area
204        if (x < 0) {
205            w -= x;
206            off += x;
207            x = 0;
208        }
209
210        if (y < 0) {
211            h -= y;
212            off += y * scansize;
213            y = 0;
214        }
215
216        if (x + w > width) {
217            w = width - x;
218        }
219
220        if (y + h > height) {
221            h = height - y;
222        }
223
224        if (w <= 0 || h <= 0) {
225            return;
226        }
227
228        // Check model
229        if (this.cm == null) {
230            setColorModel(model);
231        } else if (model == null) {
232            model = this.cm;
233        } else if (!model.equals(this.cm)) {
234            forceRGB();
235        }
236
237        boolean canArraycopy;
238        // Process pixels
239        switch (transferType) {
240            case DataBuffer.TYPE_UNDEFINED: {
241                if (isByteData) {
242                    transferType = DataBuffer.TYPE_BYTE;
243                    createRaster(transferType);
244                    // bData = new byte[width*height];
245                    canArraycopy = !forcedRGB;
246                    break;
247                }
248                transferType = DataBuffer.TYPE_INT;
249                createRaster(transferType);
250                // iData = new int[width*height];
251                canArraycopy = !forcedRGB || model.equals(ColorModel.getRGBdefault());
252                break;
253            } // And proceed to copy the pixels
254            case DataBuffer.TYPE_INT: {
255                if (isByteData) { // There are int data already but the new data
256                    // are bytes
257                    forceRGB();
258                    canArraycopy = false;
259                    break;
260                } else if (!forcedRGB || model.equals(ColorModel.getRGBdefault())) {
261                    canArraycopy = true;
262                    break;
263                } // Else fallback to the RGB conversion
264            }
265            case DataBuffer.TYPE_BYTE: {
266                if (isByteData && !forcedRGB) {
267                    canArraycopy = true;
268                    break;
269                }
270
271                // RGB conversion
272                canArraycopy = false;
273                break;
274            }
275            default: {
276                throw new IllegalStateException(Messages.getString("awt.06")); //$NON-NLS-1$
277            }
278        }
279
280        off += x;
281        int maxOffset = off + h * scansize;
282        int dstOffset = x + y * width;
283
284        if (canArraycopy) {
285            Object dstArray = isByteData ? (Object)bData : (Object)iData;
286            for (; off < maxOffset; off += scansize, dstOffset += width) {
287                System.arraycopy(pixels, off, dstArray, dstOffset, w);
288            }
289        } else {
290            // RGB conversion
291            for (; off < maxOffset; off += scansize, dstOffset += width) {
292                int srcPos = off;
293                int dstPos = dstOffset;
294                int maxDstPos = dstOffset + w;
295                for (; dstPos < maxDstPos; dstPos++, srcPos++) {
296                    iData[dstPos] = model.getRGB(isByteData ? ((byte[])pixels)[srcPos]
297                            : ((int[])pixels)[srcPos]);
298                }
299            }
300        }
301    }
302
303    /**
304     * Force rgb.
305     */
306    private void forceRGB() {
307        if (!forcedRGB) {
308            forcedRGB = true;
309            int size = width * height;
310            int rgbData[] = new int[size];
311
312            if (bData != null) {
313                for (int i = 0; i < size; i++) {
314                    rgbData[i] = cm.getRGB(bData[i]);
315                }
316            } else if (iData != null) {
317                for (int i = 0; i < size; i++) {
318                    rgbData[i] = cm.getRGB(iData[i]);
319                }
320            }
321
322            cm = ColorModel.getRGBdefault();
323            DataBufferInt db = new DataBufferInt(rgbData, size);
324            int masks[] = new int[] {
325                    0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000
326            };
327            raster = Raster.createPackedRaster(db, width, height, width, masks, null);
328            iData = accessor.getDataInt(db);
329            bData = null;
330            transferType = DataBuffer.TYPE_INT;
331        }
332    }
333
334    /**
335     * Reset.
336     */
337    private void reset() {
338        width = 0;
339        height = 0;
340        forcedRGB = false;
341        cm = null;
342        iData = null;
343        bData = null;
344        transferType = DataBuffer.TYPE_UNDEFINED;
345        raster = null;
346    }
347
348    /**
349     * Creates the raster.
350     *
351     * @param dataType
352     *            the data type.
353     */
354    private void createRaster(int dataType) {
355        boolean createdValidBuffer = false;
356        try {
357            raster = cm.createCompatibleWritableRaster(width, height);
358            int rasterType = raster.getDataBuffer().getDataType();
359            if (rasterType == dataType) {
360                switch (rasterType) {
361                    case DataBuffer.TYPE_INT: {
362                        iData = accessor.getDataInt(raster.getDataBuffer());
363                        if (iData != null) {
364                            createdValidBuffer = true;
365                        }
366                        break;
367                    }
368                    case DataBuffer.TYPE_BYTE: {
369                        bData = accessor.getDataByte(raster.getDataBuffer());
370                        if (bData != null) {
371                            createdValidBuffer = true;
372                        }
373                        break;
374                    }
375                    default:
376                        createdValidBuffer = false;
377                }
378
379                if (cm == ColorModel.getRGBdefault()) {
380                    forcedRGB = true;
381                }
382            } else {
383                createdValidBuffer = false;
384            }
385        } catch (Exception e) {
386            createdValidBuffer = false;
387        }
388
389        if (createdValidBuffer == false) {
390            cm = ColorModel.getRGBdefault();
391            raster = cm.createCompatibleWritableRaster(width, height);
392            iData = accessor.getDataInt(raster.getDataBuffer());
393            bData = null;
394            forcedRGB = true;
395        }
396    }
397}
398