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 * @date: Sep 20, 2005 22 */ 23 24package java.awt.image; 25 26import java.awt.*; 27import java.awt.geom.Point2D; 28import java.awt.geom.Rectangle2D; 29import java.util.Arrays; 30 31import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; 32import org.apache.harmony.awt.internal.nls.Messages; 33 34/** 35 * The BandCombineOp class translates coordinates from coordinates in the source 36 * Raster to coordinates in the destination Raster by an arbitrary linear 37 * combination of the bands in a source Raster, using a specified matrix. The 38 * number of bands in the matrix should equal to the number of bands in the 39 * source Raster plus 1. 40 * 41 * @since Android 1.0 42 */ 43public class BandCombineOp implements RasterOp { 44 45 /** 46 * The Constant offsets3c. 47 */ 48 static final int offsets3c[] = { 49 16, 8, 0 50 }; 51 52 /** 53 * The Constant offsets4ac. 54 */ 55 static final int offsets4ac[] = { 56 16, 8, 0, 24 57 }; 58 59 /** 60 * The Constant masks3c. 61 */ 62 static final int masks3c[] = { 63 0xFF0000, 0xFF00, 0xFF 64 }; 65 66 /** 67 * The Constant masks4ac. 68 */ 69 static final int masks4ac[] = { 70 0xFF0000, 0xFF00, 0xFF, 0xFF000000 71 }; 72 73 /** 74 * The Constant piOffsets. 75 */ 76 private static final int piOffsets[] = { 77 0, 1, 2 78 }; 79 80 /** 81 * The Constant piInvOffsets. 82 */ 83 private static final int piInvOffsets[] = { 84 2, 1, 0 85 }; 86 87 /** 88 * The Constant TYPE_BYTE3C. 89 */ 90 private static final int TYPE_BYTE3C = 0; 91 92 /** 93 * The Constant TYPE_BYTE4AC. 94 */ 95 private static final int TYPE_BYTE4AC = 1; 96 97 /** 98 * The Constant TYPE_USHORT3C. 99 */ 100 private static final int TYPE_USHORT3C = 2; 101 102 /** 103 * The Constant TYPE_SHORT3C. 104 */ 105 private static final int TYPE_SHORT3C = 3; 106 107 /** 108 * The mx width. 109 */ 110 private int mxWidth; 111 112 /** 113 * The mx height. 114 */ 115 private int mxHeight; 116 117 /** 118 * The matrix. 119 */ 120 private float matrix[][]; 121 122 /** 123 * The r hints. 124 */ 125 private RenderingHints rHints; 126 127 static { 128 // XXX - todo 129 // System.loadLibrary("imageops"); 130 } 131 132 /** 133 * Instantiates a new BandCombineOp object with the specified matrix. 134 * 135 * @param matrix 136 * the specified matrix for band combining. 137 * @param hints 138 * the RenderingHints. 139 */ 140 public BandCombineOp(float matrix[][], RenderingHints hints) { 141 this.mxHeight = matrix.length; 142 this.mxWidth = matrix[0].length; 143 this.matrix = new float[mxHeight][mxWidth]; 144 145 for (int i = 0; i < mxHeight; i++) { 146 System.arraycopy(matrix[i], 0, this.matrix[i], 0, mxWidth); 147 } 148 149 this.rHints = hints; 150 } 151 152 public final RenderingHints getRenderingHints() { 153 return this.rHints; 154 } 155 156 /** 157 * Gets the matrix associated with this BandCombineOp object. 158 * 159 * @return the matrix associated with this BandCombineOp object. 160 */ 161 public final float[][] getMatrix() { 162 float res[][] = new float[mxHeight][mxWidth]; 163 164 for (int i = 0; i < mxHeight; i++) { 165 System.arraycopy(matrix[i], 0, res[i], 0, mxWidth); 166 } 167 168 return res; 169 } 170 171 public final Point2D getPoint2D(Point2D srcPoint, Point2D dstPoint) { 172 if (dstPoint == null) { 173 dstPoint = new Point2D.Float(); 174 } 175 176 dstPoint.setLocation(srcPoint); 177 return dstPoint; 178 } 179 180 public final Rectangle2D getBounds2D(Raster src) { 181 return src.getBounds(); 182 } 183 184 public WritableRaster createCompatibleDestRaster(Raster src) { 185 int numBands = src.getNumBands(); 186 if (mxWidth != numBands && mxWidth != (numBands + 1) || numBands != mxHeight) { 187 // awt.254=Number of bands in the source raster ({0}) is 188 // incompatible with the matrix [{1}x{2}] 189 throw new IllegalArgumentException(Messages.getString("awt.254", //$NON-NLS-1$ 190 new Object[] { 191 numBands, mxWidth, mxHeight 192 })); 193 } 194 195 return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight()); 196 } 197 198 public WritableRaster filter(Raster src, WritableRaster dst) { 199 int numBands = src.getNumBands(); 200 201 if (mxWidth != numBands && mxWidth != (numBands + 1)) { 202 // awt.254=Number of bands in the source raster ({0}) is 203 // incompatible with the matrix [{1}x{2}] 204 throw new IllegalArgumentException(Messages.getString("awt.254", //$NON-NLS-1$ 205 new Object[] { 206 numBands, mxWidth, mxHeight 207 })); 208 } 209 210 if (dst == null) { 211 dst = createCompatibleDestRaster(src); 212 } else if (dst.getNumBands() != mxHeight) { 213 // awt.255=Number of bands in the destination raster ({0}) is 214 // incompatible with the matrix [{1}x{2}] 215 throw new IllegalArgumentException(Messages.getString("awt.255", //$NON-NLS-1$ 216 new Object[] { 217 dst.getNumBands(), mxWidth, mxHeight 218 })); 219 } 220 221 // XXX - todo 222 // if (ippFilter(src, dst) != 0) 223 if (verySlowFilter(src, dst) != 0) { 224 // awt.21F=Unable to transform source 225 throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ 226 } 227 228 return dst; 229 } 230 231 /** 232 * The Class SampleModelInfo. 233 */ 234 private static final class SampleModelInfo { 235 236 /** 237 * The channels. 238 */ 239 int channels; 240 241 /** 242 * The channels order. 243 */ 244 int channelsOrder[]; 245 246 /** 247 * The stride. 248 */ 249 int stride; 250 } 251 252 /** 253 * Check sample model. 254 * 255 * @param sm 256 * the sm. 257 * @return the sample model info. 258 */ 259 private final SampleModelInfo checkSampleModel(SampleModel sm) { 260 SampleModelInfo ret = new SampleModelInfo(); 261 262 if (sm instanceof PixelInterleavedSampleModel) { 263 // Check PixelInterleavedSampleModel 264 if (sm.getDataType() != DataBuffer.TYPE_BYTE) { 265 return null; 266 } 267 268 ret.channels = sm.getNumBands(); 269 ret.stride = ((ComponentSampleModel)sm).getScanlineStride(); 270 ret.channelsOrder = ((ComponentSampleModel)sm).getBandOffsets(); 271 272 } else if (sm instanceof SinglePixelPackedSampleModel) { 273 // Check SinglePixelPackedSampleModel 274 SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)sm; 275 276 ret.channels = sppsm1.getNumBands(); 277 if (sppsm1.getDataType() != DataBuffer.TYPE_INT) { 278 return null; 279 } 280 281 // Check sample models 282 for (int i = 0; i < ret.channels; i++) { 283 if (sppsm1.getSampleSize(i) != 8) { 284 return null; 285 } 286 } 287 288 ret.channelsOrder = new int[ret.channels]; 289 int bitOffsets[] = sppsm1.getBitOffsets(); 290 for (int i = 0; i < ret.channels; i++) { 291 if (bitOffsets[i] % 8 != 0) { 292 return null; 293 } 294 295 ret.channelsOrder[i] = bitOffsets[i] / 8; 296 } 297 298 ret.channels = 4; 299 ret.stride = sppsm1.getScanlineStride() * 4; 300 } else { 301 return null; 302 } 303 304 return ret; 305 } 306 307 /** 308 * Slow filter. 309 * 310 * @param src 311 * the src. 312 * @param dst 313 * the dst. 314 * @return the int. 315 */ 316 private final int slowFilter(Raster src, WritableRaster dst) { 317 int res = 0; 318 319 SampleModelInfo srcInfo, dstInfo; 320 int offsets[] = null; 321 322 srcInfo = checkSampleModel(src.getSampleModel()); 323 dstInfo = checkSampleModel(dst.getSampleModel()); 324 if (srcInfo == null || dstInfo == null) { 325 return verySlowFilter(src, dst); 326 } 327 328 // Fill offsets if there's a child raster 329 if (src.getParent() != null || dst.getParent() != null) { 330 if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0 331 || dst.getSampleModelTranslateX() != 0 || dst.getSampleModelTranslateY() != 0) { 332 offsets = new int[4]; 333 offsets[0] = -src.getSampleModelTranslateX() + src.getMinX(); 334 offsets[1] = -src.getSampleModelTranslateY() + src.getMinY(); 335 offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX(); 336 offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY(); 337 } 338 } 339 340 int rmxWidth = (srcInfo.channels + 1); // width of the reordered matrix 341 float reorderedMatrix[] = new float[rmxWidth * dstInfo.channels]; 342 for (int j = 0; j < dstInfo.channels; j++) { 343 if (j >= dstInfo.channelsOrder.length) { 344 continue; 345 } 346 347 for (int i = 0; i < srcInfo.channels; i++) { 348 if (i >= srcInfo.channelsOrder.length) { 349 break; 350 } 351 352 reorderedMatrix[dstInfo.channelsOrder[j] * rmxWidth + srcInfo.channelsOrder[i]] = matrix[j][i]; 353 } 354 if (mxWidth == rmxWidth) { 355 reorderedMatrix[(dstInfo.channelsOrder[j] + 1) * rmxWidth - 1] = matrix[j][mxWidth - 1]; 356 } 357 } 358 359 Object srcData, dstData; 360 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); 361 try { 362 srcData = dbAccess.getData(src.getDataBuffer()); 363 dstData = dbAccess.getData(dst.getDataBuffer()); 364 } catch (IllegalArgumentException e) { 365 return -1; // Unknown data buffer type 366 } 367 368 simpleCombineBands(srcData, src.getWidth(), src.getHeight(), srcInfo.stride, 369 srcInfo.channels, dstData, dstInfo.stride, dstInfo.channels, reorderedMatrix, 370 offsets); 371 372 return res; 373 } 374 375 /** 376 * Very slow filter. 377 * 378 * @param src 379 * the src. 380 * @param dst 381 * the dst. 382 * @return the int. 383 */ 384 private int verySlowFilter(Raster src, WritableRaster dst) { 385 int numBands = src.getNumBands(); 386 387 int srcMinX = src.getMinX(); 388 int srcY = src.getMinY(); 389 390 int dstMinX = dst.getMinX(); 391 int dstY = dst.getMinY(); 392 393 int dX = src.getWidth();// < dst.getWidth() ? src.getWidth() : 394 // dst.getWidth(); 395 int dY = src.getHeight();// < dst.getHeight() ? src.getHeight() : 396 // dst.getHeight(); 397 398 float sample; 399 int srcPixels[] = new int[numBands * dX * dY]; 400 int dstPixels[] = new int[mxHeight * dX * dY]; 401 402 srcPixels = src.getPixels(srcMinX, srcY, dX, dY, srcPixels); 403 404 if (numBands == mxWidth) { 405 for (int i = 0, j = 0; i < srcPixels.length; i += numBands) { 406 for (int dstB = 0; dstB < mxHeight; dstB++) { 407 sample = 0f; 408 for (int srcB = 0; srcB < numBands; srcB++) { 409 sample += matrix[dstB][srcB] * srcPixels[i + srcB]; 410 } 411 dstPixels[j++] = (int)sample; 412 } 413 } 414 } else { 415 for (int i = 0, j = 0; i < srcPixels.length; i += numBands) { 416 for (int dstB = 0; dstB < mxHeight; dstB++) { 417 sample = 0f; 418 for (int srcB = 0; srcB < numBands; srcB++) { 419 sample += matrix[dstB][srcB] * srcPixels[i + srcB]; 420 } 421 dstPixels[j++] = (int)(sample + matrix[dstB][numBands]); 422 } 423 } 424 } 425 426 dst.setPixels(dstMinX, dstY, dX, dY, dstPixels); 427 428 return 0; 429 } 430 431 // TODO remove when method is used 432 /** 433 * Ipp filter. 434 * 435 * @param src 436 * the src. 437 * @param dst 438 * the dst. 439 * @return the int. 440 */ 441 @SuppressWarnings("unused") 442 private int ippFilter(Raster src, WritableRaster dst) { 443 boolean invertChannels; 444 boolean inPlace = (src == dst); 445 int type; 446 int srcStride, dstStride; 447 int offsets[] = null; 448 449 int srcBands = src.getNumBands(); 450 int dstBands = dst.getNumBands(); 451 452 if (dstBands != 3 453 || (srcBands != 3 && !(srcBands == 4 && matrix[0][3] == 0 && matrix[1][3] == 0 && matrix[2][3] == 0))) { 454 return slowFilter(src, dst); 455 } 456 457 SampleModel srcSM = src.getSampleModel(); 458 SampleModel dstSM = dst.getSampleModel(); 459 460 if (srcSM instanceof SinglePixelPackedSampleModel 461 && dstSM instanceof SinglePixelPackedSampleModel) { 462 // Check SinglePixelPackedSampleModel 463 SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM; 464 SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM; 465 466 if (sppsm1.getDataType() != DataBuffer.TYPE_INT 467 || sppsm2.getDataType() != DataBuffer.TYPE_INT) { 468 return slowFilter(src, dst); 469 } 470 471 // Check sample models 472 if (!Arrays.equals(sppsm2.getBitOffsets(), offsets3c) 473 || !Arrays.equals(sppsm2.getBitMasks(), masks3c)) { 474 return slowFilter(src, dst); 475 } 476 477 if (srcBands == 3) { 478 if (!Arrays.equals(sppsm1.getBitOffsets(), offsets3c) 479 || !Arrays.equals(sppsm1.getBitMasks(), masks3c)) { 480 return slowFilter(src, dst); 481 } 482 } else if (srcBands == 4) { 483 if (!Arrays.equals(sppsm1.getBitOffsets(), offsets4ac) 484 || !Arrays.equals(sppsm1.getBitMasks(), masks4ac)) { 485 return slowFilter(src, dst); 486 } 487 } 488 489 type = TYPE_BYTE4AC; 490 invertChannels = true; 491 492 srcStride = sppsm1.getScanlineStride() * 4; 493 dstStride = sppsm2.getScanlineStride() * 4; 494 } else if (srcSM instanceof PixelInterleavedSampleModel 495 && dstSM instanceof PixelInterleavedSampleModel) { 496 if (srcBands != 3) { 497 return slowFilter(src, dst); 498 } 499 500 int srcDataType = srcSM.getDataType(); 501 502 switch (srcDataType) { 503 case DataBuffer.TYPE_BYTE: 504 type = TYPE_BYTE3C; 505 break; 506 case DataBuffer.TYPE_USHORT: 507 type = TYPE_USHORT3C; 508 break; 509 case DataBuffer.TYPE_SHORT: 510 type = TYPE_SHORT3C; 511 break; 512 default: 513 return slowFilter(src, dst); 514 } 515 516 // Check PixelInterleavedSampleModel 517 PixelInterleavedSampleModel pism1 = (PixelInterleavedSampleModel)srcSM; 518 PixelInterleavedSampleModel pism2 = (PixelInterleavedSampleModel)dstSM; 519 520 if (srcDataType != pism2.getDataType() || pism1.getPixelStride() != 3 521 || pism2.getPixelStride() != 3 522 || !Arrays.equals(pism1.getBandOffsets(), pism2.getBandOffsets())) { 523 return slowFilter(src, dst); 524 } 525 526 if (Arrays.equals(pism1.getBandOffsets(), piInvOffsets)) { 527 invertChannels = true; 528 } else if (Arrays.equals(pism1.getBandOffsets(), piOffsets)) { 529 invertChannels = false; 530 } else { 531 return slowFilter(src, dst); 532 } 533 534 int dataTypeSize = DataBuffer.getDataTypeSize(srcDataType) / 8; 535 536 srcStride = pism1.getScanlineStride() * dataTypeSize; 537 dstStride = pism2.getScanlineStride() * dataTypeSize; 538 } else { // XXX - todo - IPP allows support for planar data also 539 return slowFilter(src, dst); 540 } 541 542 // Fill offsets if there's a child raster 543 if (src.getParent() != null || dst.getParent() != null) { 544 if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0 545 || dst.getSampleModelTranslateX() != 0 || dst.getSampleModelTranslateY() != 0) { 546 offsets = new int[4]; 547 offsets[0] = -src.getSampleModelTranslateX() + src.getMinX(); 548 offsets[1] = -src.getSampleModelTranslateY() + src.getMinY(); 549 offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX(); 550 offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY(); 551 } 552 } 553 554 Object srcData, dstData; 555 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); 556 try { 557 srcData = dbAccess.getData(src.getDataBuffer()); 558 dstData = dbAccess.getData(dst.getDataBuffer()); 559 } catch (IllegalArgumentException e) { 560 return -1; // Unknown data buffer type 561 } 562 563 float ippMatrix[] = new float[12]; 564 565 if (invertChannels) { 566 // IPP treats big endian integers like BGR, so we have to 567 // swap columns 1 and 3 and rows 1 and 3 568 for (int i = 0; i < mxHeight; i++) { 569 ippMatrix[i * 4] = matrix[2 - i][2]; 570 ippMatrix[i * 4 + 1] = matrix[2 - i][1]; 571 ippMatrix[i * 4 + 2] = matrix[2 - i][0]; 572 573 if (mxWidth == 4) { 574 ippMatrix[i * 4 + 3] = matrix[2 - i][3]; 575 } else if (mxWidth == 5) { 576 ippMatrix[i * 4 + 3] = matrix[2 - i][4]; 577 } 578 } 579 } else { 580 for (int i = 0; i < mxHeight; i++) { 581 ippMatrix[i * 4] = matrix[i][0]; 582 ippMatrix[i * 4 + 1] = matrix[i][1]; 583 ippMatrix[i * 4 + 2] = matrix[i][2]; 584 585 if (mxWidth == 4) { 586 ippMatrix[i * 4 + 3] = matrix[i][3]; 587 } else if (mxWidth == 5) { 588 ippMatrix[i * 4 + 3] = matrix[i][4]; 589 } 590 } 591 } 592 593 return ippColorTwist(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst 594 .getWidth(), dst.getHeight(), dstStride, ippMatrix, type, offsets, inPlace); 595 } 596 597 /** 598 * Ipp color twist. 599 * 600 * @param srcData 601 * the src data. 602 * @param srcWidth 603 * the src width. 604 * @param srcHeight 605 * the src height. 606 * @param srcStride 607 * the src stride. 608 * @param dstData 609 * the dst data. 610 * @param dstWidth 611 * the dst width. 612 * @param dstHeight 613 * the dst height. 614 * @param dstStride 615 * the dst stride. 616 * @param ippMatrix 617 * the ipp matrix. 618 * @param type 619 * the type. 620 * @param offsets 621 * the offsets. 622 * @param inPlace 623 * the in place. 624 * @return the int. 625 */ 626 private final native int ippColorTwist(Object srcData, int srcWidth, int srcHeight, 627 int srcStride, Object dstData, int dstWidth, int dstHeight, int dstStride, 628 float ippMatrix[], int type, int offsets[], boolean inPlace); 629 630 /** 631 * Simple combine bands. 632 * 633 * @param srcData 634 * the src data. 635 * @param srcWidth 636 * the src width. 637 * @param srcHeight 638 * the src height. 639 * @param srcStride 640 * the src stride. 641 * @param srcChannels 642 * the src channels. 643 * @param dstData 644 * the dst data. 645 * @param dstStride 646 * the dst stride. 647 * @param dstChannels 648 * the dst channels. 649 * @param m 650 * the m. 651 * @param offsets 652 * the offsets. 653 * @return the int. 654 */ 655 private final native int simpleCombineBands(Object srcData, int srcWidth, int srcHeight, 656 int srcStride, int srcChannels, Object dstData, int dstStride, int dstChannels, 657 float m[], int offsets[]); 658} 659