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 20, 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 BandCombineOp class translates coordinates from coordinates in the source
36 * Raster to coordinates in the destination Raster by an arbitrary linear
37 * combination of the bands in a source Raster, using a specified matrix. The
38 * number of bands in the matrix should equal to the number of bands in the
39 * source Raster plus 1.
40 *
41 * @since Android 1.0
42 */
43public class BandCombineOp implements RasterOp {
44
45    /**
46     * The Constant offsets3c.
47     */
48    static final int offsets3c[] = {
49            16, 8, 0
50    };
51
52    /**
53     * The Constant offsets4ac.
54     */
55    static final int offsets4ac[] = {
56            16, 8, 0, 24
57    };
58
59    /**
60     * The Constant masks3c.
61     */
62    static final int masks3c[] = {
63            0xFF0000, 0xFF00, 0xFF
64    };
65
66    /**
67     * The Constant masks4ac.
68     */
69    static final int masks4ac[] = {
70            0xFF0000, 0xFF00, 0xFF, 0xFF000000
71    };
72
73    /**
74     * The Constant piOffsets.
75     */
76    private static final int piOffsets[] = {
77            0, 1, 2
78    };
79
80    /**
81     * The Constant piInvOffsets.
82     */
83    private static final int piInvOffsets[] = {
84            2, 1, 0
85    };
86
87    /**
88     * The Constant TYPE_BYTE3C.
89     */
90    private static final int TYPE_BYTE3C = 0;
91
92    /**
93     * The Constant TYPE_BYTE4AC.
94     */
95    private static final int TYPE_BYTE4AC = 1;
96
97    /**
98     * The Constant TYPE_USHORT3C.
99     */
100    private static final int TYPE_USHORT3C = 2;
101
102    /**
103     * The Constant TYPE_SHORT3C.
104     */
105    private static final int TYPE_SHORT3C = 3;
106
107    /**
108     * The mx width.
109     */
110    private int mxWidth;
111
112    /**
113     * The mx height.
114     */
115    private int mxHeight;
116
117    /**
118     * The matrix.
119     */
120    private float matrix[][];
121
122    /**
123     * The r hints.
124     */
125    private RenderingHints rHints;
126
127    static {
128        // XXX - todo
129        // System.loadLibrary("imageops");
130    }
131
132    /**
133     * Instantiates a new BandCombineOp object with the specified matrix.
134     *
135     * @param matrix
136     *            the specified matrix for band combining.
137     * @param hints
138     *            the RenderingHints.
139     */
140    public BandCombineOp(float matrix[][], RenderingHints hints) {
141        this.mxHeight = matrix.length;
142        this.mxWidth = matrix[0].length;
143        this.matrix = new float[mxHeight][mxWidth];
144
145        for (int i = 0; i < mxHeight; i++) {
146            System.arraycopy(matrix[i], 0, this.matrix[i], 0, mxWidth);
147        }
148
149        this.rHints = hints;
150    }
151
152    public final RenderingHints getRenderingHints() {
153        return this.rHints;
154    }
155
156    /**
157     * Gets the matrix associated with this BandCombineOp object.
158     *
159     * @return the matrix associated with this BandCombineOp object.
160     */
161    public final float[][] getMatrix() {
162        float res[][] = new float[mxHeight][mxWidth];
163
164        for (int i = 0; i < mxHeight; i++) {
165            System.arraycopy(matrix[i], 0, res[i], 0, mxWidth);
166        }
167
168        return res;
169    }
170
171    public final Point2D getPoint2D(Point2D srcPoint, Point2D dstPoint) {
172        if (dstPoint == null) {
173            dstPoint = new Point2D.Float();
174        }
175
176        dstPoint.setLocation(srcPoint);
177        return dstPoint;
178    }
179
180    public final Rectangle2D getBounds2D(Raster src) {
181        return src.getBounds();
182    }
183
184    public WritableRaster createCompatibleDestRaster(Raster src) {
185        int numBands = src.getNumBands();
186        if (mxWidth != numBands && mxWidth != (numBands + 1) || numBands != mxHeight) {
187            // awt.254=Number of bands in the source raster ({0}) is
188            // incompatible with the matrix [{1}x{2}]
189            throw new IllegalArgumentException(Messages.getString("awt.254", //$NON-NLS-1$
190                    new Object[] {
191                            numBands, mxWidth, mxHeight
192                    }));
193        }
194
195        return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
196    }
197
198    public WritableRaster filter(Raster src, WritableRaster dst) {
199        int numBands = src.getNumBands();
200
201        if (mxWidth != numBands && mxWidth != (numBands + 1)) {
202            // awt.254=Number of bands in the source raster ({0}) is
203            // incompatible with the matrix [{1}x{2}]
204            throw new IllegalArgumentException(Messages.getString("awt.254", //$NON-NLS-1$
205                    new Object[] {
206                            numBands, mxWidth, mxHeight
207                    }));
208        }
209
210        if (dst == null) {
211            dst = createCompatibleDestRaster(src);
212        } else if (dst.getNumBands() != mxHeight) {
213            // awt.255=Number of bands in the destination raster ({0}) is
214            // incompatible with the matrix [{1}x{2}]
215            throw new IllegalArgumentException(Messages.getString("awt.255", //$NON-NLS-1$
216                    new Object[] {
217                            dst.getNumBands(), mxWidth, mxHeight
218                    }));
219        }
220
221        // XXX - todo
222        // if (ippFilter(src, dst) != 0)
223        if (verySlowFilter(src, dst) != 0) {
224            // awt.21F=Unable to transform source
225            throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
226        }
227
228        return dst;
229    }
230
231    /**
232     * The Class SampleModelInfo.
233     */
234    private static final class SampleModelInfo {
235
236        /**
237         * The channels.
238         */
239        int channels;
240
241        /**
242         * The channels order.
243         */
244        int channelsOrder[];
245
246        /**
247         * The stride.
248         */
249        int stride;
250    }
251
252    /**
253     * Check sample model.
254     *
255     * @param sm
256     *            the sm.
257     * @return the sample model info.
258     */
259    private final SampleModelInfo checkSampleModel(SampleModel sm) {
260        SampleModelInfo ret = new SampleModelInfo();
261
262        if (sm instanceof PixelInterleavedSampleModel) {
263            // Check PixelInterleavedSampleModel
264            if (sm.getDataType() != DataBuffer.TYPE_BYTE) {
265                return null;
266            }
267
268            ret.channels = sm.getNumBands();
269            ret.stride = ((ComponentSampleModel)sm).getScanlineStride();
270            ret.channelsOrder = ((ComponentSampleModel)sm).getBandOffsets();
271
272        } else if (sm instanceof SinglePixelPackedSampleModel) {
273            // Check SinglePixelPackedSampleModel
274            SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)sm;
275
276            ret.channels = sppsm1.getNumBands();
277            if (sppsm1.getDataType() != DataBuffer.TYPE_INT) {
278                return null;
279            }
280
281            // Check sample models
282            for (int i = 0; i < ret.channels; i++) {
283                if (sppsm1.getSampleSize(i) != 8) {
284                    return null;
285                }
286            }
287
288            ret.channelsOrder = new int[ret.channels];
289            int bitOffsets[] = sppsm1.getBitOffsets();
290            for (int i = 0; i < ret.channels; i++) {
291                if (bitOffsets[i] % 8 != 0) {
292                    return null;
293                }
294
295                ret.channelsOrder[i] = bitOffsets[i] / 8;
296            }
297
298            ret.channels = 4;
299            ret.stride = sppsm1.getScanlineStride() * 4;
300        } else {
301            return null;
302        }
303
304        return ret;
305    }
306
307    /**
308     * Slow filter.
309     *
310     * @param src
311     *            the src.
312     * @param dst
313     *            the dst.
314     * @return the int.
315     */
316    private final int slowFilter(Raster src, WritableRaster dst) {
317        int res = 0;
318
319        SampleModelInfo srcInfo, dstInfo;
320        int offsets[] = null;
321
322        srcInfo = checkSampleModel(src.getSampleModel());
323        dstInfo = checkSampleModel(dst.getSampleModel());
324        if (srcInfo == null || dstInfo == null) {
325            return verySlowFilter(src, dst);
326        }
327
328        // Fill offsets if there's a child raster
329        if (src.getParent() != null || dst.getParent() != null) {
330            if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0
331                    || dst.getSampleModelTranslateX() != 0 || dst.getSampleModelTranslateY() != 0) {
332                offsets = new int[4];
333                offsets[0] = -src.getSampleModelTranslateX() + src.getMinX();
334                offsets[1] = -src.getSampleModelTranslateY() + src.getMinY();
335                offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX();
336                offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY();
337            }
338        }
339
340        int rmxWidth = (srcInfo.channels + 1); // width of the reordered matrix
341        float reorderedMatrix[] = new float[rmxWidth * dstInfo.channels];
342        for (int j = 0; j < dstInfo.channels; j++) {
343            if (j >= dstInfo.channelsOrder.length) {
344                continue;
345            }
346
347            for (int i = 0; i < srcInfo.channels; i++) {
348                if (i >= srcInfo.channelsOrder.length) {
349                    break;
350                }
351
352                reorderedMatrix[dstInfo.channelsOrder[j] * rmxWidth + srcInfo.channelsOrder[i]] = matrix[j][i];
353            }
354            if (mxWidth == rmxWidth) {
355                reorderedMatrix[(dstInfo.channelsOrder[j] + 1) * rmxWidth - 1] = matrix[j][mxWidth - 1];
356            }
357        }
358
359        Object srcData, dstData;
360        AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
361        try {
362            srcData = dbAccess.getData(src.getDataBuffer());
363            dstData = dbAccess.getData(dst.getDataBuffer());
364        } catch (IllegalArgumentException e) {
365            return -1; // Unknown data buffer type
366        }
367
368        simpleCombineBands(srcData, src.getWidth(), src.getHeight(), srcInfo.stride,
369                srcInfo.channels, dstData, dstInfo.stride, dstInfo.channels, reorderedMatrix,
370                offsets);
371
372        return res;
373    }
374
375    /**
376     * Very slow filter.
377     *
378     * @param src
379     *            the src.
380     * @param dst
381     *            the dst.
382     * @return the int.
383     */
384    private int verySlowFilter(Raster src, WritableRaster dst) {
385        int numBands = src.getNumBands();
386
387        int srcMinX = src.getMinX();
388        int srcY = src.getMinY();
389
390        int dstMinX = dst.getMinX();
391        int dstY = dst.getMinY();
392
393        int dX = src.getWidth();// < dst.getWidth() ? src.getWidth() :
394        // dst.getWidth();
395        int dY = src.getHeight();// < dst.getHeight() ? src.getHeight() :
396        // dst.getHeight();
397
398        float sample;
399        int srcPixels[] = new int[numBands * dX * dY];
400        int dstPixels[] = new int[mxHeight * dX * dY];
401
402        srcPixels = src.getPixels(srcMinX, srcY, dX, dY, srcPixels);
403
404        if (numBands == mxWidth) {
405            for (int i = 0, j = 0; i < srcPixels.length; i += numBands) {
406                for (int dstB = 0; dstB < mxHeight; dstB++) {
407                    sample = 0f;
408                    for (int srcB = 0; srcB < numBands; srcB++) {
409                        sample += matrix[dstB][srcB] * srcPixels[i + srcB];
410                    }
411                    dstPixels[j++] = (int)sample;
412                }
413            }
414        } else {
415            for (int i = 0, j = 0; i < srcPixels.length; i += numBands) {
416                for (int dstB = 0; dstB < mxHeight; dstB++) {
417                    sample = 0f;
418                    for (int srcB = 0; srcB < numBands; srcB++) {
419                        sample += matrix[dstB][srcB] * srcPixels[i + srcB];
420                    }
421                    dstPixels[j++] = (int)(sample + matrix[dstB][numBands]);
422                }
423            }
424        }
425
426        dst.setPixels(dstMinX, dstY, dX, dY, dstPixels);
427
428        return 0;
429    }
430
431    // TODO remove when method is used
432    /**
433     * Ipp filter.
434     *
435     * @param src
436     *            the src.
437     * @param dst
438     *            the dst.
439     * @return the int.
440     */
441    @SuppressWarnings("unused")
442    private int ippFilter(Raster src, WritableRaster dst) {
443        boolean invertChannels;
444        boolean inPlace = (src == dst);
445        int type;
446        int srcStride, dstStride;
447        int offsets[] = null;
448
449        int srcBands = src.getNumBands();
450        int dstBands = dst.getNumBands();
451
452        if (dstBands != 3
453                || (srcBands != 3 && !(srcBands == 4 && matrix[0][3] == 0 && matrix[1][3] == 0 && matrix[2][3] == 0))) {
454            return slowFilter(src, dst);
455        }
456
457        SampleModel srcSM = src.getSampleModel();
458        SampleModel dstSM = dst.getSampleModel();
459
460        if (srcSM instanceof SinglePixelPackedSampleModel
461                && dstSM instanceof SinglePixelPackedSampleModel) {
462            // Check SinglePixelPackedSampleModel
463            SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM;
464            SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM;
465
466            if (sppsm1.getDataType() != DataBuffer.TYPE_INT
467                    || sppsm2.getDataType() != DataBuffer.TYPE_INT) {
468                return slowFilter(src, dst);
469            }
470
471            // Check sample models
472            if (!Arrays.equals(sppsm2.getBitOffsets(), offsets3c)
473                    || !Arrays.equals(sppsm2.getBitMasks(), masks3c)) {
474                return slowFilter(src, dst);
475            }
476
477            if (srcBands == 3) {
478                if (!Arrays.equals(sppsm1.getBitOffsets(), offsets3c)
479                        || !Arrays.equals(sppsm1.getBitMasks(), masks3c)) {
480                    return slowFilter(src, dst);
481                }
482            } else if (srcBands == 4) {
483                if (!Arrays.equals(sppsm1.getBitOffsets(), offsets4ac)
484                        || !Arrays.equals(sppsm1.getBitMasks(), masks4ac)) {
485                    return slowFilter(src, dst);
486                }
487            }
488
489            type = TYPE_BYTE4AC;
490            invertChannels = true;
491
492            srcStride = sppsm1.getScanlineStride() * 4;
493            dstStride = sppsm2.getScanlineStride() * 4;
494        } else if (srcSM instanceof PixelInterleavedSampleModel
495                && dstSM instanceof PixelInterleavedSampleModel) {
496            if (srcBands != 3) {
497                return slowFilter(src, dst);
498            }
499
500            int srcDataType = srcSM.getDataType();
501
502            switch (srcDataType) {
503                case DataBuffer.TYPE_BYTE:
504                    type = TYPE_BYTE3C;
505                    break;
506                case DataBuffer.TYPE_USHORT:
507                    type = TYPE_USHORT3C;
508                    break;
509                case DataBuffer.TYPE_SHORT:
510                    type = TYPE_SHORT3C;
511                    break;
512                default:
513                    return slowFilter(src, dst);
514            }
515
516            // Check PixelInterleavedSampleModel
517            PixelInterleavedSampleModel pism1 = (PixelInterleavedSampleModel)srcSM;
518            PixelInterleavedSampleModel pism2 = (PixelInterleavedSampleModel)dstSM;
519
520            if (srcDataType != pism2.getDataType() || pism1.getPixelStride() != 3
521                    || pism2.getPixelStride() != 3
522                    || !Arrays.equals(pism1.getBandOffsets(), pism2.getBandOffsets())) {
523                return slowFilter(src, dst);
524            }
525
526            if (Arrays.equals(pism1.getBandOffsets(), piInvOffsets)) {
527                invertChannels = true;
528            } else if (Arrays.equals(pism1.getBandOffsets(), piOffsets)) {
529                invertChannels = false;
530            } else {
531                return slowFilter(src, dst);
532            }
533
534            int dataTypeSize = DataBuffer.getDataTypeSize(srcDataType) / 8;
535
536            srcStride = pism1.getScanlineStride() * dataTypeSize;
537            dstStride = pism2.getScanlineStride() * dataTypeSize;
538        } else { // XXX - todo - IPP allows support for planar data also
539            return slowFilter(src, dst);
540        }
541
542        // Fill offsets if there's a child raster
543        if (src.getParent() != null || dst.getParent() != null) {
544            if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0
545                    || dst.getSampleModelTranslateX() != 0 || dst.getSampleModelTranslateY() != 0) {
546                offsets = new int[4];
547                offsets[0] = -src.getSampleModelTranslateX() + src.getMinX();
548                offsets[1] = -src.getSampleModelTranslateY() + src.getMinY();
549                offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX();
550                offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY();
551            }
552        }
553
554        Object srcData, dstData;
555        AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
556        try {
557            srcData = dbAccess.getData(src.getDataBuffer());
558            dstData = dbAccess.getData(dst.getDataBuffer());
559        } catch (IllegalArgumentException e) {
560            return -1; // Unknown data buffer type
561        }
562
563        float ippMatrix[] = new float[12];
564
565        if (invertChannels) {
566            // IPP treats big endian integers like BGR, so we have to
567            // swap columns 1 and 3 and rows 1 and 3
568            for (int i = 0; i < mxHeight; i++) {
569                ippMatrix[i * 4] = matrix[2 - i][2];
570                ippMatrix[i * 4 + 1] = matrix[2 - i][1];
571                ippMatrix[i * 4 + 2] = matrix[2 - i][0];
572
573                if (mxWidth == 4) {
574                    ippMatrix[i * 4 + 3] = matrix[2 - i][3];
575                } else if (mxWidth == 5) {
576                    ippMatrix[i * 4 + 3] = matrix[2 - i][4];
577                }
578            }
579        } else {
580            for (int i = 0; i < mxHeight; i++) {
581                ippMatrix[i * 4] = matrix[i][0];
582                ippMatrix[i * 4 + 1] = matrix[i][1];
583                ippMatrix[i * 4 + 2] = matrix[i][2];
584
585                if (mxWidth == 4) {
586                    ippMatrix[i * 4 + 3] = matrix[i][3];
587                } else if (mxWidth == 5) {
588                    ippMatrix[i * 4 + 3] = matrix[i][4];
589                }
590            }
591        }
592
593        return ippColorTwist(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst
594                .getWidth(), dst.getHeight(), dstStride, ippMatrix, type, offsets, inPlace);
595    }
596
597    /**
598     * Ipp color twist.
599     *
600     * @param srcData
601     *            the src data.
602     * @param srcWidth
603     *            the src width.
604     * @param srcHeight
605     *            the src height.
606     * @param srcStride
607     *            the src stride.
608     * @param dstData
609     *            the dst data.
610     * @param dstWidth
611     *            the dst width.
612     * @param dstHeight
613     *            the dst height.
614     * @param dstStride
615     *            the dst stride.
616     * @param ippMatrix
617     *            the ipp matrix.
618     * @param type
619     *            the type.
620     * @param offsets
621     *            the offsets.
622     * @param inPlace
623     *            the in place.
624     * @return the int.
625     */
626    private final native int ippColorTwist(Object srcData, int srcWidth, int srcHeight,
627            int srcStride, Object dstData, int dstWidth, int dstHeight, int dstStride,
628            float ippMatrix[], int type, int offsets[], boolean inPlace);
629
630    /**
631     * Simple combine bands.
632     *
633     * @param srcData
634     *            the src data.
635     * @param srcWidth
636     *            the src width.
637     * @param srcHeight
638     *            the src height.
639     * @param srcStride
640     *            the src stride.
641     * @param srcChannels
642     *            the src channels.
643     * @param dstData
644     *            the dst data.
645     * @param dstStride
646     *            the dst stride.
647     * @param dstChannels
648     *            the dst channels.
649     * @param m
650     *            the m.
651     * @param offsets
652     *            the offsets.
653     * @return the int.
654     */
655    private final native int simpleCombineBands(Object srcData, int srcWidth, int srcHeight,
656            int srcStride, int srcChannels, Object dstData, int dstStride, int dstChannels,
657            float m[], int offsets[]);
658}
659