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 29, 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 ConvolveOp class convolves from the source data to the destination using 36 * a convolution kernel. Each output pixel is represented as the result of 37 * multiplying the kernel and the surround of the input pixel. 38 * 39 * @since Android 1.0 40 */ 41public class ConvolveOp implements BufferedImageOp, RasterOp { 42 43 /** 44 * The Constant EDGE_ZERO_FILL indicates that pixels at the edge of the 45 * destination image are set to zero. 46 */ 47 public static final int EDGE_ZERO_FILL = 0; 48 49 /** 50 * The Constant EDGE_NO_OP indicates that pixels at the edge of the source 51 * image are converted to the edge pixels in the destination without 52 * modification. 53 */ 54 public static final int EDGE_NO_OP = 1; 55 56 /** 57 * The kernel. 58 */ 59 private Kernel kernel; 60 61 /** 62 * The edge cond. 63 */ 64 private int edgeCond; 65 66 /** 67 * The rhs. 68 */ 69 private RenderingHints rhs = null; 70 71 static { 72 // TODO 73 // System.loadLibrary("imageops"); 74 } 75 76 /** 77 * Instantiates a new ConvolveOp object with the specified Kernel and 78 * specified edges condition. 79 * 80 * @param kernel 81 * the specified Kernel. 82 * @param edgeCondition 83 * the specified edge condition. 84 * @param hints 85 * the RenderingHints object, or null. 86 */ 87 public ConvolveOp(Kernel kernel, int edgeCondition, RenderingHints hints) { 88 this.kernel = kernel; 89 this.edgeCond = edgeCondition; 90 this.rhs = hints; 91 } 92 93 /** 94 * Instantiates a new ConvolveOp object with the specified Kernel and 95 * EDGE_ZERO_FILL edge condition. 96 * 97 * @param kernel 98 * the specified Kernel. 99 */ 100 public ConvolveOp(Kernel kernel) { 101 this.kernel = kernel; 102 this.edgeCond = EDGE_ZERO_FILL; 103 } 104 105 /** 106 * Gets the Kernel object of this ConvolveOp. 107 * 108 * @return the Kernel object of this ConvolveOp. 109 */ 110 public final Kernel getKernel() { 111 return (Kernel)kernel.clone(); 112 } 113 114 public final RenderingHints getRenderingHints() { 115 return rhs; 116 } 117 118 /** 119 * Gets the edge condition of this ConvolveOp. 120 * 121 * @return the edge condition: EDGE_NO_OP or EDGE_ZERO_FILL. 122 */ 123 public int getEdgeCondition() { 124 return edgeCond; 125 } 126 127 public final Rectangle2D getBounds2D(Raster src) { 128 return src.getBounds(); 129 } 130 131 public final Rectangle2D getBounds2D(BufferedImage src) { 132 return getBounds2D(src.getRaster()); 133 } 134 135 public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { 136 if (dstPt == null) { 137 dstPt = new Point2D.Float(); 138 } 139 140 dstPt.setLocation(srcPt); 141 return dstPt; 142 } 143 144 public WritableRaster createCompatibleDestRaster(Raster src) { 145 return src.createCompatibleWritableRaster(); 146 } 147 148 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { 149 if (dstCM == null) { 150 dstCM = src.getColorModel(); 151 } 152 153 if (dstCM instanceof IndexColorModel) { 154 dstCM = ColorModel.getRGBdefault(); 155 } 156 157 WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster() 158 .createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM 159 .createCompatibleWritableRaster(src.getWidth(), src.getHeight()); 160 161 return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null); 162 } 163 164 public final WritableRaster filter(Raster src, WritableRaster dst) { 165 if (src == null) { // Should throw according to spec 166 // awt.256=Source raster is null 167 throw new NullPointerException(Messages.getString("awt.256")); //$NON-NLS-1$ 168 } 169 170 if (src == dst) { 171 // awt.257=Source raster is equal to destination 172 throw new IllegalArgumentException(Messages.getString("awt.257")); //$NON-NLS-1$ 173 } 174 175 if (dst == null) { 176 dst = createCompatibleDestRaster(src); 177 } else if (src.getNumBands() != dst.getNumBands()) { 178 // awt.258=Number of source bands ({0}) is not equal to number of 179 // destination bands ({1}) 180 throw new IllegalArgumentException(Messages.getString( 181 "awt.258", src.getNumBands(), dst.getNumBands())); //$NON-NLS-1$ 182 } 183 184 // TODO 185 // if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM) != 0) 186 if (slowFilter(src, dst) != 0) { 187 // awt.21F=Unable to transform source 188 throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ 189 } 190 191 return dst; 192 } 193 194 /** 195 * Slow filter. 196 * 197 * @param src 198 * the src. 199 * @param dst 200 * the dst. 201 * @return the int. 202 */ 203 private int slowFilter(Raster src, WritableRaster dst) { 204 try { 205 SampleModel sm = src.getSampleModel(); 206 207 int numBands = src.getNumBands(); 208 int srcHeight = src.getHeight(); 209 int srcWidth = src.getWidth(); 210 211 int xOrigin = kernel.getXOrigin(); 212 int yOrigin = kernel.getYOrigin(); 213 int kWidth = kernel.getWidth(); 214 int kHeight = kernel.getHeight(); 215 float[] data = kernel.getKernelData(null); 216 217 int srcMinX = src.getMinX(); 218 int srcMinY = src.getMinY(); 219 int dstMinX = dst.getMinX(); 220 int dstMinY = dst.getMinY(); 221 222 int srcConvMaxX = srcWidth - (kWidth - xOrigin - 1); 223 int srcConvMaxY = srcHeight - (kHeight - yOrigin - 1); 224 225 int[] maxValues = new int[numBands]; 226 int[] masks = new int[numBands]; 227 int[] sampleSizes = sm.getSampleSize(); 228 229 for (int i = 0; i < numBands; i++) { 230 maxValues[i] = (1 << sampleSizes[i]) - 1; 231 masks[i] = ~(maxValues[i]); 232 } 233 234 // Processing bounds 235 float[] pixels = null; 236 pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels); 237 float[] newPixels = new float[pixels.length]; 238 int rowLength = srcWidth * numBands; 239 if (this.edgeCond == ConvolveOp.EDGE_NO_OP) { 240 // top 241 int start = 0; 242 int length = yOrigin * rowLength; 243 System.arraycopy(pixels, start, newPixels, start, length); 244 // bottom 245 start = (srcHeight - (kHeight - yOrigin - 1)) * rowLength; 246 length = (kHeight - yOrigin - 1) * rowLength; 247 System.arraycopy(pixels, start, newPixels, start, length); 248 // middle 249 length = xOrigin * numBands; 250 int length1 = (kWidth - xOrigin - 1) * numBands; 251 start = yOrigin * rowLength; 252 int start1 = (yOrigin + 1) * rowLength - length1; 253 for (int i = yOrigin; i < (srcHeight - (kHeight - yOrigin - 1)); i++) { 254 System.arraycopy(pixels, start, newPixels, start, length); 255 System.arraycopy(pixels, start1, newPixels, start1, length1); 256 start += rowLength; 257 start1 += rowLength; 258 } 259 260 } 261 262 // Cycle over pixels to be calculated 263 for (int i = yOrigin; i < srcConvMaxY; i++) { 264 for (int j = xOrigin; j < srcConvMaxX; j++) { 265 266 // Take kernel data in backward direction, convolution 267 int kernelIdx = data.length - 1; 268 269 int pixelIndex = i * rowLength + j * numBands; 270 for (int hIdx = 0, rasterHIdx = i - yOrigin; hIdx < kHeight; hIdx++, rasterHIdx++) { 271 for (int wIdx = 0, rasterWIdx = j - xOrigin; wIdx < kWidth; wIdx++, rasterWIdx++) { 272 int curIndex = rasterHIdx * rowLength + rasterWIdx * numBands; 273 for (int idx = 0; idx < numBands; idx++) { 274 newPixels[pixelIndex + idx] += data[kernelIdx] 275 * pixels[curIndex + idx]; 276 } 277 kernelIdx--; 278 } 279 } 280 281 // Check for overflow now 282 for (int idx = 0; idx < numBands; idx++) { 283 if (((int)newPixels[pixelIndex + idx] & masks[idx]) != 0) { 284 if (newPixels[pixelIndex + idx] < 0) { 285 newPixels[pixelIndex + idx] = 0; 286 } else { 287 newPixels[pixelIndex + idx] = maxValues[idx]; 288 } 289 } 290 } 291 } 292 } 293 294 dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, newPixels); 295 } catch (Exception e) { // Something goes wrong, signal error 296 return 1; 297 } 298 return 0; 299 } 300 301 public final BufferedImage filter(BufferedImage src, BufferedImage dst) { 302 if (src == null) { 303 // awt.259=Source image is null 304 throw new NullPointerException(Messages.getString("awt.259")); //$NON-NLS-1$ 305 } 306 307 if (src == dst) { 308 // awt.25A=Source equals to destination 309 throw new IllegalArgumentException(Messages.getString("awt.25A")); //$NON-NLS-1$ 310 } 311 312 ColorModel srcCM = src.getColorModel(); 313 BufferedImage finalDst = null; 314 315 if (srcCM instanceof IndexColorModel) { 316 src = ((IndexColorModel)srcCM).convertToIntDiscrete(src.getRaster(), true); 317 srcCM = src.getColorModel(); 318 } 319 320 if (dst == null) { 321 dst = createCompatibleDestImage(src, srcCM); 322 } else { 323 if (!srcCM.equals(dst.getColorModel())) { 324 // Treat BufferedImage.TYPE_INT_RGB and 325 // BufferedImage.TYPE_INT_ARGB as same 326 if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst 327 .getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) { 328 finalDst = dst; 329 dst = createCompatibleDestImage(src, srcCM); 330 } 331 } 332 } 333 334 // Skip alpha channel for TYPE_INT_RGB images 335 // TODO 336 // if (ippFilter(src.getRaster(), dst.getRaster(), src.getType()) != 0) 337 if (slowFilter(src.getRaster(), dst.getRaster()) != 0) { 338 // awt.21F=Unable to transform source 339 throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ 340 } 341 342 if (finalDst != null) { 343 Graphics2D g = finalDst.createGraphics(); 344 g.setComposite(AlphaComposite.Src); 345 g.drawImage(dst, 0, 0, null); 346 } else { 347 finalDst = dst; 348 } 349 350 return finalDst; 351 } 352 353 // TODO remove when this method is used 354 /** 355 * Ipp filter. 356 * 357 * @param src 358 * the src. 359 * @param dst 360 * the dst. 361 * @param imageType 362 * the image type. 363 * @return the int. 364 */ 365 @SuppressWarnings("unused") 366 private int ippFilter(Raster src, WritableRaster dst, int imageType) { 367 int srcStride, dstStride; 368 boolean skipChannel = false; 369 int channels; 370 int offsets[] = null; 371 372 switch (imageType) { 373 case BufferedImage.TYPE_INT_RGB: 374 case BufferedImage.TYPE_INT_BGR: { 375 channels = 4; 376 srcStride = src.getWidth() * 4; 377 dstStride = dst.getWidth() * 4; 378 skipChannel = true; 379 break; 380 } 381 382 case BufferedImage.TYPE_INT_ARGB: 383 case BufferedImage.TYPE_INT_ARGB_PRE: 384 case BufferedImage.TYPE_4BYTE_ABGR: 385 case BufferedImage.TYPE_4BYTE_ABGR_PRE: { 386 channels = 4; 387 srcStride = src.getWidth() * 4; 388 dstStride = dst.getWidth() * 4; 389 break; 390 } 391 392 case BufferedImage.TYPE_BYTE_GRAY: { 393 channels = 1; 394 srcStride = src.getWidth(); 395 dstStride = dst.getWidth(); 396 break; 397 } 398 399 case BufferedImage.TYPE_3BYTE_BGR: { 400 channels = 3; 401 srcStride = src.getWidth() * 3; 402 dstStride = dst.getWidth() * 3; 403 break; 404 } 405 406 case BufferedImage.TYPE_USHORT_GRAY: // TODO - could be done in 407 // native code? 408 case BufferedImage.TYPE_USHORT_565_RGB: 409 case BufferedImage.TYPE_USHORT_555_RGB: 410 case BufferedImage.TYPE_BYTE_BINARY: { 411 return slowFilter(src, dst); 412 } 413 414 default: { 415 SampleModel srcSM = src.getSampleModel(); 416 SampleModel dstSM = dst.getSampleModel(); 417 418 if (srcSM instanceof PixelInterleavedSampleModel 419 && dstSM instanceof PixelInterleavedSampleModel) { 420 // Check PixelInterleavedSampleModel 421 if (srcSM.getDataType() != DataBuffer.TYPE_BYTE 422 || dstSM.getDataType() != DataBuffer.TYPE_BYTE) { 423 return slowFilter(src, dst); 424 } 425 426 channels = srcSM.getNumBands(); // Have IPP functions for 1, 427 // 3 and 4 channels 428 if (!(channels == 1 || channels == 3 || channels == 4)) { 429 return slowFilter(src, dst); 430 } 431 432 srcStride = ((ComponentSampleModel)srcSM).getScanlineStride(); 433 dstStride = ((ComponentSampleModel)dstSM).getScanlineStride(); 434 } else if (srcSM instanceof SinglePixelPackedSampleModel 435 && dstSM instanceof SinglePixelPackedSampleModel) { 436 // Check SinglePixelPackedSampleModel 437 SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM; 438 SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM; 439 440 channels = sppsm1.getNumBands(); 441 442 // TYPE_INT_RGB, TYPE_INT_ARGB... 443 if (sppsm1.getDataType() != DataBuffer.TYPE_INT 444 || sppsm2.getDataType() != DataBuffer.TYPE_INT 445 || !(channels == 3 || channels == 4)) { 446 return slowFilter(src, dst); 447 } 448 449 // Check compatibility of sample models 450 if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets()) 451 || !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) { 452 return slowFilter(src, dst); 453 } 454 455 for (int i = 0; i < channels; i++) { 456 if (sppsm1.getSampleSize(i) != 8) { 457 return slowFilter(src, dst); 458 } 459 } 460 461 if (channels == 3) { // Cannot skip channel, don't know 462 // which is alpha... 463 channels = 4; 464 } 465 466 srcStride = sppsm1.getScanlineStride() * 4; 467 dstStride = sppsm2.getScanlineStride() * 4; 468 } else { 469 return slowFilter(src, dst); 470 } 471 472 // Fill offsets if there's a child raster 473 if (src.getParent() != null || dst.getParent() != null) { 474 if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0 475 || dst.getSampleModelTranslateX() != 0 476 || dst.getSampleModelTranslateY() != 0) { 477 offsets = new int[4]; 478 offsets[0] = -src.getSampleModelTranslateX() + src.getMinX(); 479 offsets[1] = -src.getSampleModelTranslateY() + src.getMinY(); 480 offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX(); 481 offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY(); 482 } 483 } 484 } 485 } 486 487 Object srcData, dstData; 488 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); 489 try { 490 srcData = dbAccess.getData(src.getDataBuffer()); 491 dstData = dbAccess.getData(dst.getDataBuffer()); 492 } catch (IllegalArgumentException e) { 493 return -1; // Unknown data buffer type 494 } 495 496 return ippFilter32f(kernel.data, kernel.getWidth(), kernel.getHeight(), 497 kernel.getXOrigin(), kernel.getYOrigin(), edgeCond, srcData, src.getWidth(), src 498 .getHeight(), srcStride, dstData, dst.getWidth(), dst.getHeight(), 499 dstStride, channels, skipChannel, offsets); 500 } 501 502 /** 503 * Ipp filter32f. 504 * 505 * @param kernel 506 * the kernel. 507 * @param kWidth 508 * the k width. 509 * @param kHeight 510 * the k height. 511 * @param anchorX 512 * the anchor x. 513 * @param anchorY 514 * the anchor y. 515 * @param borderType 516 * the border type. 517 * @param src 518 * the src. 519 * @param srcWidth 520 * the src width. 521 * @param srcHeight 522 * the src height. 523 * @param srcStride 524 * the src stride. 525 * @param dst 526 * the dst. 527 * @param dstWidth 528 * the dst width. 529 * @param dstHeight 530 * the dst height. 531 * @param dstStride 532 * the dst stride. 533 * @param channels 534 * the channels. 535 * @param skipChannel 536 * the skip channel. 537 * @param offsets 538 * the offsets. 539 * @return the int. 540 */ 541 private native int ippFilter32f(float kernel[], int kWidth, int kHeight, int anchorX, 542 int anchorY, int borderType, Object src, int srcWidth, int srcHeight, int srcStride, 543 Object dst, int dstWidth, int dstHeight, int dstStride, int channels, 544 boolean skipChannel, int offsets[]); 545} 546