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 
26 package sun.java2d.pipe;
27 
28 import java.awt.color.ColorSpace;
29 import java.awt.image.AffineTransformOp;
30 import java.awt.image.BufferedImage;
31 import java.awt.image.BufferedImageOp;
32 import java.awt.image.BufferedImageOp;
33 import java.awt.image.ByteLookupTable;
34 import java.awt.image.ColorModel;
35 import java.awt.image.ConvolveOp;
36 import java.awt.image.IndexColorModel;
37 import java.awt.image.Kernel;
38 import java.awt.image.LookupOp;
39 import java.awt.image.LookupTable;
40 import java.awt.image.RescaleOp;
41 import java.awt.image.ShortLookupTable;
42 import sun.java2d.SurfaceData;
43 import sun.java2d.loops.CompositeType;
44 import static sun.java2d.pipe.BufferedOpCodes.*;
45 
46 public class BufferedBufImgOps {
47 
enableBufImgOp(RenderQueue rq, SurfaceData srcData, BufferedImage srcImg, BufferedImageOp biop)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 
disableBufImgOp(RenderQueue rq, BufferedImageOp biop)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 
isConvolveOpValid(ConvolveOp cop)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 
enableConvolveOp(RenderQueue rq, SurfaceData srcData, ConvolveOp cop)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 
disableConvolveOp(RenderQueue rq)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 
isRescaleOpValid(RescaleOp rop, BufferedImage srcImg)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 
enableRescaleOp(RenderQueue rq, SurfaceData srcData, BufferedImage srcImg, RescaleOp rop)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 
disableRescaleOp(RenderQueue rq)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 
isLookupOpValid(LookupOp lop, BufferedImage srcImg)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 
enableLookupOp(RenderQueue rq, SurfaceData srcData, BufferedImage srcImg, LookupOp lop)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 
disableLookupOp(RenderQueue rq)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