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: Oct 6, 2005
22 */
23
24package java.awt.image;
25
26import java.awt.geom.Point2D;
27import java.awt.geom.Rectangle2D;
28import java.awt.*;
29import java.util.Arrays;
30
31import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor;
32import org.apache.harmony.awt.internal.nls.Messages;
33
34/**
35 * The Class RescaleOp performs rescaling of the source image data by
36 * multiplying the pixel values with a scale factor and then adding an offset.
37 *
38 * @since Android 1.0
39 */
40public class RescaleOp implements BufferedImageOp, RasterOp {
41
42    /**
43     * The scale factors.
44     */
45    private float scaleFactors[];
46
47    /**
48     * The offsets.
49     */
50    private float offsets[];
51
52    /**
53     * The hints.
54     */
55    private RenderingHints hints;
56
57    static {
58        // TODO
59        // System.loadLibrary("imageops");
60    }
61
62    /**
63     * Instantiates a new RescaleOp object with the specified scale factors and
64     * offsets.
65     *
66     * @param scaleFactors
67     *            the array of scale factor values.
68     * @param offsets
69     *            the array of offset values.
70     * @param hints
71     *            the RenderingHints or null.
72     */
73    public RescaleOp(float[] scaleFactors, float[] offsets, RenderingHints hints) {
74        int numFactors = Math.min(scaleFactors.length, offsets.length);
75
76        this.scaleFactors = new float[numFactors];
77        this.offsets = new float[numFactors];
78
79        System.arraycopy(scaleFactors, 0, this.scaleFactors, 0, numFactors);
80        System.arraycopy(offsets, 0, this.offsets, 0, numFactors);
81
82        this.hints = hints;
83    }
84
85    /**
86     * Instantiates a new RescaleOp object with the specified scale factor and
87     * offset.
88     *
89     * @param scaleFactor
90     *            the scale factor.
91     * @param offset
92     *            the offset.
93     * @param hints
94     *            the RenderingHints or null.
95     */
96    public RescaleOp(float scaleFactor, float offset, RenderingHints hints) {
97        scaleFactors = new float[1];
98        offsets = new float[1];
99
100        scaleFactors[0] = scaleFactor;
101        offsets[0] = offset;
102
103        this.hints = hints;
104    }
105
106    /**
107     * Gets the number of scaling factors.
108     *
109     * @return the number of scaling factors.
110     */
111    public final int getNumFactors() {
112        return scaleFactors.length;
113    }
114
115    public final RenderingHints getRenderingHints() {
116        return hints;
117    }
118
119    /**
120     * Gets the scale factors of this RescaleOp.
121     *
122     * @param scaleFactors
123     *            the desired scale factors array will be copied to this array.
124     * @return the scale factors array.
125     */
126    public final float[] getScaleFactors(float[] scaleFactors) {
127        if (scaleFactors == null) {
128            scaleFactors = new float[this.scaleFactors.length];
129        }
130
131        int minLength = Math.min(scaleFactors.length, this.scaleFactors.length);
132        System.arraycopy(this.scaleFactors, 0, scaleFactors, 0, minLength);
133        return scaleFactors;
134    }
135
136    /**
137     * Gets the offsets array of this RescaleOp.
138     *
139     * @param offsets
140     *            the desired offsets array will be copied to this array.
141     * @return the offsets array of this RescaleOp.
142     */
143    public final float[] getOffsets(float[] offsets) {
144        if (offsets == null) {
145            offsets = new float[this.offsets.length];
146        }
147
148        int minLength = Math.min(offsets.length, this.offsets.length);
149        System.arraycopy(this.offsets, 0, offsets, 0, minLength);
150        return offsets;
151    }
152
153    public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
154        if (dstPt == null) {
155            dstPt = new Point2D.Float();
156        }
157
158        dstPt.setLocation(srcPt);
159        return dstPt;
160    }
161
162    public final Rectangle2D getBounds2D(Raster src) {
163        return src.getBounds();
164    }
165
166    public final Rectangle2D getBounds2D(BufferedImage src) {
167        return getBounds2D(src.getRaster());
168    }
169
170    public WritableRaster createCompatibleDestRaster(Raster src) {
171        return src.createCompatibleWritableRaster();
172    }
173
174    public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
175        if (dstCM == null) {
176            dstCM = src.getColorModel();
177        }
178
179        if (dstCM instanceof IndexColorModel) {
180            dstCM = ColorModel.getRGBdefault();
181        }
182
183        WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster()
184                .createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM
185                .createCompatibleWritableRaster(src.getWidth(), src.getHeight());
186
187        return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null);
188    }
189
190    public final WritableRaster filter(Raster src, WritableRaster dst) {
191        if (dst == null) {
192            dst = createCompatibleDestRaster(src);
193        } else {
194            if (src.getNumBands() != dst.getNumBands()) {
195                // awt.21D=Number of src bands ({0}) does not match number of
196                // dst bands ({1})
197                throw new IllegalArgumentException(Messages.getString("awt.21D", //$NON-NLS-1$
198                        src.getNumBands(), dst.getNumBands()));
199            }
200        }
201
202        if (this.scaleFactors.length != 1 && this.scaleFactors.length != src.getNumBands()) {
203            // awt.21E=Number of scaling constants is not equal to the number of
204            // bands
205            throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$
206        }
207
208        // TODO
209        // if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM, false) != 0)
210        if (slowFilter(src, dst, false) != 0) {
211            // awt.21F=Unable to transform source
212            throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
213        }
214
215        return dst;
216    }
217
218    /**
219     * Slow filter.
220     *
221     * @param src
222     *            the src.
223     * @param dst
224     *            the dst.
225     * @param skipAlpha
226     *            the skip alpha.
227     * @return the int.
228     */
229    private final int slowFilter(Raster src, WritableRaster dst, boolean skipAlpha) {
230        SampleModel sm = src.getSampleModel();
231
232        int numBands = src.getNumBands();
233        int srcHeight = src.getHeight();
234        int srcWidth = src.getWidth();
235
236        int srcMinX = src.getMinX();
237        int srcMinY = src.getMinY();
238        int dstMinX = dst.getMinX();
239        int dstMinY = dst.getMinY();
240
241        int[] maxValues = new int[numBands];
242        int[] masks = new int[numBands];
243        int[] sampleSizes = sm.getSampleSize();
244
245        for (int i = 0; i < numBands; i++) {
246            maxValues[i] = (1 << sampleSizes[i]) - 1;
247            masks[i] = ~(maxValues[i]);
248        }
249
250        // Processing bounds
251        float[] pixels = null;
252        pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels);
253
254        // Cycle over pixels to be calculated
255        if (skipAlpha) { // Always suppose that alpha channel is the last band
256            if (scaleFactors.length > 1) {
257                for (int i = 0; i < pixels.length;) {
258                    for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) {
259                        pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx];
260                        // Check for overflow now
261                        if (((int)pixels[i] & masks[bandIdx]) != 0) {
262                            if (pixels[i] < 0) {
263                                pixels[i] = 0;
264                            } else {
265                                pixels[i] = maxValues[bandIdx];
266                            }
267                        }
268                    }
269
270                    i++;
271                }
272            } else {
273                for (int i = 0; i < pixels.length;) {
274                    for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) {
275                        pixels[i] = pixels[i] * scaleFactors[0] + offsets[0];
276                        // Check for overflow now
277                        if (((int)pixels[i] & masks[bandIdx]) != 0) {
278                            if (pixels[i] < 0) {
279                                pixels[i] = 0;
280                            } else {
281                                pixels[i] = maxValues[bandIdx];
282                            }
283                        }
284                    }
285
286                    i++;
287                }
288            }
289        } else {
290            if (scaleFactors.length > 1) {
291                for (int i = 0; i < pixels.length;) {
292                    for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) {
293                        pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx];
294                        // Check for overflow now
295                        if (((int)pixels[i] & masks[bandIdx]) != 0) {
296                            if (pixels[i] < 0) {
297                                pixels[i] = 0;
298                            } else {
299                                pixels[i] = maxValues[bandIdx];
300                            }
301                        }
302                    }
303                }
304            } else {
305                for (int i = 0; i < pixels.length;) {
306                    for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) {
307                        pixels[i] = pixels[i] * scaleFactors[0] + offsets[0];
308                        // Check for overflow now
309                        if (((int)pixels[i] & masks[bandIdx]) != 0) {
310                            if (pixels[i] < 0) {
311                                pixels[i] = 0;
312                            } else {
313                                pixels[i] = maxValues[bandIdx];
314                            }
315                        }
316                    }
317                }
318            }
319        }
320
321        dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, pixels);
322
323        return 0;
324    }
325
326    public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
327        ColorModel srcCM = src.getColorModel();
328
329        if (srcCM instanceof IndexColorModel) {
330            // awt.220=Source should not have IndexColorModel
331            throw new IllegalArgumentException(Messages.getString("awt.220")); //$NON-NLS-1$
332        }
333
334        // Check if the number of scaling factors matches the number of bands
335        int nComponents = srcCM.getNumComponents();
336        boolean skipAlpha;
337        if (srcCM.hasAlpha()) {
338            if (scaleFactors.length == 1 || scaleFactors.length == nComponents - 1) {
339                skipAlpha = true;
340            } else if (scaleFactors.length == nComponents) {
341                skipAlpha = false;
342            } else {
343                // awt.21E=Number of scaling constants is not equal to the
344                // number of bands
345                throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$
346            }
347        } else if (scaleFactors.length == 1 || scaleFactors.length == nComponents) {
348            skipAlpha = false;
349        } else {
350            // awt.21E=Number of scaling constants is not equal to the number of
351            // bands
352            throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$
353        }
354
355        BufferedImage finalDst = null;
356        if (dst == null) {
357            finalDst = dst;
358            dst = createCompatibleDestImage(src, srcCM);
359        } else if (!srcCM.equals(dst.getColorModel())) {
360            // Treat BufferedImage.TYPE_INT_RGB and BufferedImage.TYPE_INT_ARGB
361            // as same
362            if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst
363                    .getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) {
364                finalDst = dst;
365                dst = createCompatibleDestImage(src, srcCM);
366            }
367        }
368
369        // TODO
370        // if (ippFilter(src.getRaster(), dst.getRaster(), src.getType(),
371        // skipAlpha) != 0)
372        if (slowFilter(src.getRaster(), dst.getRaster(), skipAlpha) != 0) {
373            // awt.21F=Unable to transform source
374            throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$
375        }
376
377        if (finalDst != null) {
378            Graphics2D g = finalDst.createGraphics();
379            g.setComposite(AlphaComposite.Src);
380            g.drawImage(dst, 0, 0, null);
381        } else {
382            finalDst = dst;
383        }
384
385        return finalDst;
386    }
387
388    // Don't forget to pass allocated arrays for levels and values, size should
389    // be numBands*4
390    /**
391     * Creates the levels.
392     *
393     * @param sm
394     *            the sm.
395     * @param numBands
396     *            the num bands.
397     * @param skipAlpha
398     *            the skip alpha.
399     * @param levels
400     *            the levels.
401     * @param values
402     *            the values.
403     * @param channelsOrder
404     *            the channels order.
405     */
406    private final void createLevels(SampleModel sm, int numBands, boolean skipAlpha, int levels[],
407            int values[], int channelsOrder[]) {
408        // Suppose same sample size for all channels, otherwise use slow filter
409        int maxValue = (1 << sm.getSampleSize(0)) - 1;
410
411        // For simplicity introduce these arrays
412        float extScaleFactors[] = new float[numBands];
413        float extOffsets[] = new float[numBands];
414
415        if (scaleFactors.length != 1) {
416            System.arraycopy(scaleFactors, 0, extScaleFactors, 0, scaleFactors.length);
417            System.arraycopy(offsets, 0, extOffsets, 0, scaleFactors.length);
418        } else {
419            for (int i = 0; i < numBands; i++) {
420                extScaleFactors[i] = scaleFactors[0];
421                extOffsets[i] = offsets[0];
422            }
423        }
424
425        if (skipAlpha) {
426            extScaleFactors[numBands - 1] = 1;
427            extOffsets[numBands - 1] = 0;
428        }
429
430        // Create a levels
431        for (int i = 0; i < numBands; i++) {
432            if (extScaleFactors[i] == 0) {
433                levels[i * 4] = 0;
434                levels[i * 4 + 1] = 0;
435                levels[i * 4 + 2] = maxValue + 1;
436                levels[i * 4 + 3] = maxValue + 1;
437            }
438
439            float minLevel = -extOffsets[i] / extScaleFactors[i];
440            float maxLevel = (maxValue - extOffsets[i]) / extScaleFactors[i];
441
442            if (minLevel < 0) {
443                minLevel = 0;
444            } else if (minLevel > maxValue) {
445                minLevel = maxValue;
446            }
447
448            if (maxLevel < 0) {
449                maxLevel = 0;
450            } else if (maxLevel > maxValue) {
451                maxLevel = maxValue;
452            }
453
454            levels[i * 4] = 0;
455            if (minLevel > maxLevel) {
456                levels[i * 4 + 1] = (int)maxLevel;
457                levels[i * 4 + 2] = (int)minLevel;
458            } else {
459                levels[i * 4 + 1] = (int)minLevel;
460                levels[i * 4 + 2] = (int)maxLevel;
461            }
462            levels[i * 4 + 3] = maxValue + 1;
463
464            // Fill values
465            for (int k = 0; k < 4; k++) {
466                int idx = i * 4 + k;
467                values[idx] = (int)(extScaleFactors[i] * levels[idx] + extOffsets[i]);
468                if (values[idx] < 0) {
469                    values[idx] = 0;
470                } else if (values[idx] > maxValue) {
471                    values[idx] = maxValue;
472                }
473            }
474        }
475
476        // Reorder data if channels are stored in different order
477        if (channelsOrder != null) {
478            int len = numBands * 4;
479            int savedLevels[] = new int[len];
480            int savedValues[] = new int[len];
481            System.arraycopy(levels, 0, savedLevels, 0, len);
482            System.arraycopy(values, 0, savedValues, 0, len);
483            for (int i = 0; i < channelsOrder.length; i++) {
484                System.arraycopy(savedLevels, i * 4, levels, channelsOrder[i] * 4, 4);
485                System.arraycopy(savedValues, i * 4, values, channelsOrder[i] * 4, 4);
486            }
487        }
488    }
489
490    // TODO remove when this method is used
491    /**
492     * Ipp filter.
493     *
494     * @param src
495     *            the src.
496     * @param dst
497     *            the dst.
498     * @param imageType
499     *            the image type.
500     * @param skipAlpha
501     *            the skip alpha.
502     * @return the int.
503     */
504    @SuppressWarnings("unused")
505    private final int ippFilter(Raster src, WritableRaster dst, int imageType, boolean skipAlpha) {
506        int res;
507
508        int srcStride, dstStride;
509        int channels;
510        int offsets[] = null;
511        int channelsOrder[] = null;
512
513        switch (imageType) {
514            case BufferedImage.TYPE_INT_ARGB:
515            case BufferedImage.TYPE_INT_ARGB_PRE:
516            case BufferedImage.TYPE_INT_RGB: {
517                channels = 4;
518                srcStride = src.getWidth() * 4;
519                dstStride = dst.getWidth() * 4;
520                channelsOrder = new int[] {
521                        2, 1, 0, 3
522                };
523                break;
524            }
525
526            case BufferedImage.TYPE_4BYTE_ABGR:
527            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
528            case BufferedImage.TYPE_INT_BGR: {
529                channels = 4;
530                srcStride = src.getWidth() * 4;
531                dstStride = dst.getWidth() * 4;
532                break;
533            }
534
535            case BufferedImage.TYPE_BYTE_GRAY: {
536                channels = 1;
537                srcStride = src.getWidth();
538                dstStride = dst.getWidth();
539                break;
540            }
541
542            case BufferedImage.TYPE_3BYTE_BGR: {
543                channels = 3;
544                srcStride = src.getWidth() * 3;
545                dstStride = dst.getWidth() * 3;
546                channelsOrder = new int[] {
547                        2, 1, 0
548                };
549                break;
550            }
551
552            case BufferedImage.TYPE_USHORT_GRAY:
553            case BufferedImage.TYPE_USHORT_565_RGB:
554            case BufferedImage.TYPE_USHORT_555_RGB:
555            case BufferedImage.TYPE_BYTE_BINARY: {
556                return slowFilter(src, dst, skipAlpha);
557            }
558
559            default: {
560                SampleModel srcSM = src.getSampleModel();
561                SampleModel dstSM = dst.getSampleModel();
562
563                if (srcSM instanceof PixelInterleavedSampleModel
564                        && dstSM instanceof PixelInterleavedSampleModel) {
565                    // Check PixelInterleavedSampleModel
566                    if (srcSM.getDataType() != DataBuffer.TYPE_BYTE
567                            || dstSM.getDataType() != DataBuffer.TYPE_BYTE) {
568                        return slowFilter(src, dst, skipAlpha);
569                    }
570
571                    channels = srcSM.getNumBands(); // Have IPP functions for 1,
572                    // 3 and 4 channels
573                    if (!(channels == 1 || channels == 3 || channels == 4)) {
574                        return slowFilter(src, dst, skipAlpha);
575                    }
576
577                    srcStride = ((ComponentSampleModel)srcSM).getScanlineStride();
578                    dstStride = ((ComponentSampleModel)dstSM).getScanlineStride();
579
580                    channelsOrder = ((ComponentSampleModel)srcSM).getBandOffsets();
581                } else if (srcSM instanceof SinglePixelPackedSampleModel
582                        && dstSM instanceof SinglePixelPackedSampleModel) {
583                    // Check SinglePixelPackedSampleModel
584                    SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM;
585                    SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM;
586
587                    channels = sppsm1.getNumBands();
588
589                    // TYPE_INT_RGB, TYPE_INT_ARGB...
590                    if (sppsm1.getDataType() != DataBuffer.TYPE_INT
591                            || sppsm2.getDataType() != DataBuffer.TYPE_INT
592                            || !(channels == 3 || channels == 4)) {
593                        return slowFilter(src, dst, skipAlpha);
594                    }
595
596                    // Check compatibility of sample models
597                    if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets())
598                            || !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) {
599                        return slowFilter(src, dst, skipAlpha);
600                    }
601
602                    for (int i = 0; i < channels; i++) {
603                        if (sppsm1.getSampleSize(i) != 8) {
604                            return slowFilter(src, dst, skipAlpha);
605                        }
606                    }
607
608                    channelsOrder = new int[channels];
609                    int bitOffsets[] = sppsm1.getBitOffsets();
610                    for (int i = 0; i < channels; i++) {
611                        channelsOrder[i] = bitOffsets[i] / 8;
612                    }
613
614                    if (channels == 3) { // Don't skip channel now, could be
615                        // optimized
616                        channels = 4;
617                    }
618
619                    srcStride = sppsm1.getScanlineStride() * 4;
620                    dstStride = sppsm2.getScanlineStride() * 4;
621                } else {
622                    return slowFilter(src, dst, skipAlpha);
623                }
624
625                // Fill offsets if there's a child raster
626                if (src.getParent() != null || dst.getParent() != null) {
627                    if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0
628                            || dst.getSampleModelTranslateX() != 0
629                            || dst.getSampleModelTranslateY() != 0) {
630                        offsets = new int[4];
631                        offsets[0] = -src.getSampleModelTranslateX() + src.getMinX();
632                        offsets[1] = -src.getSampleModelTranslateY() + src.getMinY();
633                        offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX();
634                        offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY();
635                    }
636                }
637            }
638        }
639
640        int levels[] = new int[4 * channels];
641        int values[] = new int[4 * channels];
642
643        createLevels(src.getSampleModel(), channels, skipAlpha, levels, values, channelsOrder);
644
645        Object srcData, dstData;
646        AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance();
647        try {
648            srcData = dbAccess.getData(src.getDataBuffer());
649            dstData = dbAccess.getData(dst.getDataBuffer());
650        } catch (IllegalArgumentException e) {
651            return -1; // Unknown data buffer type
652        }
653
654        res = LookupOp.ippLUT(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst
655                .getWidth(), dst.getHeight(), dstStride, levels, values, channels, offsets, true);
656
657        return res;
658    }
659}
660