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