1/*
2 * Copyright (c) 2007, 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 sun.java2d.pipe;
27
28import java.awt.color.ColorSpace;
29import java.awt.image.AffineTransformOp;
30import java.awt.image.BufferedImage;
31import java.awt.image.BufferedImageOp;
32import java.awt.image.BufferedImageOp;
33import java.awt.image.ByteLookupTable;
34import java.awt.image.ColorModel;
35import java.awt.image.ConvolveOp;
36import java.awt.image.IndexColorModel;
37import java.awt.image.Kernel;
38import java.awt.image.LookupOp;
39import java.awt.image.LookupTable;
40import java.awt.image.RescaleOp;
41import java.awt.image.ShortLookupTable;
42import sun.java2d.SurfaceData;
43import sun.java2d.loops.CompositeType;
44import static sun.java2d.pipe.BufferedOpCodes.*;
45
46public class BufferedBufImgOps {
47
48    public static void enableBufImgOp(RenderQueue rq, SurfaceData srcData,
49                                      BufferedImage srcImg,
50                                      BufferedImageOp biop)
51    {
52        if (biop instanceof ConvolveOp) {
53            enableConvolveOp(rq, srcData, (ConvolveOp)biop);
54        } else if (biop instanceof RescaleOp) {
55            enableRescaleOp(rq, srcData, srcImg, (RescaleOp)biop);
56        } else if (biop instanceof LookupOp) {
57            enableLookupOp(rq, srcData, srcImg, (LookupOp)biop);
58        } else {
59            throw new InternalError("Unknown BufferedImageOp");
60        }
61    }
62
63    public static void disableBufImgOp(RenderQueue rq, BufferedImageOp biop) {
64        if (biop instanceof ConvolveOp) {
65            disableConvolveOp(rq);
66        } else if (biop instanceof RescaleOp) {
67            disableRescaleOp(rq);
68        } else if (biop instanceof LookupOp) {
69            disableLookupOp(rq);
70        } else {
71            throw new InternalError("Unknown BufferedImageOp");
72        }
73    }
74
75/**************************** ConvolveOp support ****************************/
76
77    public static boolean isConvolveOpValid(ConvolveOp cop) {
78        Kernel kernel = cop.getKernel();
79        int kw = kernel.getWidth();
80        int kh = kernel.getHeight();
81        // REMIND: we currently can only handle 3x3 and 5x5 kernels,
82        //         but hopefully this is just a temporary restriction;
83        //         see native shader comments for more details
84        if (!(kw == 3 && kh == 3) && !(kw == 5 && kh == 5)) {
85            return false;
86        }
87        return true;
88    }
89
90    private static void enableConvolveOp(RenderQueue rq,
91                                         SurfaceData srcData,
92                                         ConvolveOp cop)
93    {
94        // assert rq.lock.isHeldByCurrentThread();
95        boolean edgeZero =
96            cop.getEdgeCondition() == ConvolveOp.EDGE_ZERO_FILL;
97        Kernel kernel = cop.getKernel();
98        int kernelWidth = kernel.getWidth();
99        int kernelHeight = kernel.getHeight();
100        int kernelSize = kernelWidth * kernelHeight;
101        int sizeofFloat = 4;
102        int totalBytesRequired = 4 + 8 + 12 + (kernelSize * sizeofFloat);
103
104        RenderBuffer buf = rq.getBuffer();
105        rq.ensureCapacityAndAlignment(totalBytesRequired, 4);
106        buf.putInt(ENABLE_CONVOLVE_OP);
107        buf.putLong(srcData.getNativeOps());
108        buf.putInt(edgeZero ? 1 : 0);
109        buf.putInt(kernelWidth);
110        buf.putInt(kernelHeight);
111        buf.put(kernel.getKernelData(null));
112    }
113
114    private static void disableConvolveOp(RenderQueue rq) {
115        // assert rq.lock.isHeldByCurrentThread();
116        RenderBuffer buf = rq.getBuffer();
117        rq.ensureCapacity(4);
118        buf.putInt(DISABLE_CONVOLVE_OP);
119    }
120
121/**************************** RescaleOp support *****************************/
122
123    public static boolean isRescaleOpValid(RescaleOp rop,
124                                           BufferedImage srcImg)
125    {
126        int numFactors = rop.getNumFactors();
127        ColorModel srcCM = srcImg.getColorModel();
128
129        if (srcCM instanceof IndexColorModel) {
130            throw new
131                IllegalArgumentException("Rescaling cannot be "+
132                                         "performed on an indexed image");
133        }
134        if (numFactors != 1 &&
135            numFactors != srcCM.getNumColorComponents() &&
136            numFactors != srcCM.getNumComponents())
137        {
138            throw new IllegalArgumentException("Number of scaling constants "+
139                                               "does not equal the number of"+
140                                               " of color or color/alpha "+
141                                               " components");
142        }
143
144        int csType = srcCM.getColorSpace().getType();
145        if (csType != ColorSpace.TYPE_RGB &&
146            csType != ColorSpace.TYPE_GRAY)
147        {
148            // Not prepared to deal with other color spaces
149            return false;
150        }
151
152        if (numFactors == 2 || numFactors > 4) {
153            // Not really prepared to handle this at the native level, so...
154            return false;
155        }
156
157        return true;
158    }
159
160    private static void enableRescaleOp(RenderQueue rq,
161                                        SurfaceData srcData,
162                                        BufferedImage srcImg,
163                                        RescaleOp rop)
164    {
165        // assert rq.lock.isHeldByCurrentThread();
166        ColorModel srcCM = srcImg.getColorModel();
167        boolean nonPremult =
168            srcCM.hasAlpha() &&
169            srcCM.isAlphaPremultiplied();
170
171        /*
172         * Note: The user-provided scale factors and offsets are arranged
173         * in R/G/B/A order, regardless of the raw data order of the
174         * underlying Raster/DataBuffer.  The source image data is ultimately
175         * converted into RGBA data when uploaded to an OpenGL texture
176         * (even for TYPE_GRAY), so the scale factors and offsets are already
177         * in the order expected by the native OpenGL code.
178         *
179         * However, the offsets provided by the user are in a range dictated
180         * by the size of each color/alpha band in the source image.  For
181         * example, for 8/8/8 data each offset is in the range [0,255],
182         * for 5/5/5 data each offset is in the range [0,31], and so on.
183         * The OpenGL shader only thinks in terms of [0,1], so below we need
184         * to normalize the user-provided offset values into the range [0,1].
185         */
186        int numFactors = rop.getNumFactors();
187        float[] origScaleFactors = rop.getScaleFactors(null);
188        float[] origOffsets = rop.getOffsets(null);
189
190        // To make things easier, we will always pass all four bands
191        // down to native code...
192        float[] normScaleFactors;
193        float[] normOffsets;
194
195        if (numFactors == 1) {
196            normScaleFactors = new float[4];
197            normOffsets      = new float[4];
198            for (int i = 0; i < 3; i++) {
199                normScaleFactors[i] = origScaleFactors[0];
200                normOffsets[i]      = origOffsets[0];
201            }
202            // Leave alpha untouched...
203            normScaleFactors[3] = 1.0f;
204            normOffsets[3]      = 0.0f;
205        } else if (numFactors == 3) {
206            normScaleFactors = new float[4];
207            normOffsets      = new float[4];
208            for (int i = 0; i < 3; i++) {
209                normScaleFactors[i] = origScaleFactors[i];
210                normOffsets[i]      = origOffsets[i];
211            }
212            // Leave alpha untouched...
213            normScaleFactors[3] = 1.0f;
214            normOffsets[3]      = 0.0f;
215        } else { // (numFactors == 4)
216            normScaleFactors = origScaleFactors;
217            normOffsets      = origOffsets;
218        }
219
220        // The user-provided offsets are specified in the range
221        // of each source color band, but the OpenGL shader only wants
222        // to deal with data in the range [0,1], so we need to normalize
223        // each offset value to the range [0,1] here.
224        if (srcCM.getNumComponents() == 1) {
225            // Gray data
226            int nBits = srcCM.getComponentSize(0);
227            int maxValue = (1 << nBits) - 1;
228            for (int i = 0; i < 3; i++) {
229                normOffsets[i] /= maxValue;
230            }
231        } else {
232            // RGB(A) data
233            for (int i = 0; i < srcCM.getNumComponents(); i++) {
234                int nBits = srcCM.getComponentSize(i);
235                int maxValue = (1 << nBits) - 1;
236                normOffsets[i] /= maxValue;
237            }
238        }
239
240        int sizeofFloat = 4;
241        int totalBytesRequired = 4 + 8 + 4 + (4 * sizeofFloat * 2);
242
243        RenderBuffer buf = rq.getBuffer();
244        rq.ensureCapacityAndAlignment(totalBytesRequired, 4);
245        buf.putInt(ENABLE_RESCALE_OP);
246        buf.putLong(srcData.getNativeOps());
247        buf.putInt(nonPremult ? 1 : 0);
248        buf.put(normScaleFactors);
249        buf.put(normOffsets);
250    }
251
252    private static void disableRescaleOp(RenderQueue rq) {
253        // assert rq.lock.isHeldByCurrentThread();
254        RenderBuffer buf = rq.getBuffer();
255        rq.ensureCapacity(4);
256        buf.putInt(DISABLE_RESCALE_OP);
257    }
258
259/**************************** LookupOp support ******************************/
260
261    public static boolean isLookupOpValid(LookupOp lop,
262                                          BufferedImage srcImg)
263    {
264        LookupTable table = lop.getTable();
265        int numComps = table.getNumComponents();
266        ColorModel srcCM = srcImg.getColorModel();
267
268        if (srcCM instanceof IndexColorModel) {
269            throw new
270                IllegalArgumentException("LookupOp cannot be "+
271                                         "performed on an indexed image");
272        }
273        if (numComps != 1 &&
274            numComps != srcCM.getNumComponents() &&
275            numComps != srcCM.getNumColorComponents())
276        {
277            throw new IllegalArgumentException("Number of arrays in the "+
278                                               " lookup table ("+
279                                               numComps+
280                                               ") is not compatible with"+
281                                               " the src image: "+srcImg);
282        }
283
284        int csType = srcCM.getColorSpace().getType();
285        if (csType != ColorSpace.TYPE_RGB &&
286            csType != ColorSpace.TYPE_GRAY)
287        {
288            // Not prepared to deal with other color spaces
289            return false;
290        }
291
292        if (numComps == 2 || numComps > 4) {
293            // Not really prepared to handle this at the native level, so...
294            return false;
295        }
296
297        // The LookupTable spec says that "all arrays must be the
298        // same size" but unfortunately the constructors do not
299        // enforce that.  Also, our native code only works with
300        // arrays no larger than 256 elements, so check both of
301        // these restrictions here.
302        if (table instanceof ByteLookupTable) {
303            byte[][] data = ((ByteLookupTable)table).getTable();
304            for (int i = 1; i < data.length; i++) {
305                if (data[i].length > 256 ||
306                    data[i].length != data[i-1].length)
307                {
308                    return false;
309                }
310            }
311        } else if (table instanceof ShortLookupTable) {
312            short[][] data = ((ShortLookupTable)table).getTable();
313            for (int i = 1; i < data.length; i++) {
314                if (data[i].length > 256 ||
315                    data[i].length != data[i-1].length)
316                {
317                    return false;
318                }
319            }
320        } else {
321            return false;
322        }
323
324        return true;
325    }
326
327    private static void enableLookupOp(RenderQueue rq,
328                                       SurfaceData srcData,
329                                       BufferedImage srcImg,
330                                       LookupOp lop)
331    {
332        // assert rq.lock.isHeldByCurrentThread();
333        boolean nonPremult =
334            srcImg.getColorModel().hasAlpha() &&
335            srcImg.isAlphaPremultiplied();
336
337        LookupTable table = lop.getTable();
338        int numBands = table.getNumComponents();
339        int offset = table.getOffset();
340        int bandLength;
341        int bytesPerElem;
342        boolean shortData;
343
344        if (table instanceof ShortLookupTable) {
345            short[][] data = ((ShortLookupTable)table).getTable();
346            bandLength = data[0].length;
347            bytesPerElem = 2;
348            shortData = true;
349        } else { // (table instanceof ByteLookupTable)
350            byte[][] data = ((ByteLookupTable)table).getTable();
351            bandLength = data[0].length;
352            bytesPerElem = 1;
353            shortData = false;
354        }
355
356        // Adjust the LUT length so that it ends on a 4-byte boundary
357        int totalLutBytes = numBands * bandLength * bytesPerElem;
358        int paddedLutBytes = (totalLutBytes + 3) & (~3);
359        int padding = paddedLutBytes - totalLutBytes;
360        int totalBytesRequired = 4 + 8 + 20 + paddedLutBytes;
361
362        RenderBuffer buf = rq.getBuffer();
363        rq.ensureCapacityAndAlignment(totalBytesRequired, 4);
364        buf.putInt(ENABLE_LOOKUP_OP);
365        buf.putLong(srcData.getNativeOps());
366        buf.putInt(nonPremult ? 1 : 0);
367        buf.putInt(shortData ? 1 : 0);
368        buf.putInt(numBands);
369        buf.putInt(bandLength);
370        buf.putInt(offset);
371        if (shortData) {
372            short[][] data = ((ShortLookupTable)table).getTable();
373            for (int i = 0; i < numBands; i++) {
374                buf.put(data[i]);
375            }
376        } else {
377            byte[][] data = ((ByteLookupTable)table).getTable();
378            for (int i = 0; i < numBands; i++) {
379                buf.put(data[i]);
380            }
381        }
382        if (padding != 0) {
383            buf.position(buf.position() + padding);
384        }
385    }
386
387    private static void disableLookupOp(RenderQueue rq) {
388        // assert rq.lock.isHeldByCurrentThread();
389        RenderBuffer buf = rq.getBuffer();
390        rq.ensureCapacity(4);
391        buf.putInt(DISABLE_LOOKUP_OP);
392    }
393}
394