1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Oleg V. Khaschansky
19 * @version $Revision$
20 *
21 * @date: Sep 29, 2005
22 */
23
24package java.awt.image;
25
26import java.awt.*;
27import java.awt.geom.Point2D;
28import java.awt.geom.Rectangle2D;
29import java.util.Arrays;
30
31import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
32import org.apache.harmony.awt.internal.nls.Messages;
33
34/**
35 * The ConvolveOp class convolves from the source data to the destination using
36 * a convolution kernel. Each output pixel is represented as the result of
37 * multiplying the kernel and the surround of the input pixel.
38 *
39 * @since Android 1.0
40 */
41public class ConvolveOp implements BufferedImageOp, RasterOp {
42
43    /**
44     * The Constant EDGE_ZERO_FILL indicates that pixels at the edge of the
45     * destination image are set to zero.
46     */
47    public static final int EDGE_ZERO_FILL = 0;
48
49    /**
50     * The Constant EDGE_NO_OP indicates that pixels at the edge of the source
51     * image are converted to the edge pixels in the destination without
52     * modification.
53     */
54    public static final int EDGE_NO_OP = 1;
55
56    /**
57     * The kernel.
58     */
59    private Kernel kernel;
60
61    /**
62     * The edge cond.
63     */
64    private int edgeCond;
65
66    /**
67     * The rhs.
68     */
69    private RenderingHints rhs = null;
70
71    static {
72        // TODO
73        // System.loadLibrary("imageops");
74    }
75
76    /**
77     * Instantiates a new ConvolveOp object with the specified Kernel and
78     * specified edges condition.
79     *
80     * @param kernel
81     *            the specified Kernel.
82     * @param edgeCondition
83     *            the specified edge condition.
84     * @param hints
85     *            the RenderingHints object, or null.
86     */
87    public ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints) {
88        this.kernel = kernel;
89        this.edgeCond = edgeCondition;
90        this.rhs = hints;
91    }
92
93    /**
94     * Instantiates a new ConvolveOp object with the specified Kernel and
95     * EDGE_ZERO_FILL edge condition.
96     *
97     * @param kernel
98     *            the specified Kernel.
99     */
100    public ConvolveOp(Kernel kernel) {
101        this.kernel = kernel;
102        this.edgeCond = EDGE_ZERO_FILL;
103    }
104
105    /**
106     * Gets the Kernel object of this ConvolveOp.
107     *
108     * @return the Kernel object of this ConvolveOp.
109     */
110    public final Kernel getKernel() {
111        return (Kernel)kernel.clone();
112    }
113
114    public final RenderingHints getRenderingHints() {
115        return rhs;
116    }
117
118    /**
119     * Gets the edge condition of this ConvolveOp.
120     *
121     * @return the edge condition: EDGE_NO_OP or EDGE_ZERO_FILL.
122     */
123    public int getEdgeCondition() {
124        return edgeCond;
125    }
126
127    public final Rectangle2D getBounds2D(Raster src) {
128        return src.getBounds();
129    }
130
131    public final Rectangle2D getBounds2D(BufferedImage src) {
132        return getBounds2D(src.getRaster());
133    }
134
135    public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
136        if (dstPt == null) {
137            dstPt = new Point2D.Float();
138        }
139
140        dstPt.setLocation(srcPt);
141        return dstPt;
142    }
143
144    public WritableRaster createCompatibleDestRaster(Raster src) {
145        return src.createCompatibleWritableRaster();
146    }
147
148    public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
149        if (dstCM == null) {
150            dstCM = src.getColorModel();
151        }
152
153        if (dstCM instanceof IndexColorModel) {
154            dstCM = ColorModel.getRGBdefault();
155        }
156
157        WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster()
158                .createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM
159                .createCompatibleWritableRaster(src.getWidth(), src.getHeight());
160
161        return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null);
162    }
163
164    public final WritableRaster filter(Raster src, WritableRaster dst) {
165        if (src == null) { // Should throw according to spec
166            // awt.256=Source raster is null
167            throw new NullPointerException(Messages.getString("awt.256")); //$NON-NLS-1$
168        }
169
170        if (src == dst) {
171            // awt.257=Source raster is equal to destination
172            throw new IllegalArgumentException(Messages.getString("awt.257")); //$NON-NLS-1$
173        }
174
175        if (dst == null) {
176            dst = createCompatibleDestRaster(src);
177        } else if (src.getNumBands() != dst.getNumBands()) {
178            // awt.258=Number of source bands ({0}) is not equal to number of
179            // destination bands ({1})
180            throw new IllegalArgumentException(Messages.getString(
181                    "awt.258", src.getNumBands(), dst.getNumBands())); //$NON-NLS-1$
182        }
183
184        // TODO
185        // if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM) != 0)
186        if (slowFilter(src, dst) != 0) {
187            // awt.21F=Unable to transform source
188            throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
189        }
190
191        return dst;
192    }
193
194    /**
195     * Slow filter.
196     *
197     * @param src
198     *            the src.
199     * @param dst
200     *            the dst.
201     * @return the int.
202     */
203    private int slowFilter(Raster src, WritableRaster dst) {
204        try {
205            SampleModel sm = src.getSampleModel();
206
207            int numBands = src.getNumBands();
208            int srcHeight = src.getHeight();
209            int srcWidth = src.getWidth();
210
211            int xOrigin = kernel.getXOrigin();
212            int yOrigin = kernel.getYOrigin();
213            int kWidth = kernel.getWidth();
214            int kHeight = kernel.getHeight();
215            float[] data = kernel.getKernelData(null);
216
217            int srcMinX = src.getMinX();
218            int srcMinY = src.getMinY();
219            int dstMinX = dst.getMinX();
220            int dstMinY = dst.getMinY();
221
222            int srcConvMaxX = srcWidth - (kWidth - xOrigin - 1);
223            int srcConvMaxY = srcHeight - (kHeight - yOrigin - 1);
224
225            int[] maxValues = new int[numBands];
226            int[] masks = new int[numBands];
227            int[] sampleSizes = sm.getSampleSize();
228
229            for (int i = 0; i < numBands; i++) {
230                maxValues[i] = (1 << sampleSizes[i]) - 1;
231                masks[i] = ~(maxValues[i]);
232            }
233
234            // Processing bounds
235            float[] pixels = null;
236            pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels);
237            float[] newPixels = new float[pixels.length];
238            int rowLength = srcWidth * numBands;
239            if (this.edgeCond == ConvolveOp.EDGE_NO_OP) {
240                // top
241                int start = 0;
242                int length = yOrigin * rowLength;
243                System.arraycopy(pixels, start, newPixels, start, length);
244                // bottom
245                start = (srcHeight - (kHeight - yOrigin - 1)) * rowLength;
246                length = (kHeight - yOrigin - 1) * rowLength;
247                System.arraycopy(pixels, start, newPixels, start, length);
248                // middle
249                length = xOrigin * numBands;
250                int length1 = (kWidth - xOrigin - 1) * numBands;
251                start = yOrigin * rowLength;
252                int start1 = (yOrigin + 1) * rowLength - length1;
253                for (int i = yOrigin; i < (srcHeight - (kHeight - yOrigin - 1)); i++) {
254                    System.arraycopy(pixels, start, newPixels, start, length);
255                    System.arraycopy(pixels, start1, newPixels, start1, length1);
256                    start += rowLength;
257                    start1 += rowLength;
258                }
259
260            }
261
262            // Cycle over pixels to be calculated
263            for (int i = yOrigin; i < srcConvMaxY; i++) {
264                for (int j = xOrigin; j < srcConvMaxX; j++) {
265
266                    // Take kernel data in backward direction, convolution
267                    int kernelIdx = data.length - 1;
268
269                    int pixelIndex = i * rowLength + j * numBands;
270                    for (int hIdx = 0, rasterHIdx = i - yOrigin; hIdx < kHeight; hIdx++, rasterHIdx++) {
271                        for (int wIdx = 0, rasterWIdx = j - xOrigin; wIdx < kWidth; wIdx++, rasterWIdx++) {
272                            int curIndex = rasterHIdx * rowLength + rasterWIdx * numBands;
273                            for (int idx = 0; idx < numBands; idx++) {
274                                newPixels[pixelIndex + idx] += data[kernelIdx]
275                                        * pixels[curIndex + idx];
276                            }
277                            kernelIdx--;
278                        }
279                    }
280
281                    // Check for overflow now
282                    for (int idx = 0; idx < numBands; idx++) {
283                        if (((int)newPixels[pixelIndex + idx] & masks[idx]) != 0) {
284                            if (newPixels[pixelIndex + idx] < 0) {
285                                newPixels[pixelIndex + idx] = 0;
286                            } else {
287                                newPixels[pixelIndex + idx] = maxValues[idx];
288                            }
289                        }
290                    }
291                }
292            }
293
294            dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, newPixels);
295        } catch (Exception e) { // Something goes wrong, signal error
296            return 1;
297        }
298        return 0;
299    }
300
301    public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
302        if (src == null) {
303            // awt.259=Source image is null
304            throw new NullPointerException(Messages.getString("awt.259")); //$NON-NLS-1$
305        }
306
307        if (src == dst) {
308            // awt.25A=Source equals to destination
309            throw new IllegalArgumentException(Messages.getString("awt.25A")); //$NON-NLS-1$
310        }
311
312        ColorModel srcCM = src.getColorModel();
313        BufferedImage finalDst = null;
314
315        if (srcCM instanceof IndexColorModel) {
316            src = ((IndexColorModel)srcCM).convertToIntDiscrete(src.getRaster(), true);
317            srcCM = src.getColorModel();
318        }
319
320        if (dst == null) {
321            dst = createCompatibleDestImage(src, srcCM);
322        } else {
323            if (!srcCM.equals(dst.getColorModel())) {
324                // Treat BufferedImage.TYPE_INT_RGB and
325                // BufferedImage.TYPE_INT_ARGB as same
326                if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst
327                        .getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) {
328                    finalDst = dst;
329                    dst = createCompatibleDestImage(src, srcCM);
330                }
331            }
332        }
333
334        // Skip alpha channel for TYPE_INT_RGB images
335        // TODO
336        // if (ippFilter(src.getRaster(), dst.getRaster(), src.getType()) != 0)
337        if (slowFilter(src.getRaster(), dst.getRaster()) != 0) {
338            // awt.21F=Unable to transform source
339            throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
340        }
341
342        if (finalDst != null) {
343            Graphics2D g = finalDst.createGraphics();
344            g.setComposite(AlphaComposite.Src);
345            g.drawImage(dst, 0, 0, null);
346        } else {
347            finalDst = dst;
348        }
349
350        return finalDst;
351    }
352
353    // TODO remove when this method is used
354    /**
355     * Ipp filter.
356     *
357     * @param src
358     *            the src.
359     * @param dst
360     *            the dst.
361     * @param imageType
362     *            the image type.
363     * @return the int.
364     */
365    @SuppressWarnings("unused")
366    private int ippFilter(Raster src, WritableRaster dst, int imageType) {
367        int srcStride, dstStride;
368        boolean skipChannel = false;
369        int channels;
370        int offsets[] = null;
371
372        switch (imageType) {
373            case BufferedImage.TYPE_INT_RGB:
374            case BufferedImage.TYPE_INT_BGR: {
375                channels = 4;
376                srcStride = src.getWidth() * 4;
377                dstStride = dst.getWidth() * 4;
378                skipChannel = true;
379                break;
380            }
381
382            case BufferedImage.TYPE_INT_ARGB:
383            case BufferedImage.TYPE_INT_ARGB_PRE:
384            case BufferedImage.TYPE_4BYTE_ABGR:
385            case BufferedImage.TYPE_4BYTE_ABGR_PRE: {
386                channels = 4;
387                srcStride = src.getWidth() * 4;
388                dstStride = dst.getWidth() * 4;
389                break;
390            }
391
392            case BufferedImage.TYPE_BYTE_GRAY: {
393                channels = 1;
394                srcStride = src.getWidth();
395                dstStride = dst.getWidth();
396                break;
397            }
398
399            case BufferedImage.TYPE_3BYTE_BGR: {
400                channels = 3;
401                srcStride = src.getWidth() * 3;
402                dstStride = dst.getWidth() * 3;
403                break;
404            }
405
406            case BufferedImage.TYPE_USHORT_GRAY: // TODO - could be done in
407                // native code?
408            case BufferedImage.TYPE_USHORT_565_RGB:
409            case BufferedImage.TYPE_USHORT_555_RGB:
410            case BufferedImage.TYPE_BYTE_BINARY: {
411                return slowFilter(src, dst);
412            }
413
414            default: {
415                SampleModel srcSM = src.getSampleModel();
416                SampleModel dstSM = dst.getSampleModel();
417
418                if (srcSM instanceof PixelInterleavedSampleModel
419                        && dstSM instanceof PixelInterleavedSampleModel) {
420                    // Check PixelInterleavedSampleModel
421                    if (srcSM.getDataType() != DataBuffer.TYPE_BYTE
422                            || dstSM.getDataType() != DataBuffer.TYPE_BYTE) {
423                        return slowFilter(src, dst);
424                    }
425
426                    channels = srcSM.getNumBands(); // Have IPP functions for 1,
427                    // 3 and 4 channels
428                    if (!(channels == 1 || channels == 3 || channels == 4)) {
429                        return slowFilter(src, dst);
430                    }
431
432                    srcStride = ((ComponentSampleModel)srcSM).getScanlineStride();
433                    dstStride = ((ComponentSampleModel)dstSM).getScanlineStride();
434                } else if (srcSM instanceof SinglePixelPackedSampleModel
435                        && dstSM instanceof SinglePixelPackedSampleModel) {
436                    // Check SinglePixelPackedSampleModel
437                    SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM;
438                    SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM;
439
440                    channels = sppsm1.getNumBands();
441
442                    // TYPE_INT_RGB, TYPE_INT_ARGB...
443                    if (sppsm1.getDataType() != DataBuffer.TYPE_INT
444                            || sppsm2.getDataType() != DataBuffer.TYPE_INT
445                            || !(channels == 3 || channels == 4)) {
446                        return slowFilter(src, dst);
447                    }
448
449                    // Check compatibility of sample models
450                    if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets())
451                            || !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) {
452                        return slowFilter(src, dst);
453                    }
454
455                    for (int i = 0; i < channels; i++) {
456                        if (sppsm1.getSampleSize(i) != 8) {
457                            return slowFilter(src, dst);
458                        }
459                    }
460
461                    if (channels == 3) { // Cannot skip channel, don't know
462                        // which is alpha...
463                        channels = 4;
464                    }
465
466                    srcStride = sppsm1.getScanlineStride() * 4;
467                    dstStride = sppsm2.getScanlineStride() * 4;
468                } else {
469                    return slowFilter(src, dst);
470                }
471
472                // Fill offsets if there's a child raster
473                if (src.getParent() != null || dst.getParent() != null) {
474                    if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0
475                            || dst.getSampleModelTranslateX() != 0
476                            || dst.getSampleModelTranslateY() != 0) {
477                        offsets = new int[4];
478                        offsets[0] = -src.getSampleModelTranslateX() + src.getMinX();
479                        offsets[1] = -src.getSampleModelTranslateY() + src.getMinY();
480                        offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX();
481                        offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY();
482                    }
483                }
484            }
485        }
486
487        Object srcData, dstData;
488        AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
489        try {
490            srcData = dbAccess.getData(src.getDataBuffer());
491            dstData = dbAccess.getData(dst.getDataBuffer());
492        } catch (IllegalArgumentException e) {
493            return -1; // Unknown data buffer type
494        }
495
496        return ippFilter32f(kernel.data, kernel.getWidth(), kernel.getHeight(),
497                kernel.getXOrigin(), kernel.getYOrigin(), edgeCond, srcData, src.getWidth(), src
498                        .getHeight(), srcStride, dstData, dst.getWidth(), dst.getHeight(),
499                dstStride, channels, skipChannel, offsets);
500    }
501
502    /**
503     * Ipp filter32f.
504     *
505     * @param kernel
506     *            the kernel.
507     * @param kWidth
508     *            the k width.
509     * @param kHeight
510     *            the k height.
511     * @param anchorX
512     *            the anchor x.
513     * @param anchorY
514     *            the anchor y.
515     * @param borderType
516     *            the border type.
517     * @param src
518     *            the src.
519     * @param srcWidth
520     *            the src width.
521     * @param srcHeight
522     *            the src height.
523     * @param srcStride
524     *            the src stride.
525     * @param dst
526     *            the dst.
527     * @param dstWidth
528     *            the dst width.
529     * @param dstHeight
530     *            the dst height.
531     * @param dstStride
532     *            the dst stride.
533     * @param channels
534     *            the channels.
535     * @param skipChannel
536     *            the skip channel.
537     * @param offsets
538     *            the offsets.
539     * @return the int.
540     */
541    private native int ippFilter32f(float kernel[], int kWidth, int kHeight, int anchorX,
542            int anchorY, int borderType, Object src, int srcWidth, int srcHeight, int srcStride,
543            Object dst, int dstWidth, int dstHeight, int dstStride, int channels,
544            boolean skipChannel, int offsets[]);
545}
546