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 package sun.java2d.cmm.lcms; 26 27 import java.awt.image.BufferedImage; 28 import java.awt.image.ComponentColorModel; 29 import java.awt.image.ComponentSampleModel; 30 import java.awt.image.DataBuffer; 31 import java.awt.image.ColorModel; 32 import java.awt.image.Raster; 33 import java.awt.image.SampleModel; 34 import sun.awt.image.ByteComponentRaster; 35 import sun.awt.image.ShortComponentRaster; 36 import sun.awt.image.IntegerComponentRaster; 37 38 class LCMSImageLayout { 39 40 public static int BYTES_SH(int x) { 41 return x; 42 } 43 44 public static int EXTRA_SH(int x) { 45 return x << 7; 46 } 47 48 public static int CHANNELS_SH(int x) { 49 return x << 3; 50 } 51 public static final int SWAPFIRST = 1 << 14; 52 public static final int DOSWAP = 1 << 10; 53 public static final int PT_RGB_8 = 54 CHANNELS_SH(3) | BYTES_SH(1); 55 public static final int PT_GRAY_8 = 56 CHANNELS_SH(1) | BYTES_SH(1); 57 public static final int PT_GRAY_16 = 58 CHANNELS_SH(1) | BYTES_SH(2); 59 public static final int PT_RGBA_8 = 60 EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1); 61 public static final int PT_ARGB_8 = 62 EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1) | SWAPFIRST; 63 public static final int PT_BGR_8 = 64 DOSWAP | CHANNELS_SH(3) | BYTES_SH(1); 65 public static final int PT_ABGR_8 = 66 DOSWAP | EXTRA_SH(1) | CHANNELS_SH(3) | BYTES_SH(1); 67 public static final int PT_BGRA_8 = EXTRA_SH(1) | CHANNELS_SH(3) 68 | BYTES_SH(1) | DOSWAP | SWAPFIRST; 69 public static final int DT_BYTE = 0; 70 public static final int DT_SHORT = 1; 71 public static final int DT_INT = 2; 72 public static final int DT_DOUBLE = 3; 73 boolean isIntPacked = false; 74 int pixelType; 75 int dataType; 76 int width; 77 int height; 78 int nextRowOffset; 79 private int nextPixelOffset; 80 int offset; 81 82 /* This flag indicates whether the image can be processed 83 * at once by doTransfrom() native call. Otherwise, the 84 * image is processed scan by scan. 85 */ 86 private boolean imageAtOnce = false; 87 Object dataArray; 88 89 private int dataArrayLength; /* in bytes */ 90 91 private LCMSImageLayout(int np, int pixelType, int pixelSize) 92 throws ImageLayoutException 93 { 94 this.pixelType = pixelType; 95 width = np; 96 height = 1; 97 nextPixelOffset = pixelSize; 98 nextRowOffset = safeMult(pixelSize, np); 99 offset = 0; 100 } 101 102 private LCMSImageLayout(int width, int height, int pixelType, 103 int pixelSize) 104 throws ImageLayoutException 105 { 106 this.pixelType = pixelType; 107 this.width = width; 108 this.height = height; 109 nextPixelOffset = pixelSize; 110 nextRowOffset = safeMult(pixelSize, width); 111 offset = 0; 112 } 113 114 115 public LCMSImageLayout(byte[] data, int np, int pixelType, int pixelSize) 116 throws ImageLayoutException 117 { 118 this(np, pixelType, pixelSize); 119 dataType = DT_BYTE; 120 dataArray = data; 121 dataArrayLength = data.length; 122 123 verify(); 124 } 125 126 public LCMSImageLayout(short[] data, int np, int pixelType, int pixelSize) 127 throws ImageLayoutException 128 { 129 this(np, pixelType, pixelSize); 130 dataType = DT_SHORT; 131 dataArray = data; 132 dataArrayLength = 2 * data.length; 133 134 verify(); 135 } 136 137 public LCMSImageLayout(int[] data, int np, int pixelType, int pixelSize) 138 throws ImageLayoutException 139 { 140 this(np, pixelType, pixelSize); 141 dataType = DT_INT; 142 dataArray = data; 143 dataArrayLength = 4 * data.length; 144 145 verify(); 146 } 147 148 public LCMSImageLayout(double[] data, int np, int pixelType, int pixelSize) 149 throws ImageLayoutException 150 { 151 this(np, pixelType, pixelSize); 152 dataType = DT_DOUBLE; 153 dataArray = data; 154 dataArrayLength = 8 * data.length; 155 156 verify(); 157 } 158 159 private LCMSImageLayout() { 160 } 161 162 /* This method creates a layout object for given image. 163 * Returns null if the image is not supported by current implementation. 164 */ 165 public static LCMSImageLayout createImageLayout(BufferedImage image) throws ImageLayoutException { 166 LCMSImageLayout l = new LCMSImageLayout(); 167 168 switch (image.getType()) { 169 case BufferedImage.TYPE_INT_RGB: 170 l.pixelType = PT_ARGB_8; 171 l.isIntPacked = true; 172 break; 173 case BufferedImage.TYPE_INT_ARGB: 174 l.pixelType = PT_ARGB_8; 175 l.isIntPacked = true; 176 break; 177 case BufferedImage.TYPE_INT_BGR: 178 l.pixelType = PT_ABGR_8; 179 l.isIntPacked = true; 180 break; 181 case BufferedImage.TYPE_3BYTE_BGR: 182 l.pixelType = PT_BGR_8; 183 break; 184 case BufferedImage.TYPE_4BYTE_ABGR: 185 l.pixelType = PT_ABGR_8; 186 break; 187 case BufferedImage.TYPE_BYTE_GRAY: 188 l.pixelType = PT_GRAY_8; 189 break; 190 case BufferedImage.TYPE_USHORT_GRAY: 191 l.pixelType = PT_GRAY_16; 192 break; 193 default: 194 /* ColorConvertOp creates component images as 195 * default destination, so this kind of images 196 * has to be supported. 197 */ 198 ColorModel cm = image.getColorModel(); 199 if (cm instanceof ComponentColorModel) { 200 ComponentColorModel ccm = (ComponentColorModel) cm; 201 202 // verify whether the component size is fine 203 int[] cs = ccm.getComponentSize(); 204 for (int s : cs) { 205 if (s != 8) { 206 return null; 207 } 208 } 209 210 return createImageLayout(image.getRaster()); 211 212 } 213 return null; 214 } 215 216 l.width = image.getWidth(); 217 l.height = image.getHeight(); 218 219 switch (image.getType()) { 220 case BufferedImage.TYPE_INT_RGB: 221 case BufferedImage.TYPE_INT_ARGB: 222 case BufferedImage.TYPE_INT_BGR: 223 do { 224 IntegerComponentRaster intRaster = (IntegerComponentRaster) 225 image.getRaster(); 226 l.nextRowOffset = safeMult(4, intRaster.getScanlineStride()); 227 l.nextPixelOffset = safeMult(4, intRaster.getPixelStride()); 228 l.offset = safeMult(4, intRaster.getDataOffset(0)); 229 l.dataArray = intRaster.getDataStorage(); 230 l.dataArrayLength = 4 * intRaster.getDataStorage().length; 231 l.dataType = DT_INT; 232 233 if (l.nextRowOffset == l.width * 4 * intRaster.getPixelStride()) { 234 l.imageAtOnce = true; 235 } 236 } while (false); 237 break; 238 239 case BufferedImage.TYPE_3BYTE_BGR: 240 case BufferedImage.TYPE_4BYTE_ABGR: 241 do { 242 ByteComponentRaster byteRaster = (ByteComponentRaster) 243 image.getRaster(); 244 l.nextRowOffset = byteRaster.getScanlineStride(); 245 l.nextPixelOffset = byteRaster.getPixelStride(); 246 247 int firstBand = image.getSampleModel().getNumBands() - 1; 248 l.offset = byteRaster.getDataOffset(firstBand); 249 l.dataArray = byteRaster.getDataStorage(); 250 l.dataArrayLength = byteRaster.getDataStorage().length; 251 l.dataType = DT_BYTE; 252 if (l.nextRowOffset == l.width * byteRaster.getPixelStride()) { 253 l.imageAtOnce = true; 254 } 255 } while (false); 256 break; 257 258 case BufferedImage.TYPE_BYTE_GRAY: 259 do { 260 ByteComponentRaster byteRaster = (ByteComponentRaster) 261 image.getRaster(); 262 l.nextRowOffset = byteRaster.getScanlineStride(); 263 l.nextPixelOffset = byteRaster.getPixelStride(); 264 265 l.dataArrayLength = byteRaster.getDataStorage().length; 266 l.offset = byteRaster.getDataOffset(0); 267 l.dataArray = byteRaster.getDataStorage(); 268 l.dataType = DT_BYTE; 269 270 if (l.nextRowOffset == l.width * byteRaster.getPixelStride()) { 271 l.imageAtOnce = true; 272 } 273 } while (false); 274 break; 275 276 case BufferedImage.TYPE_USHORT_GRAY: 277 do { 278 ShortComponentRaster shortRaster = (ShortComponentRaster) 279 image.getRaster(); 280 l.nextRowOffset = safeMult(2, shortRaster.getScanlineStride()); 281 l.nextPixelOffset = safeMult(2, shortRaster.getPixelStride()); 282 283 l.offset = safeMult(2, shortRaster.getDataOffset(0)); 284 l.dataArray = shortRaster.getDataStorage(); 285 l.dataArrayLength = 2 * shortRaster.getDataStorage().length; 286 l.dataType = DT_SHORT; 287 288 if (l.nextRowOffset == l.width * 2 * shortRaster.getPixelStride()) { 289 l.imageAtOnce = true; 290 } 291 } while (false); 292 break; 293 default: 294 return null; 295 } 296 l.verify(); 297 return l; 298 } 299 300 private static enum BandOrder { 301 DIRECT, 302 INVERTED, 303 ARBITRARY, 304 UNKNOWN; 305 306 public static BandOrder getBandOrder(int[] bandOffsets) { 307 BandOrder order = UNKNOWN; 308 309 int numBands = bandOffsets.length; 310 311 for (int i = 0; (order != ARBITRARY) && (i < bandOffsets.length); i++) { 312 switch (order) { 313 case UNKNOWN: 314 if (bandOffsets[i] == i) { 315 order = DIRECT; 316 } else if (bandOffsets[i] == (numBands - 1 - i)) { 317 order = INVERTED; 318 } else { 319 order = ARBITRARY; 320 } 321 break; 322 case DIRECT: 323 if (bandOffsets[i] != i) { 324 order = ARBITRARY; 325 } 326 break; 327 case INVERTED: 328 if (bandOffsets[i] != (numBands - 1 - i)) { 329 order = ARBITRARY; 330 } 331 break; 332 } 333 } 334 return order; 335 } 336 } 337 338 private void verify() throws ImageLayoutException { 339 340 if (offset < 0 || offset >= dataArrayLength) { 341 throw new ImageLayoutException("Invalid image layout"); 342 } 343 344 if (nextPixelOffset != getBytesPerPixel(pixelType)) { 345 throw new ImageLayoutException("Invalid image layout"); 346 } 347 348 int lastScanOffset = safeMult(nextRowOffset, (height - 1)); 349 350 int lastPixelOffset = safeMult(nextPixelOffset, (width -1 )); 351 352 lastPixelOffset = safeAdd(lastPixelOffset, lastScanOffset); 353 354 int off = safeAdd(offset, lastPixelOffset); 355 356 if (off < 0 || off >= dataArrayLength) { 357 throw new ImageLayoutException("Invalid image layout"); 358 } 359 } 360 361 static int safeAdd(int a, int b) throws ImageLayoutException { 362 long res = a; 363 res += b; 364 if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) { 365 throw new ImageLayoutException("Invalid image layout"); 366 } 367 return (int)res; 368 } 369 370 static int safeMult(int a, int b) throws ImageLayoutException { 371 long res = a; 372 res *= b; 373 if (res < Integer.MIN_VALUE || res > Integer.MAX_VALUE) { 374 throw new ImageLayoutException("Invalid image layout"); 375 } 376 return (int)res; 377 } 378 379 public static class ImageLayoutException extends Exception { 380 public ImageLayoutException(String message) { 381 super(message); 382 } 383 } 384 public static LCMSImageLayout createImageLayout(Raster r) { 385 LCMSImageLayout l = new LCMSImageLayout(); 386 if (r instanceof ByteComponentRaster) { 387 ByteComponentRaster br = (ByteComponentRaster)r; 388 389 ComponentSampleModel csm = (ComponentSampleModel)r.getSampleModel(); 390 391 l.pixelType = CHANNELS_SH(br.getNumBands()) | BYTES_SH(1); 392 393 int[] bandOffsets = csm.getBandOffsets(); 394 BandOrder order = BandOrder.getBandOrder(bandOffsets); 395 396 int firstBand = 0; 397 switch (order) { 398 case INVERTED: 399 l.pixelType |= DOSWAP; 400 firstBand = csm.getNumBands() - 1; 401 break; 402 case DIRECT: 403 // do nothing 404 break; 405 default: 406 // unable to create the image layout; 407 return null; 408 } 409 410 l.nextRowOffset = br.getScanlineStride(); 411 l.nextPixelOffset = br.getPixelStride(); 412 413 l.offset = br.getDataOffset(firstBand); 414 l.dataArray = br.getDataStorage(); 415 l.dataType = DT_BYTE; 416 417 l.width = br.getWidth(); 418 l.height = br.getHeight(); 419 420 if (l.nextRowOffset == l.width * br.getPixelStride()) { 421 l.imageAtOnce = true; 422 } 423 return l; 424 } 425 return null; 426 } 427 428 /** 429 * Derives number of bytes per pixel from the pixel format. 430 * Following bit fields are used here: 431 * [0..2] - bytes per sample 432 * [3..6] - number of color samples per pixel 433 * [7..9] - number of non-color samples per pixel 434 * 435 * A complete description of the pixel format can be found 436 * here: lcms2.h, lines 651 - 667. 437 * 438 * @param pixelType pixel format in lcms2 notation. 439 * @return number of bytes per pixel for given pixel format. 440 */ 441 private static int getBytesPerPixel(int pixelType) { 442 int bytesPerSample = (0x7 & pixelType); 443 int colorSamplesPerPixel = 0xF & (pixelType >> 3); 444 int extraSamplesPerPixel = 0x7 & (pixelType >> 7); 445 446 return bytesPerSample * (colorSamplesPerPixel + extraSamplesPerPixel); 447 } 448 }