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 package org.apache.harmony.awt.gl.color; 22 23 import java.awt.image.BufferedImage; 24 import java.awt.image.ColorModel; 25 import java.awt.image.ComponentSampleModel; 26 import java.awt.image.DataBuffer; 27 import java.awt.image.Raster; 28 import java.awt.image.SampleModel; 29 import java.awt.image.SinglePixelPackedSampleModel; 30 import java.util.ArrayList; 31 32 import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; 33 import org.apache.harmony.awt.internal.nls.Messages; 34 35 36 /** 37 * This class converts java color/sample models to the LCMS pixel formats. 38 * It also encapsulates all the information about the image format, which native CMM 39 * needs to have in order to read/write data. 40 * 41 * At present planar formats (multiple bands) are not supported 42 * and they are handled as a common (custom) case. 43 * Samples other than 1 - 7 bytes and multiple of 8 bits are 44 * also handled as custom (and won't be supported in the nearest future). 45 */ 46 class NativeImageFormat { 47 ////////////////////////////////////////////// 48 // LCMS Pixel types 49 private static final int PT_ANY = 0; // Don't check colorspace 50 // 1 & 2 are reserved 51 private static final int PT_GRAY = 3; 52 private static final int PT_RGB = 4; 53 // Skipping other since we don't use them here 54 /////////////////////////////////////////////// 55 56 // Conversion of predefined BufferedImage formats to LCMS formats 57 private static final int INT_RGB_LCMS_FMT = 58 colorspaceSh(PT_RGB)| 59 extraSh(1)| 60 channelsSh(3)| 61 bytesSh(1)| 62 doswapSh(1)| 63 swapfirstSh(1); 64 65 private static final int INT_ARGB_LCMS_FMT = INT_RGB_LCMS_FMT; 66 67 private static final int INT_BGR_LCMS_FMT = 68 colorspaceSh(PT_RGB)| 69 extraSh(1)| 70 channelsSh(3)| 71 bytesSh(1); 72 73 private static final int THREE_BYTE_BGR_LCMS_FMT = 74 colorspaceSh(PT_RGB)| 75 channelsSh(3)| 76 bytesSh(1)| 77 doswapSh(1); 78 79 private static final int FOUR_BYTE_ABGR_LCMS_FMT = 80 colorspaceSh(PT_RGB)| 81 extraSh(1)| 82 channelsSh(3)| 83 bytesSh(1)| 84 doswapSh(1); 85 86 private static final int BYTE_GRAY_LCMS_FMT = 87 colorspaceSh(PT_GRAY)| 88 channelsSh(1)| 89 bytesSh(1); 90 91 private static final int USHORT_GRAY_LCMS_FMT = 92 colorspaceSh(PT_GRAY)| 93 channelsSh(1)| 94 bytesSh(2); 95 96 // LCMS format packed into 32 bit value. For description 97 // of this format refer to LCMS documentation. 98 private int cmmFormat = 0; 99 100 // Dimensions 101 private int rows = 0; 102 private int cols = 0; 103 104 // Scanline may contain some padding in the end 105 private int scanlineStride = -1; 106 107 private Object imageData; 108 // It's possible to have offset from the beginning of the array 109 private int dataOffset; 110 111 // Has the image alpha channel? If has - here its band band offset goes 112 private int alphaOffset = -1; 113 114 // initializes proper field IDs initIDs()115 private static native void initIDs(); 116 117 static { NativeCMM.loadCMM()118 NativeCMM.loadCMM(); initIDs()119 initIDs(); 120 } 121 122 //////////////////////////////////// 123 // LCMS image format encoders 124 //////////////////////////////////// colorspaceSh(int s)125 private static int colorspaceSh(int s) { 126 return (s << 16); 127 } 128 swapfirstSh(int s)129 private static int swapfirstSh(int s) { 130 return (s << 14); 131 } 132 flavorSh(int s)133 private static int flavorSh(int s) { 134 return (s << 13); 135 } 136 planarSh(int s)137 private static int planarSh(int s) { 138 return (s << 12); 139 } 140 endianSh(int s)141 private static int endianSh(int s) { 142 return (s << 11); 143 } 144 doswapSh(int s)145 private static int doswapSh(int s) { 146 return (s << 10); 147 } 148 extraSh(int s)149 private static int extraSh(int s) { 150 return (s << 7); 151 } 152 channelsSh(int s)153 private static int channelsSh(int s) { 154 return (s << 3); 155 } 156 bytesSh(int s)157 private static int bytesSh(int s) { 158 return s; 159 } 160 //////////////////////////////////// 161 // End of LCMS image format encoders 162 //////////////////////////////////// 163 164 // Accessors getChannelData()165 Object getChannelData() { 166 return imageData; 167 } 168 getNumCols()169 int getNumCols() { 170 return cols; 171 } 172 getNumRows()173 int getNumRows() { 174 return rows; 175 } 176 177 // Constructors NativeImageFormat()178 public NativeImageFormat() { 179 } 180 181 /** 182 * Simple image layout for common case with 183 * not optimized workflow. 184 * 185 * For hifi colorspaces with 5+ color channels imgData 186 * should be <code>byte</code> array. 187 * 188 * For common colorspaces with up to 4 color channels it 189 * should be <code>short</code> array. 190 * 191 * Alpha channel is handled by caller, not by CMS. 192 * 193 * Color channels are in their natural order (not BGR but RGB). 194 * 195 * @param imgData - array of <code>byte</code> or <code>short</code> 196 * @param nChannels - number of channels 197 * @param nRows - number of scanlines in the image 198 * @param nCols - number of pixels in one row of the image 199 */ NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols)200 public NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols) { 201 if (imgData instanceof short[]) { 202 cmmFormat |= bytesSh(2); 203 } 204 else if (imgData instanceof byte[]) { 205 cmmFormat |= bytesSh(1); 206 } 207 else 208 // awt.47=First argument should be byte or short array 209 throw new IllegalArgumentException(Messages.getString("awt.47")); //$NON-NLS-1$ 210 211 cmmFormat |= channelsSh(nChannels); 212 213 rows = nRows; 214 cols = nCols; 215 216 imageData = imgData; 217 218 dataOffset = 0; 219 } 220 221 /** 222 * Deduces image format from the buffered image type 223 * or color and sample models. 224 * @param bi - image 225 * @return image format object 226 */ createNativeImageFormat(BufferedImage bi)227 public static NativeImageFormat createNativeImageFormat(BufferedImage bi) { 228 NativeImageFormat fmt = new NativeImageFormat(); 229 230 switch (bi.getType()) { 231 case BufferedImage.TYPE_INT_RGB: { 232 fmt.cmmFormat = INT_RGB_LCMS_FMT; 233 break; 234 } 235 236 case BufferedImage.TYPE_INT_ARGB: 237 case BufferedImage.TYPE_INT_ARGB_PRE: { 238 fmt.cmmFormat = INT_ARGB_LCMS_FMT; 239 fmt.alphaOffset = 3; 240 break; 241 } 242 243 case BufferedImage.TYPE_INT_BGR: { 244 fmt.cmmFormat = INT_BGR_LCMS_FMT; 245 break; 246 } 247 248 case BufferedImage.TYPE_3BYTE_BGR: { 249 fmt.cmmFormat = THREE_BYTE_BGR_LCMS_FMT; 250 break; 251 } 252 253 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 254 case BufferedImage.TYPE_4BYTE_ABGR: { 255 fmt.cmmFormat = FOUR_BYTE_ABGR_LCMS_FMT; 256 fmt.alphaOffset = 0; 257 break; 258 } 259 260 case BufferedImage.TYPE_BYTE_GRAY: { 261 fmt.cmmFormat = BYTE_GRAY_LCMS_FMT; 262 break; 263 } 264 265 case BufferedImage.TYPE_USHORT_GRAY: { 266 fmt.cmmFormat = USHORT_GRAY_LCMS_FMT; 267 break; 268 } 269 270 case BufferedImage.TYPE_BYTE_BINARY: 271 case BufferedImage.TYPE_USHORT_565_RGB: 272 case BufferedImage.TYPE_USHORT_555_RGB: 273 case BufferedImage.TYPE_BYTE_INDEXED: { 274 // A bunch of unsupported formats 275 return null; 276 } 277 278 default: 279 break; // Try to look at sample model and color model 280 } 281 282 283 if (fmt.cmmFormat == 0) { 284 ColorModel cm = bi.getColorModel(); 285 SampleModel sm = bi.getSampleModel(); 286 287 if (sm instanceof ComponentSampleModel) { 288 ComponentSampleModel csm = (ComponentSampleModel) sm; 289 fmt.cmmFormat = getFormatFromComponentModel(csm, cm.hasAlpha()); 290 fmt.scanlineStride = calculateScanlineStrideCSM(csm, bi.getRaster()); 291 } else if (sm instanceof SinglePixelPackedSampleModel) { 292 SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; 293 fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, cm.hasAlpha()); 294 fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, bi.getRaster()); 295 } 296 297 if (cm.hasAlpha()) 298 fmt.alphaOffset = calculateAlphaOffset(sm, bi.getRaster()); 299 } 300 301 if (fmt.cmmFormat == 0) 302 return null; 303 304 if (!fmt.setImageData(bi.getRaster().getDataBuffer())) { 305 return null; 306 } 307 308 fmt.rows = bi.getHeight(); 309 fmt.cols = bi.getWidth(); 310 311 fmt.dataOffset = bi.getRaster().getDataBuffer().getOffset(); 312 313 return fmt; 314 } 315 316 /** 317 * Deduces image format from the raster sample model. 318 * @param r - raster 319 * @return image format object 320 */ createNativeImageFormat(Raster r)321 public static NativeImageFormat createNativeImageFormat(Raster r) { 322 NativeImageFormat fmt = new NativeImageFormat(); 323 SampleModel sm = r.getSampleModel(); 324 325 // Assume that there's no alpha 326 if (sm instanceof ComponentSampleModel) { 327 ComponentSampleModel csm = (ComponentSampleModel) sm; 328 fmt.cmmFormat = getFormatFromComponentModel(csm, false); 329 fmt.scanlineStride = calculateScanlineStrideCSM(csm, r); 330 } else if (sm instanceof SinglePixelPackedSampleModel) { 331 SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; 332 fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, false); 333 fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, r); 334 } 335 336 if (fmt.cmmFormat == 0) 337 return null; 338 339 fmt.cols = r.getWidth(); 340 fmt.rows = r.getHeight(); 341 fmt.dataOffset = r.getDataBuffer().getOffset(); 342 343 if (!fmt.setImageData(r.getDataBuffer())) 344 return null; 345 346 return fmt; 347 } 348 349 /** 350 * Obtains LCMS format from the component sample model 351 * @param sm - sample model 352 * @param hasAlpha - true if there's an alpha channel 353 * @return LCMS format 354 */ getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha)355 private static int getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha) { 356 // Multiple data arrays (banks) not supported 357 int bankIndex = sm.getBankIndices()[0]; 358 for (int i=1; i < sm.getNumBands(); i++) { 359 if (sm.getBankIndices()[i] != bankIndex) { 360 return 0; 361 } 362 } 363 364 int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); 365 int extra = hasAlpha ? 1 : 0; 366 int bytes = 1; 367 switch (sm.getDataType()) { 368 case DataBuffer.TYPE_BYTE: 369 bytes = 1; break; 370 case DataBuffer.TYPE_SHORT: 371 case DataBuffer.TYPE_USHORT: 372 bytes = 2; break; 373 case DataBuffer.TYPE_INT: 374 bytes = 4; break; 375 case DataBuffer.TYPE_DOUBLE: 376 bytes = 0; break; 377 default: 378 return 0; // Unsupported data type 379 } 380 381 int doSwap = 0; 382 int swapFirst = 0; 383 boolean knownFormat = false; 384 385 int i; 386 387 // "RGBA" 388 for (i=0; i < sm.getNumBands(); i++) { 389 if (sm.getBandOffsets()[i] != i) break; 390 } 391 if (i == sm.getNumBands()) { // Ok, it is it 392 doSwap = 0; 393 swapFirst = 0; 394 knownFormat = true; 395 } 396 397 // "ARGB" 398 if (!knownFormat) { 399 for (i=0; i < sm.getNumBands()-1; i++) { 400 if (sm.getBandOffsets()[i] != i+1) break; 401 } 402 if (sm.getBandOffsets()[i] == 0) i++; 403 if (i == sm.getNumBands()) { // Ok, it is it 404 doSwap = 0; 405 swapFirst = 1; 406 knownFormat = true; 407 } 408 } 409 410 // "BGRA" 411 if (!knownFormat) { 412 for (i=0; i < sm.getNumBands()-1; i++) { 413 if (sm.getBandOffsets()[i] != sm.getNumBands() - 2 - i) break; 414 } 415 if (sm.getBandOffsets()[i] == sm.getNumBands()-1) i++; 416 if (i == sm.getNumBands()) { // Ok, it is it 417 doSwap = 1; 418 swapFirst = 1; 419 knownFormat = true; 420 } 421 } 422 423 // "ABGR" 424 if (!knownFormat) { 425 for (i=0; i < sm.getNumBands(); i++) { 426 if (sm.getBandOffsets()[i] != sm.getNumBands() - 1 - i) break; 427 } 428 if (i == sm.getNumBands()) { // Ok, it is it 429 doSwap = 1; 430 swapFirst = 0; 431 knownFormat = true; 432 } 433 } 434 435 // XXX - Planar formats are not supported yet 436 if (!knownFormat) 437 return 0; 438 439 return 440 channelsSh(channels) | 441 bytesSh(bytes) | 442 extraSh(extra) | 443 doswapSh(doSwap) | 444 swapfirstSh(swapFirst); 445 } 446 447 /** 448 * Obtains LCMS format from the single pixel packed sample model 449 * @param sm - sample model 450 * @param hasAlpha - true if there's an alpha channel 451 * @return LCMS format 452 */ getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm, boolean hasAlpha)453 private static int getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm, 454 boolean hasAlpha) { 455 // Can we extract bytes? 456 int mask = sm.getBitMasks()[0] >>> sm.getBitOffsets()[0]; 457 if (!(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF)) 458 return 0; 459 460 // All masks are same? 461 for (int i = 1; i < sm.getNumBands(); i++) { 462 if ((sm.getBitMasks()[i] >>> sm.getBitOffsets()[i]) != mask) 463 return 0; 464 } 465 466 int pixelSize = 0; 467 // Check if data type is supported 468 if (sm.getDataType() == DataBuffer.TYPE_USHORT) 469 pixelSize = 2; 470 else if (sm.getDataType() == DataBuffer.TYPE_INT) 471 pixelSize = 4; 472 else 473 return 0; 474 475 476 int bytes = 0; 477 switch (mask) { 478 case 0xFF: 479 bytes = 1; 480 break; 481 case 0xFFFF: 482 bytes = 2; 483 break; 484 case 0xFFFFFFFF: 485 bytes = 4; 486 break; 487 default: return 0; 488 } 489 490 491 int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); 492 int extra = hasAlpha ? 1 : 0; 493 extra += pixelSize/bytes - sm.getNumBands(); // Unused bytes? 494 495 // Form an ArrayList containing offset for each band 496 ArrayList<Integer> offsetsLst = new ArrayList<Integer>(); 497 for (int k=0; k < sm.getNumBands(); k++) { 498 offsetsLst.add(new Integer(sm.getBitOffsets()[k]/(bytes*8))); 499 } 500 501 // Add offsets for unused space 502 for (int i=0; i<pixelSize/bytes; i++) { 503 if (offsetsLst.indexOf(new Integer(i)) < 0) 504 offsetsLst.add(new Integer(i)); 505 } 506 507 int offsets[] = new int[pixelSize/bytes]; 508 for (int i=0; i<offsetsLst.size(); i++) { 509 offsets[i] = offsetsLst.get(i).intValue(); 510 } 511 512 int doSwap = 0; 513 int swapFirst = 0; 514 boolean knownFormat = false; 515 516 int i; 517 518 // "RGBA" 519 for (i=0; i < pixelSize; i++) { 520 if (offsets[i] != i) break; 521 } 522 if (i == pixelSize) { // Ok, it is it 523 doSwap = 0; 524 swapFirst = 0; 525 knownFormat = true; 526 } 527 528 // "ARGB" 529 if (!knownFormat) { 530 for (i=0; i < pixelSize-1; i++) { 531 if (offsets[i] != i+1) break; 532 } 533 if (offsets[i] == 0) i++; 534 if (i == pixelSize) { // Ok, it is it 535 doSwap = 0; 536 swapFirst = 1; 537 knownFormat = true; 538 } 539 } 540 541 // "BGRA" 542 if (!knownFormat) { 543 for (i=0; i < pixelSize-1; i++) { 544 if (offsets[i] != pixelSize - 2 - i) break; 545 } 546 if (offsets[i] == pixelSize-1) i++; 547 if (i == pixelSize) { // Ok, it is it 548 doSwap = 1; 549 swapFirst = 1; 550 knownFormat = true; 551 } 552 } 553 554 // "ABGR" 555 if (!knownFormat) { 556 for (i=0; i < pixelSize; i++) { 557 if (offsets[i] != pixelSize - 1 - i) break; 558 } 559 if (i == pixelSize) { // Ok, it is it 560 doSwap = 1; 561 swapFirst = 0; 562 knownFormat = true; 563 } 564 } 565 566 // XXX - Planar formats are not supported yet 567 if (!knownFormat) 568 return 0; 569 570 return 571 channelsSh(channels) | 572 bytesSh(bytes) | 573 extraSh(extra) | 574 doswapSh(doSwap) | 575 swapfirstSh(swapFirst); 576 } 577 578 /** 579 * Obtains data array from the DataBuffer object 580 * @param db - data buffer 581 * @return - true if successful 582 */ setImageData(DataBuffer db)583 private boolean setImageData(DataBuffer db) { 584 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); 585 try { 586 imageData = dbAccess.getData(db); 587 } catch (IllegalArgumentException e) { 588 return false; // Unknown data buffer type 589 } 590 591 return true; 592 } 593 594 /** 595 * Calculates scanline stride in bytes 596 * @param csm - component sample model 597 * @param r - raster 598 * @return scanline stride in bytes 599 */ calculateScanlineStrideCSM(ComponentSampleModel csm, Raster r)600 private static int calculateScanlineStrideCSM(ComponentSampleModel csm, Raster r) { 601 if (csm.getScanlineStride() != csm.getPixelStride()*csm.getWidth()) { 602 int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; 603 return csm.getScanlineStride()*dataTypeSize; 604 } 605 return -1; 606 } 607 608 /** 609 * Calculates scanline stride in bytes 610 * @param sppsm - sample model 611 * @param r - raster 612 * @return scanline stride in bytes 613 */ calculateScanlineStrideSPPSM(SinglePixelPackedSampleModel sppsm, Raster r)614 private static int calculateScanlineStrideSPPSM(SinglePixelPackedSampleModel sppsm, Raster r) { 615 if (sppsm.getScanlineStride() != sppsm.getWidth()) { 616 int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; 617 return sppsm.getScanlineStride()*dataTypeSize; 618 } 619 return -1; 620 } 621 622 /** 623 * Calculates byte offset of the alpha channel from the beginning of the pixel data 624 * @param sm - sample model 625 * @param r - raster 626 * @return byte offset of the alpha channel 627 */ calculateAlphaOffset(SampleModel sm, Raster r)628 private static int calculateAlphaOffset(SampleModel sm, Raster r) { 629 if (sm instanceof ComponentSampleModel) { 630 ComponentSampleModel csm = (ComponentSampleModel) sm; 631 int dataTypeSize = 632 DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; 633 return 634 csm.getBandOffsets()[csm.getBandOffsets().length - 1] * dataTypeSize; 635 } else if (sm instanceof SinglePixelPackedSampleModel) { 636 SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; 637 return sppsm.getBitOffsets()[sppsm.getBitOffsets().length - 1] / 8; 638 } else { 639 return -1; // No offset, don't copy alpha 640 } 641 } 642 } 643