1 /* 2 3 Licensed to the Apache Software Foundation (ASF) under one or more 4 contributor license agreements. See the NOTICE file distributed with 5 this work for additional information regarding copyright ownership. 6 The ASF licenses this file to You under the Apache License, Version 2.0 7 (the "License"); you may not use this file except in compliance with 8 the License. You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 18 */ 19 package ch.randelshofer.quaqua.ext.batik.ext.awt.image; 20 21 import java.awt.Composite; 22 import java.awt.Graphics2D; 23 import java.awt.GraphicsConfiguration; 24 import java.awt.GraphicsDevice; 25 import java.awt.Point; 26 import java.awt.Rectangle; 27 import java.awt.RenderingHints; 28 import java.awt.Shape; 29 import java.awt.color.ColorSpace; 30 import java.awt.geom.AffineTransform; 31 import java.awt.geom.Rectangle2D; 32 import java.awt.image.BufferedImage; 33 import java.awt.image.ColorModel; 34 import java.awt.image.ComponentSampleModel; 35 import java.awt.image.DataBuffer; 36 import java.awt.image.DataBufferByte; 37 import java.awt.image.DataBufferInt; 38 import java.awt.image.DataBufferShort; 39 import java.awt.image.DataBufferUShort; 40 import java.awt.image.DirectColorModel; 41 import java.awt.image.Raster; 42 import java.awt.image.RenderedImage; 43 import java.awt.image.SampleModel; 44 import java.awt.image.SinglePixelPackedSampleModel; 45 import java.awt.image.WritableRaster; 46 import java.awt.image.renderable.RenderContext; 47 import java.awt.image.renderable.RenderableImage; 48 import java.lang.ref.Reference; 49 import java.lang.ref.WeakReference; 50 51 import ch.randelshofer.quaqua.ext.batik.ext.awt.RenderingHintsKeyExt; 52 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.renderable.PaintRable; 53 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.AffineRed; 54 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.Any2LsRGBRed; 55 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.Any2sRGBRed; 56 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.BufferedImageCachableRed; 57 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.CachableRed; 58 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.FormatRed; 59 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.RenderedImageCachableRed; 60 import ch.randelshofer.quaqua.ext.batik.ext.awt.image.rendered.TranslateRed; 61 62 63 /** 64 * Set of utility methods for Graphics. 65 * These generally bypass broken methods in Java2D or provide tweaked 66 * implementations. 67 * 68 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a> 69 * @version $Id: GraphicsUtil.java 498740 2007-01-22 18:35:57Z dvholten $ 70 */ 71 public class GraphicsUtil { 72 73 public static AffineTransform IDENTITY = new AffineTransform(); 74 75 /** 76 * Draws <tt>ri</tt> into <tt>g2d</tt>. It does this be 77 * requesting tiles from <tt>ri</tt> and drawing them individually 78 * in <tt>g2d</tt> it also takes care of some colorspace and alpha 79 * issues. 80 * @param g2d The Graphics2D to draw into. 81 * @param ri The image to be drawn. 82 */ drawImage(Graphics2D g2d, RenderedImage ri)83 public static void drawImage(Graphics2D g2d, 84 RenderedImage ri) { 85 drawImage(g2d, wrap(ri)); 86 } 87 88 /** 89 * Draws <tt>cr</tt> into <tt>g2d</tt>. It does this be 90 * requesting tiles from <tt>ri</tt> and drawing them individually 91 * in <tt>g2d</tt> it also takes care of some colorspace and alpha 92 * issues. 93 * @param g2d The Graphics2D to draw into. 94 * @param cr The image to be drawn. 95 */ drawImage(Graphics2D g2d, CachableRed cr)96 public static void drawImage(Graphics2D g2d, 97 CachableRed cr) { 98 99 // System.out.println("DrawImage G: " + g2d); 100 101 AffineTransform at = null; 102 while (true) { 103 if (cr instanceof AffineRed) { 104 AffineRed ar = (AffineRed)cr; 105 if (at == null) 106 at = ar.getTransform(); 107 else 108 at.concatenate(ar.getTransform()); 109 cr = ar.getSource(); 110 continue; 111 } else if (cr instanceof TranslateRed) { 112 TranslateRed tr = (TranslateRed)cr; 113 // System.out.println("testing Translate"); 114 int dx = tr.getDeltaX(); 115 int dy = tr.getDeltaY(); 116 if (at == null) 117 at = AffineTransform.getTranslateInstance(dx, dy); 118 else 119 at.translate(dx, dy); 120 cr = tr.getSource(); 121 continue; 122 } 123 break; 124 } 125 AffineTransform g2dAt = g2d.getTransform(); 126 if ((at == null) || (at.isIdentity())) 127 at = g2dAt; 128 else 129 at.preConcatenate(g2dAt); 130 131 ColorModel srcCM = cr.getColorModel(); 132 ColorModel g2dCM = getDestinationColorModel(g2d); 133 ColorSpace g2dCS = null; 134 if (g2dCM != null) 135 g2dCS = g2dCM.getColorSpace(); 136 if (g2dCS == null) 137 // Assume device is sRGB 138 g2dCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); 139 140 ColorModel drawCM = g2dCM; 141 if ((g2dCM == null) || !g2dCM.hasAlpha()) { 142 // If we can't find out about our device or the device 143 // does not support alpha just use SRGB unpremultiplied 144 // (Just because this seems to work for us). 145 drawCM = sRGB_Unpre; 146 } 147 148 if (cr instanceof BufferedImageCachableRed) { 149 // There is a huge win if we can use the BI directly here. 150 // This results in something like a 10x performance gain 151 // for images, the best thing is this is the common case. 152 if (g2dCS.equals(srcCM.getColorSpace()) && 153 drawCM.equals(srcCM)) { 154 // System.err.println("Fast Case"); 155 g2d.setTransform(at); 156 BufferedImageCachableRed bicr; 157 bicr = (BufferedImageCachableRed)cr; 158 g2d.drawImage(bicr.getBufferedImage(), 159 bicr.getMinX(), bicr.getMinY(), null); 160 g2d.setTransform(g2dAt); 161 return; 162 } 163 } 164 165 // Scaling down so do it before color conversion. 166 double determinant = at.getDeterminant(); 167 if (!at.isIdentity() && (determinant <= 1.0)) { 168 if (at.getType() != AffineTransform.TYPE_TRANSLATION) 169 cr = new AffineRed(cr, at, g2d.getRenderingHints()); 170 else { 171 int xloc = cr.getMinX() + (int)at.getTranslateX(); 172 int yloc = cr.getMinY() + (int)at.getTranslateY(); 173 cr = new TranslateRed(cr, xloc, yloc); 174 } 175 } 176 177 if (g2dCS != srcCM.getColorSpace()) { 178 // System.out.println("srcCS: " + srcCM.getColorSpace()); 179 // System.out.println("g2dCS: " + g2dCS); 180 // System.out.println("sRGB: " + 181 // ColorSpace.getInstance(ColorSpace.CS_sRGB)); 182 // System.out.println("LsRGB: " + 183 // ColorSpace.getInstance 184 // (ColorSpace.CS_LINEAR_RGB)); 185 if (g2dCS == ColorSpace.getInstance(ColorSpace.CS_sRGB)) 186 cr = convertTosRGB(cr); 187 else if (g2dCS == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) 188 cr = convertToLsRGB(cr); 189 } 190 srcCM = cr.getColorModel(); 191 if (!drawCM.equals(srcCM)) 192 cr = FormatRed.construct(cr, drawCM); 193 194 // Scaling up so do it after color conversion. 195 if (!at.isIdentity() && (determinant > 1.0)) 196 cr = new AffineRed(cr, at, g2d.getRenderingHints()); 197 198 // Now CR is in device space, so clear the g2d transform. 199 g2d.setTransform(IDENTITY); 200 201 // Ugly Hack alert. This Makes it use our SrcOver implementation 202 // Which doesn't seem to have as many bugs as the JDK one when 203 // going between different src's and destinations (of course it's 204 // also a lot slower). 205 Composite g2dComposite = g2d.getComposite(); 206 if (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) == 207 RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING) { 208 if (SVGComposite.OVER.equals(g2dComposite)) { 209 g2d.setComposite(SVGComposite.OVER); 210 } 211 } 212 Rectangle crR = cr.getBounds(); 213 Shape clip = g2d.getClip(); 214 215 try { 216 Rectangle clipR; 217 if (clip == null) { 218 clip = crR; 219 clipR = crR; 220 } else { 221 clipR = clip.getBounds(); 222 223 if ( ! clipR.intersects(crR) ) 224 return; // Nothing to draw... 225 clipR = clipR.intersection(crR); 226 } 227 228 Rectangle gcR = getDestinationBounds(g2d); 229 // System.out.println("ClipRects: " + clipR + " -> " + gcR); 230 if (gcR != null) { 231 if ( ! clipR.intersects(gcR) ) 232 return; // Nothing to draw... 233 clipR = clipR.intersection(gcR); 234 } 235 236 // System.out.println("Starting Draw: " + cr); 237 // long startTime = System.currentTimeMillis(); 238 239 boolean useDrawRenderedImage = false; 240 241 srcCM = cr.getColorModel(); 242 SampleModel srcSM = cr.getSampleModel(); 243 if ((srcSM.getWidth()*srcSM.getHeight()) >= 244 (clipR.width*clipR.height)) 245 // if srcSM tiles are around the clip size 246 // then just draw the renderedImage 247 useDrawRenderedImage = true; 248 249 Object atpHint = g2d.getRenderingHint 250 (RenderingHintsKeyExt.KEY_AVOID_TILE_PAINTING); 251 252 if (atpHint == RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_ON) 253 useDrawRenderedImage = true; //for PDF and PS transcoders 254 255 if (atpHint == RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_OFF) 256 useDrawRenderedImage = false; 257 258 259 WritableRaster wr; 260 if (useDrawRenderedImage) { 261 // This can be significantly faster but can also 262 // require much more memory, so we only use it when 263 // the clip size is smaller than the tile size. 264 Raster r = cr.getData(clipR); 265 wr = ((WritableRaster)r).createWritableChild 266 (clipR.x, clipR.y, clipR.width, clipR.height, 267 0, 0, null); 268 269 BufferedImage bi = new BufferedImage 270 (srcCM, wr, srcCM.isAlphaPremultiplied(), null); 271 272 // Any of the drawImage calls that take an 273 // Affine are prone to the 'CGGStackRestore: gstack 274 // underflow' bug on Mac OS X. This should work 275 // around that problem. 276 g2d.drawImage(bi, clipR.x, clipR.y, null); 277 } else { 278 // Use tiles to draw image... 279 wr = Raster.createWritableRaster(srcSM, new Point(0,0)); 280 BufferedImage bi = new BufferedImage 281 (srcCM, wr, srcCM.isAlphaPremultiplied(), null); 282 283 int xt0 = cr.getMinTileX(); 284 int xt1 = xt0+cr.getNumXTiles(); 285 int yt0 = cr.getMinTileY(); 286 int yt1 = yt0+cr.getNumYTiles(); 287 int tw = srcSM.getWidth(); 288 int th = srcSM.getHeight(); 289 290 Rectangle tR = new Rectangle(0,0,tw,th); 291 Rectangle iR = new Rectangle(0,0,0,0); 292 293 if (false) { 294 System.err.println("SrcCM: " + srcCM); 295 System.err.println("CR: " + cr); 296 System.err.println("CRR: " + crR + " TG: [" + 297 xt0 + ',' + 298 yt0 + ',' + 299 xt1 + ',' + 300 yt1 +"] Off: " + 301 cr.getTileGridXOffset() + ',' + 302 cr.getTileGridYOffset()); 303 } 304 305 int yloc = yt0*th+cr.getTileGridYOffset(); 306 int skip = (clipR.y-yloc)/th; 307 if (skip <0) skip = 0; 308 yt0+=skip; 309 310 int xloc = xt0*tw+cr.getTileGridXOffset(); 311 skip = (clipR.x-xloc)/tw; 312 if (skip <0) skip = 0; 313 xt0+=skip; 314 315 int endX = clipR.x+clipR.width-1; 316 int endY = clipR.y+clipR.height-1; 317 318 if (false) { 319 System.out.println("clipR: " + clipR + " TG: [" + 320 xt0 + ',' + 321 yt0 + ',' + 322 xt1 + ',' + 323 yt1 +"] Off: " + 324 cr.getTileGridXOffset() + ',' + 325 cr.getTileGridYOffset()); 326 } 327 328 329 yloc = yt0*th+cr.getTileGridYOffset(); 330 int minX = xt0*tw+cr.getTileGridXOffset(); 331 int xStep = tw; 332 xloc = minX; 333 for (int y=yt0; y<yt1; y++, yloc += th) { 334 if (yloc > endY) break; 335 for (int x=xt0; x<xt1; x++, xloc+=xStep) { 336 if ((xloc<minX) || (xloc > endX)) break; 337 tR.x = xloc; 338 tR.y = yloc; 339 Rectangle2D.intersect(crR, tR, iR); 340 341 WritableRaster twr; 342 twr = wr.createWritableChild(0, 0, 343 iR.width, iR.height, 344 iR.x, iR.y, null); 345 346 // System.out.println("Generating tile: " + twr); 347 cr.copyData(twr); 348 349 // Make sure we only draw the region that was written. 350 BufferedImage subBI; 351 subBI = bi.getSubimage(0, 0, iR.width, iR.height); 352 353 if (false) { 354 System.out.println("Drawing: " + tR); 355 System.out.println("IR: " + iR); 356 } 357 358 // For some reason using the transform version 359 // causes a gStackUnderflow error but if I just 360 // use the drawImage with an x & y it works. 361 g2d.drawImage(subBI, iR.x, iR.y, null); 362 // AffineTransform trans 363 // = AffineTransform.getTranslateInstance(iR.x, iR.y); 364 // g2d.drawImage(subBI, trans, null); 365 366 // String label = "sub [" + x + ", " + y + "]: "; 367 // org.ImageDisplay.showImage 368 // (label, subBI); 369 } 370 xStep = -xStep; // Reverse directions. 371 xloc += xStep; // Get back in bounds. 372 } 373 } 374 // long endTime = System.currentTimeMillis(); 375 // System.out.println("Time: " + (endTime-startTime)); 376 377 378 } finally { 379 g2d.setTransform(g2dAt); 380 g2d.setComposite(g2dComposite); 381 } 382 383 // System.out.println("Finished Draw"); 384 } 385 386 387 /** 388 * Draws a <tt>Filter</tt> (<tt>RenderableImage</tt>) into a 389 * Graphics 2D after taking into account a particular 390 * <tt>RenderContext</tt>.<p> 391 * 392 * This method also attempts to unwind the rendering chain a bit. 393 * So it knows about certain operations (like affine, pad, 394 * composite), rather than applying each of these operations in 395 * turn it accounts for their affects through modifications to the 396 * Graphics2D. This avoids generating lots of intermediate images. 397 * 398 * @param g2d The Graphics to draw into. 399 * @param filter The filter to draw 400 * @param rc The render context that controls the drawing operation. 401 */ drawImage(Graphics2D g2d, RenderableImage filter, RenderContext rc)402 public static void drawImage(Graphics2D g2d, 403 RenderableImage filter, 404 RenderContext rc) { 405 406 AffineTransform origDev = g2d.getTransform(); 407 Shape origClip = g2d.getClip(); 408 RenderingHints origRH = g2d.getRenderingHints(); 409 410 Shape clip = rc.getAreaOfInterest(); 411 if (clip != null) 412 g2d.clip(clip); 413 g2d.transform(rc.getTransform()); 414 g2d.setRenderingHints(rc.getRenderingHints()); 415 416 drawImage(g2d, filter); 417 418 g2d.setTransform(origDev); 419 g2d.setClip(origClip); 420 g2d.setRenderingHints(origRH); 421 } 422 423 /** 424 * Draws a <tt>Filter</tt> (<tt>RenderableImage</tt>) into a 425 * Graphics 2D.<p> 426 * 427 * This method also attempts to unwind the rendering chain a bit. 428 * So it knows about certain operations (like affine, pad, 429 * composite), rather than applying each of these operations in 430 * turn it accounts for their affects through modifications to the 431 * Graphics2D. This avoids generating lots of intermediate images. 432 * 433 * @param g2d The Graphics to draw into. 434 * @param filter The filter to draw 435 */ drawImage(Graphics2D g2d, RenderableImage filter)436 public static void drawImage(Graphics2D g2d, 437 RenderableImage filter) { 438 if (filter instanceof PaintRable) { 439 PaintRable pr = (PaintRable)filter; 440 if (pr.paintRable(g2d)) 441 // paintRable succeeded so we are done... 442 return; 443 } 444 445 // Get our sources image... 446 // System.out.println("UnOpt: " + filter); 447 AffineTransform at = g2d.getTransform(); 448 RenderedImage ri = filter.createRendering 449 (new RenderContext(at, g2d.getClip(), g2d.getRenderingHints())); 450 451 if (ri == null) 452 return; 453 454 g2d.setTransform(IDENTITY); 455 drawImage(g2d, GraphicsUtil.wrap(ri)); 456 g2d.setTransform(at); 457 } 458 459 /** 460 * This is a wrapper around the system's 461 * BufferedImage.createGraphics that arranges for bi to be stored 462 * in a Rendering hint in the returned Graphics2D. 463 * This allows for accurate determination of the 'devices' size, 464 * and colorspace. 465 * @param bi The BufferedImage that the returned Graphics should 466 * draw into. 467 * @return A Graphics2D that draws into BufferedImage with <tt>bi</tt> 468 * stored in a rendering hint. 469 */ createGraphics(BufferedImage bi, RenderingHints hints)470 public static Graphics2D createGraphics(BufferedImage bi, 471 RenderingHints hints) { 472 Graphics2D g2d = bi.createGraphics(); 473 if (hints != null) 474 g2d.addRenderingHints(hints); 475 g2d.setRenderingHint(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE, 476 new WeakReference(bi)); 477 g2d.clip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight())); 478 return g2d; 479 } 480 481 createGraphics(BufferedImage bi)482 public static Graphics2D createGraphics(BufferedImage bi) { 483 Graphics2D g2d = bi.createGraphics(); 484 g2d.setRenderingHint(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE, 485 new WeakReference(bi)); 486 g2d.clip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight())); 487 return g2d; 488 } 489 490 491 public static final boolean WARN_DESTINATION; 492 493 static { 494 boolean warn = true; 495 try { 496 String s = System.getProperty 497 ("ch.randelshofer.quaqua.ext.batik.warn_destination", "true"); 498 warn = Boolean.valueOf(s).booleanValue(); 499 } catch (SecurityException se) { 500 } catch (NumberFormatException nfe) { 501 } finally { 502 WARN_DESTINATION = warn; 503 } 504 } 505 getDestination(Graphics2D g2d)506 public static BufferedImage getDestination(Graphics2D g2d) { 507 Object o = g2d.getRenderingHint 508 (RenderingHintsKeyExt.KEY_BUFFERED_IMAGE); 509 if (o != null) 510 return (BufferedImage)(((Reference)o).get()); 511 512 // Check if this is a BufferedImage G2d if so throw an error... 513 GraphicsConfiguration gc = g2d.getDeviceConfiguration(); 514 GraphicsDevice gd = gc.getDevice(); 515 if (WARN_DESTINATION && 516 (gd.getType() == GraphicsDevice.TYPE_IMAGE_BUFFER) && 517 (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) != 518 RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING)) 519 // throw new IllegalArgumentException 520 System.err.println 521 ("Graphics2D from BufferedImage lacks BUFFERED_IMAGE hint"); 522 523 return null; 524 } 525 getDestinationColorModel(Graphics2D g2d)526 public static ColorModel getDestinationColorModel(Graphics2D g2d) { 527 BufferedImage bi = getDestination(g2d); 528 if (bi != null) 529 return bi.getColorModel(); 530 531 GraphicsConfiguration gc = g2d.getDeviceConfiguration(); 532 if (gc == null) 533 return null; // Can't tell 534 535 // We are going to a BufferedImage but no hint was provided 536 // so we can't determine the destination Color Model. 537 if (gc.getDevice().getType() == GraphicsDevice.TYPE_IMAGE_BUFFER) { 538 if (g2d.getRenderingHint(RenderingHintsKeyExt.KEY_TRANSCODING) == 539 RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING) 540 return sRGB_Unpre; 541 542 // System.out.println("CM: " + gc.getColorModel()); 543 // System.out.println("CS: " + gc.getColorModel().getColorSpace()); 544 return null; 545 } 546 547 return gc.getColorModel(); 548 } 549 getDestinationColorSpace(Graphics2D g2d)550 public static ColorSpace getDestinationColorSpace(Graphics2D g2d) { 551 ColorModel cm = getDestinationColorModel(g2d); 552 if (cm != null) return cm.getColorSpace(); 553 554 return null; 555 } 556 getDestinationBounds(Graphics2D g2d)557 public static Rectangle getDestinationBounds(Graphics2D g2d) { 558 BufferedImage bi = getDestination(g2d); 559 if (bi != null) 560 return new Rectangle(0, 0, bi.getWidth(), bi.getHeight()); 561 562 GraphicsConfiguration gc = g2d.getDeviceConfiguration(); 563 564 // We are going to a BufferedImage but no hint was provided 565 // so we can't determine the destination bounds. 566 if (gc.getDevice().getType() == GraphicsDevice.TYPE_IMAGE_BUFFER) 567 return null; 568 569 // This is a JDK 1.3ism, so we will just return null... 570 // return gc.getBounds(); 571 return null; 572 } 573 574 575 /** 576 * Standard prebuilt Linear_sRGB color model with no alpha */ 577 public static final ColorModel Linear_sRGB = 578 new DirectColorModel(ColorSpace.getInstance 579 (ColorSpace.CS_LINEAR_RGB), 24, 580 0x00FF0000, 0x0000FF00, 581 0x000000FF, 0x0, false, 582 DataBuffer.TYPE_INT); 583 /** 584 * Standard prebuilt Linear_sRGB color model with premultiplied alpha. 585 */ 586 public static final ColorModel Linear_sRGB_Pre = 587 new DirectColorModel(ColorSpace.getInstance 588 (ColorSpace.CS_LINEAR_RGB), 32, 589 0x00FF0000, 0x0000FF00, 590 0x000000FF, 0xFF000000, true, 591 DataBuffer.TYPE_INT); 592 /** 593 * Standard prebuilt Linear_sRGB color model with unpremultiplied alpha. 594 */ 595 public static final ColorModel Linear_sRGB_Unpre = 596 new DirectColorModel(ColorSpace.getInstance 597 (ColorSpace.CS_LINEAR_RGB), 32, 598 0x00FF0000, 0x0000FF00, 599 0x000000FF, 0xFF000000, false, 600 DataBuffer.TYPE_INT); 601 602 /** 603 * Standard prebuilt sRGB color model with no alpha. 604 */ 605 public static final ColorModel sRGB = 606 new DirectColorModel(ColorSpace.getInstance 607 (ColorSpace.CS_sRGB), 24, 608 0x00FF0000, 0x0000FF00, 609 0x000000FF, 0x0, false, 610 DataBuffer.TYPE_INT); 611 /** 612 * Standard prebuilt sRGB color model with premultiplied alpha. 613 */ 614 public static final ColorModel sRGB_Pre = 615 new DirectColorModel(ColorSpace.getInstance 616 (ColorSpace.CS_sRGB), 32, 617 0x00FF0000, 0x0000FF00, 618 0x000000FF, 0xFF000000, true, 619 DataBuffer.TYPE_INT); 620 /** 621 * Standard prebuilt sRGB color model with unpremultiplied alpha. 622 */ 623 public static final ColorModel sRGB_Unpre = 624 new DirectColorModel(ColorSpace.getInstance 625 (ColorSpace.CS_sRGB), 32, 626 0x00FF0000, 0x0000FF00, 627 0x000000FF, 0xFF000000, false, 628 DataBuffer.TYPE_INT); 629 630 /** 631 * Method that returns either Linear_sRGB_Pre or Linear_sRGB_UnPre 632 * based on premult flag. 633 * @param premult True if the ColorModel should have premultiplied alpha. 634 * @return a ColorMdoel with Linear sRGB colorSpace and 635 * the alpha channel set in accordance with 636 * <tt>premult</tt> 637 */ makeLinear_sRGBCM( boolean premult )638 public static ColorModel makeLinear_sRGBCM( boolean premult ) { 639 640 return premult ? Linear_sRGB_Pre : Linear_sRGB_Unpre; 641 } 642 643 /** 644 * Constructs a BufferedImage with a linear sRGB colorModel, and alpha. 645 * @param width The desired width of the BufferedImage 646 * @param height The desired height of the BufferedImage 647 * @param premult The desired state of alpha premultiplied 648 * @return The requested BufferedImage. 649 */ makeLinearBufferedImage(int width, int height, boolean premult)650 public static BufferedImage makeLinearBufferedImage(int width, 651 int height, 652 boolean premult) { 653 ColorModel cm = makeLinear_sRGBCM(premult); 654 WritableRaster wr = cm.createCompatibleWritableRaster(width, height); 655 return new BufferedImage(cm, wr, premult, null); 656 } 657 658 /** 659 * This method will return a CacheableRed that has it's data in 660 * the linear sRGB colorspace. If <tt>src</tt> is already in 661 * linear sRGB then this method does nothing and returns <tt>src</tt>. 662 * Otherwise it creates a transform that will convert 663 * <tt>src</tt>'s output to linear sRGB and returns that CacheableRed. 664 * 665 * @param src The image to convert to linear sRGB. 666 * @return An equivilant image to <tt>src</tt> who's data is in 667 * linear sRGB. 668 */ convertToLsRGB(CachableRed src)669 public static CachableRed convertToLsRGB(CachableRed src) { 670 ColorModel cm = src.getColorModel(); 671 ColorSpace cs = cm.getColorSpace(); 672 if (cs == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) 673 return src; 674 675 return new Any2LsRGBRed(src); 676 } 677 678 /** 679 * This method will return a CacheableRed that has it's data in 680 * the sRGB colorspace. If <tt>src</tt> is already in 681 * sRGB then this method does nothing and returns <tt>src</tt>. 682 * Otherwise it creates a transform that will convert 683 * <tt>src</tt>'s output to sRGB and returns that CacheableRed. 684 * 685 * @param src The image to convert to sRGB. 686 * @return An equivilant image to <tt>src</tt> who's data is in sRGB. 687 */ convertTosRGB(CachableRed src)688 public static CachableRed convertTosRGB(CachableRed src) { 689 ColorModel cm = src.getColorModel(); 690 ColorSpace cs = cm.getColorSpace(); 691 if (cs == ColorSpace.getInstance(ColorSpace.CS_sRGB)) 692 return src; 693 694 return new Any2sRGBRed(src); 695 } 696 697 /** 698 * Convertes any RenderedImage to a CacheableRed. <p> 699 * If <tt>ri</tt> is already a CacheableRed it casts it down and 700 * returns it.<p> 701 * 702 * In cases where <tt>ri</tt> is not already a CacheableRed it 703 * wraps <tt>ri</tt> with a helper class. The wrapped 704 * CacheableRed "Pretends" that it has no sources since it has no 705 * way of inteligently handling the dependency/dirty region calls 706 * if it exposed the source. 707 * @param ri The RenderedImage to convert. 708 * @return a CacheableRed that contains the same data as ri. 709 */ wrap(RenderedImage ri)710 public static CachableRed wrap(RenderedImage ri) { 711 if (ri instanceof CachableRed) 712 return (CachableRed) ri; 713 if (ri instanceof BufferedImage) 714 return new BufferedImageCachableRed((BufferedImage)ri); 715 return new RenderedImageCachableRed(ri); 716 } 717 718 /** 719 * An internal optimized version of copyData designed to work on 720 * Integer packed data with a SinglePixelPackedSampleModel. Only 721 * the region of overlap between src and dst is copied. 722 * 723 * Calls to this should be preflighted with is_INT_PACK_Data 724 * on both src and dest (requireAlpha can be false). 725 * 726 * @param src The source of the data 727 * @param dst The destination for the data. 728 */ copyData_INT_PACK(Raster src, WritableRaster dst)729 public static void copyData_INT_PACK(Raster src, WritableRaster dst) { 730 // System.out.println("Fast copyData"); 731 int x0 = dst.getMinX(); 732 if (x0 < src.getMinX()) x0 = src.getMinX(); 733 734 int y0 = dst.getMinY(); 735 if (y0 < src.getMinY()) y0 = src.getMinY(); 736 737 int x1 = dst.getMinX()+dst.getWidth()-1; 738 if (x1 > src.getMinX()+src.getWidth()-1) 739 x1 = src.getMinX()+src.getWidth()-1; 740 741 int y1 = dst.getMinY()+dst.getHeight()-1; 742 if (y1 > src.getMinY()+src.getHeight()-1) 743 y1 = src.getMinY()+src.getHeight()-1; 744 745 int width = x1-x0+1; 746 int height = y1-y0+1; 747 748 SinglePixelPackedSampleModel srcSPPSM; 749 srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel(); 750 751 final int srcScanStride = srcSPPSM.getScanlineStride(); 752 DataBufferInt srcDB = (DataBufferInt)src.getDataBuffer(); 753 final int [] srcPixels = srcDB.getBankData()[0]; 754 final int srcBase = 755 (srcDB.getOffset() + 756 srcSPPSM.getOffset(x0-src.getSampleModelTranslateX(), 757 y0-src.getSampleModelTranslateY())); 758 759 760 SinglePixelPackedSampleModel dstSPPSM; 761 dstSPPSM = (SinglePixelPackedSampleModel)dst.getSampleModel(); 762 763 final int dstScanStride = dstSPPSM.getScanlineStride(); 764 DataBufferInt dstDB = (DataBufferInt)dst.getDataBuffer(); 765 final int [] dstPixels = dstDB.getBankData()[0]; 766 final int dstBase = 767 (dstDB.getOffset() + 768 dstSPPSM.getOffset(x0-dst.getSampleModelTranslateX(), 769 y0-dst.getSampleModelTranslateY())); 770 771 if ((srcScanStride == dstScanStride) && 772 (srcScanStride == width)) { 773 // System.out.println("VERY Fast copyData"); 774 775 System.arraycopy(srcPixels, srcBase, dstPixels, dstBase, 776 width*height); 777 } else if (width > 128) { 778 int srcSP = srcBase; 779 int dstSP = dstBase; 780 for (int y=0; y<height; y++) { 781 System.arraycopy(srcPixels, srcSP, dstPixels, dstSP, width); 782 srcSP += srcScanStride; 783 dstSP += dstScanStride; 784 } 785 } else { 786 for (int y=0; y<height; y++) { 787 int srcSP = srcBase+y*srcScanStride; 788 int dstSP = dstBase+y*dstScanStride; 789 for (int x=0; x<width; x++) 790 dstPixels[dstSP++] = srcPixels[srcSP++]; 791 } 792 } 793 } 794 copyData_FALLBACK(Raster src, WritableRaster dst)795 public static void copyData_FALLBACK(Raster src, WritableRaster dst) { 796 // System.out.println("Fallback copyData"); 797 798 int x0 = dst.getMinX(); 799 if (x0 < src.getMinX()) x0 = src.getMinX(); 800 801 int y0 = dst.getMinY(); 802 if (y0 < src.getMinY()) y0 = src.getMinY(); 803 804 int x1 = dst.getMinX()+dst.getWidth()-1; 805 if (x1 > src.getMinX()+src.getWidth()-1) 806 x1 = src.getMinX()+src.getWidth()-1; 807 808 int y1 = dst.getMinY()+dst.getHeight()-1; 809 if (y1 > src.getMinY()+src.getHeight()-1) 810 y1 = src.getMinY()+src.getHeight()-1; 811 812 int width = x1-x0+1; 813 int [] data = null; 814 815 for (int y = y0; y <= y1 ; y++) { 816 data = src.getPixels(x0,y,width,1,data); 817 dst.setPixels (x0,y,width,1,data); 818 } 819 } 820 821 /** 822 * Copies data from one raster to another. Only the region of 823 * overlap between src and dst is copied. <tt>Src</tt> and 824 * <tt>Dst</tt> must have compatible SampleModels. 825 * 826 * @param src The source of the data 827 * @param dst The destination for the data. 828 */ copyData(Raster src, WritableRaster dst)829 public static void copyData(Raster src, WritableRaster dst) { 830 if (is_INT_PACK_Data(src.getSampleModel(), false) && 831 is_INT_PACK_Data(dst.getSampleModel(), false)) { 832 copyData_INT_PACK(src, dst); 833 return; 834 } 835 836 copyData_FALLBACK(src, dst); 837 } 838 839 /** 840 * Creates a new raster that has a <b>copy</b> of the data in 841 * <tt>ras</tt>. This is highly optimized for speed. There is 842 * no provision for changing any aspect of the SampleModel. 843 * 844 * This method should be used when you need to change the contents 845 * of a Raster that you do not "own" (ie the result of a 846 * <tt>getData</tt> call). 847 * @param ras The Raster to copy. 848 * @return A writable copy of <tt>ras</tt> 849 */ copyRaster(Raster ras)850 public static WritableRaster copyRaster(Raster ras) { 851 return copyRaster(ras, ras.getMinX(), ras.getMinY()); 852 } 853 854 855 /** 856 * Creates a new raster that has a <b>copy</b> of the data in 857 * <tt>ras</tt>. This is highly optimized for speed. There is 858 * no provision for changing any aspect of the SampleModel. 859 * However you can specify a new location for the returned raster. 860 * 861 * This method should be used when you need to change the contents 862 * of a Raster that you do not "own" (ie the result of a 863 * <tt>getData</tt> call). 864 * 865 * @param ras The Raster to copy. 866 * 867 * @param minX The x location for the upper left corner of the 868 * returned WritableRaster. 869 * 870 * @param minY The y location for the upper left corner of the 871 * returned WritableRaster. 872 * 873 * @return A writable copy of <tt>ras</tt> 874 */ copyRaster(Raster ras, int minX, int minY)875 public static WritableRaster copyRaster(Raster ras, int minX, int minY) { 876 WritableRaster ret = Raster.createWritableRaster 877 (ras.getSampleModel(), 878 new Point(0,0)); 879 ret = ret.createWritableChild 880 (ras.getMinX()-ras.getSampleModelTranslateX(), 881 ras.getMinY()-ras.getSampleModelTranslateY(), 882 ras.getWidth(), ras.getHeight(), 883 minX, minY, null); 884 885 // Use System.arraycopy to copy the data between the two... 886 DataBuffer srcDB = ras.getDataBuffer(); 887 DataBuffer retDB = ret.getDataBuffer(); 888 if (srcDB.getDataType() != retDB.getDataType()) { 889 throw new IllegalArgumentException 890 ("New DataBuffer doesn't match original"); 891 } 892 int len = srcDB.getSize(); 893 int banks = srcDB.getNumBanks(); 894 int [] offsets = srcDB.getOffsets(); 895 for (int b=0; b< banks; b++) { 896 switch (srcDB.getDataType()) { 897 case DataBuffer.TYPE_BYTE: { 898 DataBufferByte srcDBT = (DataBufferByte)srcDB; 899 DataBufferByte retDBT = (DataBufferByte)retDB; 900 System.arraycopy(srcDBT.getData(b), offsets[b], 901 retDBT.getData(b), offsets[b], len); 902 break; 903 } 904 case DataBuffer.TYPE_INT: { 905 DataBufferInt srcDBT = (DataBufferInt)srcDB; 906 DataBufferInt retDBT = (DataBufferInt)retDB; 907 System.arraycopy(srcDBT.getData(b), offsets[b], 908 retDBT.getData(b), offsets[b], len); 909 break; 910 } 911 case DataBuffer.TYPE_SHORT: { 912 DataBufferShort srcDBT = (DataBufferShort)srcDB; 913 DataBufferShort retDBT = (DataBufferShort)retDB; 914 System.arraycopy(srcDBT.getData(b), offsets[b], 915 retDBT.getData(b), offsets[b], len); 916 break; 917 } 918 case DataBuffer.TYPE_USHORT: { 919 DataBufferUShort srcDBT = (DataBufferUShort)srcDB; 920 DataBufferUShort retDBT = (DataBufferUShort)retDB; 921 System.arraycopy(srcDBT.getData(b), offsets[b], 922 retDBT.getData(b), offsets[b], len); 923 break; 924 } 925 } 926 } 927 928 return ret; 929 } 930 931 /** 932 * Coerces <tt>ras</tt> to be writable. The returned Raster continues to 933 * reference the DataBuffer from ras, so modifications to the returned 934 * WritableRaster will be seen in ras.<p> 935 * 936 * This method should only be used if you need a WritableRaster due to 937 * an interface (such as to construct a BufferedImage), but have no 938 * intention of modifying the contents of the returned Raster. If 939 * you have any doubt about other users of the data in <tt>ras</tt>, 940 * use copyRaster (above). 941 * @param ras The raster to make writable. 942 * @return A Writable version of ras (shares DataBuffer with 943 * <tt>ras</tt>). 944 */ makeRasterWritable(Raster ras)945 public static WritableRaster makeRasterWritable(Raster ras) { 946 return makeRasterWritable(ras, ras.getMinX(), ras.getMinY()); 947 } 948 949 /** 950 * Coerces <tt>ras</tt> to be writable. The returned Raster continues to 951 * reference the DataBuffer from ras, so modifications to the returned 952 * WritableRaster will be seen in ras.<p> 953 * 954 * You can specify a new location for the returned WritableRaster, this 955 * is especially useful for constructing BufferedImages which require 956 * the Raster to be at (0,0). 957 * 958 * This method should only be used if you need a WritableRaster due to 959 * an interface (such as to construct a BufferedImage), but have no 960 * intention of modifying the contents of the returned Raster. If 961 * you have any doubt about other users of the data in <tt>ras</tt>, 962 * use copyRaster (above). 963 * 964 * @param ras The raster to make writable. 965 * 966 * @param minX The x location for the upper left corner of the 967 * returned WritableRaster. 968 * 969 * @param minY The y location for the upper left corner of the 970 * returned WritableRaster. 971 * 972 * @return A Writable version of <tT>ras</tt> with it's upper left 973 * hand coordinate set to minX, minY (shares it's DataBuffer 974 * with <tt>ras</tt>). 975 */ makeRasterWritable(Raster ras, int minX, int minY)976 public static WritableRaster makeRasterWritable(Raster ras, 977 int minX, int minY) { 978 WritableRaster ret = Raster.createWritableRaster 979 (ras.getSampleModel(), 980 ras.getDataBuffer(), 981 new Point(0,0)); 982 ret = ret.createWritableChild 983 (ras.getMinX()-ras.getSampleModelTranslateX(), 984 ras.getMinY()-ras.getSampleModelTranslateY(), 985 ras.getWidth(), ras.getHeight(), 986 minX, minY, null); 987 return ret; 988 } 989 990 /** 991 * Create a new ColorModel with it's alpha premultiplied state matching 992 * newAlphaPreMult. 993 * @param cm The ColorModel to change the alpha premult state of. 994 * @param newAlphaPreMult The new state of alpha premult. 995 * @return A new colorModel that has isAlphaPremultiplied() 996 * equal to newAlphaPreMult. 997 */ 998 public static ColorModel coerceColorModel(ColorModel cm, boolean newAlphaPreMult)999 coerceColorModel(ColorModel cm, boolean newAlphaPreMult) { 1000 if (cm.isAlphaPremultiplied() == newAlphaPreMult) 1001 return cm; 1002 1003 // Easiest way to build proper colormodel for new Alpha state... 1004 // Eventually this should switch on known ColorModel types and 1005 // only fall back on this hack when the CM type is unknown. 1006 WritableRaster wr = cm.createCompatibleWritableRaster(1,1); 1007 return cm.coerceData(wr, newAlphaPreMult); 1008 } 1009 1010 /** 1011 * Coerces data within a bufferedImage to match newAlphaPreMult, 1012 * Note that this can not change the colormodel of bi so you 1013 * 1014 * @param wr The raster to change the state of. 1015 * @param cm The colormodel currently associated with data in wr. 1016 * @param newAlphaPreMult The desired state of alpha Premult for raster. 1017 * @return A new colormodel that matches newAlphaPreMult. 1018 */ 1019 public static ColorModel coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult)1020 coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult) { 1021 1022 // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() + 1023 // " Out: " + newAlphaPreMult); 1024 if ( ! cm.hasAlpha() ) 1025 // Nothing to do no alpha channel 1026 return cm; 1027 1028 if (cm.isAlphaPremultiplied() == newAlphaPreMult) 1029 // nothing to do alpha state matches... 1030 return cm; 1031 1032 // System.out.println("CoerceData: " + wr.getSampleModel()); 1033 1034 if (newAlphaPreMult) { 1035 multiplyAlpha(wr); 1036 } else { 1037 divideAlpha(wr); 1038 } 1039 1040 return coerceColorModel(cm, newAlphaPreMult); 1041 } 1042 multiplyAlpha(WritableRaster wr)1043 public static void multiplyAlpha(WritableRaster wr) { 1044 if (is_BYTE_COMP_Data(wr.getSampleModel())) 1045 mult_BYTE_COMP_Data(wr); 1046 else if (is_INT_PACK_Data(wr.getSampleModel(), true)) 1047 mult_INT_PACK_Data(wr); 1048 else { 1049 int [] pixel = null; 1050 int bands = wr.getNumBands(); 1051 float norm = 1.0f/255f; 1052 int x0, x1, y0, y1, a, b; 1053 float alpha; 1054 x0 = wr.getMinX(); 1055 x1 = x0+wr.getWidth(); 1056 y0 = wr.getMinY(); 1057 y1 = y0+wr.getHeight(); 1058 for (int y=y0; y<y1; y++) 1059 for (int x=x0; x<x1; x++) { 1060 pixel = wr.getPixel(x,y,pixel); 1061 a = pixel[bands-1]; 1062 if ((a >= 0) && (a < 255)) { 1063 alpha = a*norm; 1064 for (b=0; b<bands-1; b++) 1065 pixel[b] = (int)(pixel[b]*alpha+0.5f); 1066 wr.setPixel(x,y,pixel); 1067 } 1068 } 1069 } 1070 } 1071 divideAlpha(WritableRaster wr)1072 public static void divideAlpha(WritableRaster wr) { 1073 if (is_BYTE_COMP_Data(wr.getSampleModel())) 1074 divide_BYTE_COMP_Data(wr); 1075 else if (is_INT_PACK_Data(wr.getSampleModel(), true)) 1076 divide_INT_PACK_Data(wr); 1077 else { 1078 int x0, x1, y0, y1, a, b; 1079 float ialpha; 1080 int bands = wr.getNumBands(); 1081 int [] pixel = null; 1082 1083 x0 = wr.getMinX(); 1084 x1 = x0+wr.getWidth(); 1085 y0 = wr.getMinY(); 1086 y1 = y0+wr.getHeight(); 1087 for (int y=y0; y<y1; y++) 1088 for (int x=x0; x<x1; x++) { 1089 pixel = wr.getPixel(x,y,pixel); 1090 a = pixel[bands-1]; 1091 if ((a > 0) && (a < 255)) { 1092 ialpha = 255/(float)a; 1093 for (b=0; b<bands-1; b++) 1094 pixel[b] = (int)(pixel[b]*ialpha+0.5f); 1095 wr.setPixel(x,y,pixel); 1096 } 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Copies data from one bufferedImage to another paying attention 1103 * to the state of AlphaPreMultiplied. 1104 * 1105 * @param src The source 1106 * @param dst The destination 1107 */ 1108 public static void copyData(BufferedImage src, BufferedImage dst)1109 copyData(BufferedImage src, BufferedImage dst) { 1110 Rectangle srcRect = new Rectangle(0, 0, 1111 src.getWidth(), src.getHeight()); 1112 copyData(src, srcRect, dst, new Point(0,0)); 1113 } 1114 1115 1116 /** 1117 * Copies data from one bufferedImage to another paying attention 1118 * to the state of AlphaPreMultiplied. 1119 * 1120 * @param src The source 1121 * @param srcRect The Rectangle of source data to be copied 1122 * @param dst The destination 1123 * @param destP The Place for the upper left corner of srcRect in dst. 1124 */ 1125 public static void copyData(BufferedImage src, Rectangle srcRect, BufferedImage dst, Point destP)1126 copyData(BufferedImage src, Rectangle srcRect, 1127 BufferedImage dst, Point destP) { 1128 1129 /* 1130 if (srcCS != dstCS) 1131 throw new IllegalArgumentException 1132 ("Images must be in the same ColorSpace in order "+ 1133 "to copy Data between them"); 1134 */ 1135 boolean srcAlpha = src.getColorModel().hasAlpha(); 1136 boolean dstAlpha = dst.getColorModel().hasAlpha(); 1137 1138 // System.out.println("Src has: " + srcAlpha + 1139 // " is: " + src.isAlphaPremultiplied()); 1140 // 1141 // System.out.println("Dst has: " + dstAlpha + 1142 // " is: " + dst.isAlphaPremultiplied()); 1143 1144 if (srcAlpha == dstAlpha) 1145 if (( ! srcAlpha ) || 1146 (src.isAlphaPremultiplied() == dst.isAlphaPremultiplied())) { 1147 // They match one another so just copy everything... 1148 copyData(src.getRaster(), dst.getRaster()); 1149 return; 1150 } 1151 1152 // System.out.println("Using Slow CopyData"); 1153 1154 int [] pixel = null; 1155 Raster srcR = src.getRaster(); 1156 WritableRaster dstR = dst.getRaster(); 1157 int bands = dstR.getNumBands(); 1158 1159 int dx = destP.x-srcRect.x; 1160 int dy = destP.y-srcRect.y; 1161 1162 int w = srcRect.width; 1163 int x0 = srcRect.x; 1164 int y0 = srcRect.y; 1165 int y1 = y0+srcRect.height-1; 1166 1167 if (!srcAlpha) { 1168 // Src has no alpha dest does so set alpha to 1.0 everywhere. 1169 // System.out.println("Add Alpha"); 1170 int [] oPix = new int[bands*w]; 1171 int out = (w*bands)-1; // The 2 skips alpha channel 1172 while(out >= 0) { 1173 // Fill alpha channel with 255's 1174 oPix[out] = 255; 1175 out -= bands; 1176 } 1177 1178 int b, in; 1179 for (int y=y0; y<=y1; y++) { 1180 pixel = srcR.getPixels(x0,y,w,1,pixel); 1181 in = w*(bands-1)-1; 1182 out = (w*bands)-2; // The 2 skips alpha channel on last pix 1183 switch (bands) { 1184 case 4: 1185 while(in >= 0) { 1186 oPix[out--] = pixel[in--]; 1187 oPix[out--] = pixel[in--]; 1188 oPix[out--] = pixel[in--]; 1189 out--; 1190 } 1191 break; 1192 default: 1193 while(in >= 0) { 1194 for (b=0; b<bands-1; b++) 1195 oPix[out--] = pixel[in--]; 1196 out--; 1197 } 1198 } 1199 dstR.setPixels(x0+dx, y+dy, w, 1, oPix); 1200 } 1201 } else if (dstAlpha && dst.isAlphaPremultiplied()) { 1202 // Src and dest have Alpha but we need to multiply it for dst. 1203 // System.out.println("Mult Case"); 1204 int a, b, alpha, in, fpNorm = (1<<24)/255, pt5 = 1<<23; 1205 for (int y=y0; y<=y1; y++) { 1206 pixel = srcR.getPixels(x0,y,w,1,pixel); 1207 in=bands*w-1; 1208 switch (bands) { 1209 case 4: 1210 while(in >= 0) { 1211 a = pixel[in]; 1212 if (a == 255) 1213 in -= 4; 1214 else { 1215 in--; 1216 alpha = fpNorm*a; 1217 pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--; 1218 pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--; 1219 pixel[in] = (pixel[in]*alpha+pt5)>>>24; in--; 1220 } 1221 } 1222 break; 1223 default: 1224 while(in >= 0) { 1225 a = pixel[in]; 1226 if (a == 255) 1227 in -= bands; 1228 else { 1229 in--; 1230 alpha = fpNorm*a; 1231 for (b=0; b<bands-1; b++) { 1232 pixel[in] = (pixel[in]*alpha+pt5)>>>24; 1233 in--; 1234 } 1235 } 1236 } 1237 } 1238 dstR.setPixels(x0+dx, y+dy, w, 1, pixel); 1239 } 1240 } else if (dstAlpha && !dst.isAlphaPremultiplied()) { 1241 // Src and dest have Alpha but we need to divide it out for dst. 1242 // System.out.println("Div Case"); 1243 int a, b, ialpha, in, fpNorm = 0x00FF0000, pt5 = 1<<15; 1244 for (int y=y0; y<=y1; y++) { 1245 pixel = srcR.getPixels(x0,y,w,1,pixel); 1246 in=(bands*w)-1; 1247 switch(bands) { 1248 case 4: 1249 while(in >= 0) { 1250 a = pixel[in]; 1251 if ((a <= 0) || (a >= 255)) 1252 in -= 4; 1253 else { 1254 in--; 1255 ialpha = fpNorm/a; 1256 pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--; 1257 pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--; 1258 pixel[in] = (pixel[in]*ialpha+pt5)>>>16; in--; 1259 } 1260 } 1261 break; 1262 default: 1263 while(in >= 0) { 1264 a = pixel[in]; 1265 if ((a <= 0) || (a >= 255)) 1266 in -= bands; 1267 else { 1268 in--; 1269 ialpha = fpNorm/a; 1270 for (b=0; b<bands-1; b++) { 1271 pixel[in] = (pixel[in]*ialpha+pt5)>>>16; 1272 in--; 1273 } 1274 } 1275 } 1276 } 1277 dstR.setPixels(x0+dx, y+dy, w, 1, pixel); 1278 } 1279 } else if (src.isAlphaPremultiplied()) { 1280 int [] oPix = new int[bands*w]; 1281 // Src has alpha dest does not so unpremult and store... 1282 // System.out.println("Remove Alpha, Div Case"); 1283 int a, b, ialpha, in, out, fpNorm = 0x00FF0000, pt5 = 1<<15; 1284 for (int y=y0; y<=y1; y++) { 1285 pixel = srcR.getPixels(x0,y,w,1,pixel); 1286 in = (bands+1)*w -1; 1287 out = (bands*w)-1; 1288 while(in >= 0) { 1289 a = pixel[in]; in--; 1290 if (a > 0) { 1291 if (a < 255) { 1292 ialpha = fpNorm/a; 1293 for (b=0; b<bands; b++) 1294 oPix[out--] = (pixel[in--]*ialpha+pt5)>>>16; 1295 } else 1296 for (b=0; b<bands; b++) 1297 oPix[out--] = pixel[in--]; 1298 } else { 1299 in -= bands; 1300 for (b=0; b<bands; b++) 1301 oPix[out--] = 255; 1302 } 1303 } 1304 dstR.setPixels(x0+dx, y+dy, w, 1, oPix); 1305 } 1306 } else { 1307 // Src has unpremult alpha, dest does not have alpha, 1308 // just copy the color channels over. 1309 Rectangle dstRect = new Rectangle(destP.x, destP.y, 1310 srcRect.width, srcRect.height); 1311 for (int b=0; b<bands; b++) 1312 copyBand(srcR, srcRect, b, 1313 dstR, dstRect, b); 1314 } 1315 } 1316 copyBand(Raster src, int srcBand, WritableRaster dst, int dstBand)1317 public static void copyBand(Raster src, int srcBand, 1318 WritableRaster dst, int dstBand) { 1319 1320 Rectangle sR = src.getBounds(); 1321 Rectangle dR = dst.getBounds(); 1322 Rectangle cpR = sR.intersection(dR); 1323 1324 copyBand(src, cpR, srcBand, dst, cpR, dstBand); 1325 } 1326 copyBand(Raster src, Rectangle sR, int sBand, WritableRaster dst, Rectangle dR, int dBand)1327 public static void copyBand(Raster src, Rectangle sR, int sBand, 1328 WritableRaster dst, Rectangle dR, int dBand) { 1329 int dy = dR.y -sR.y; 1330 int dx = dR.x -sR.x; 1331 sR = sR.intersection(src.getBounds()); 1332 dR = dR.intersection(dst.getBounds()); 1333 int width, height; 1334 if (dR.width < sR.width) width = dR.width; 1335 else width = sR.width; 1336 if (dR.height < sR.height) height = dR.height; 1337 else height = sR.height; 1338 1339 int x = sR.x+dx; 1340 int [] samples = null; 1341 for (int y=sR.y; y< sR.y+height; y++) { 1342 samples = src.getSamples(sR.x, y, width, 1, sBand, samples); 1343 dst.setSamples(x, y+dy, width, 1, dBand, samples); 1344 } 1345 } 1346 is_INT_PACK_Data(SampleModel sm, boolean requireAlpha)1347 public static boolean is_INT_PACK_Data(SampleModel sm, 1348 boolean requireAlpha) { 1349 // Check ColorModel is of type DirectColorModel 1350 if(!(sm instanceof SinglePixelPackedSampleModel)) return false; 1351 1352 // Check transfer type 1353 if(sm.getDataType() != DataBuffer.TYPE_INT) return false; 1354 1355 SinglePixelPackedSampleModel sppsm; 1356 sppsm = (SinglePixelPackedSampleModel)sm; 1357 1358 int [] masks = sppsm.getBitMasks(); 1359 if (masks.length == 3) { 1360 if (requireAlpha) return false; 1361 } else if (masks.length != 4) 1362 return false; 1363 1364 if(masks[0] != 0x00ff0000) return false; 1365 if(masks[1] != 0x0000ff00) return false; 1366 if(masks[2] != 0x000000ff) return false; 1367 if ((masks.length == 4) && 1368 (masks[3] != 0xff000000)) return false; 1369 1370 return true; 1371 } 1372 is_BYTE_COMP_Data(SampleModel sm)1373 public static boolean is_BYTE_COMP_Data(SampleModel sm) { 1374 // Check ColorModel is of type DirectColorModel 1375 if(!(sm instanceof ComponentSampleModel)) return false; 1376 1377 // Check transfer type 1378 if(sm.getDataType() != DataBuffer.TYPE_BYTE) return false; 1379 1380 return true; 1381 } 1382 divide_INT_PACK_Data(WritableRaster wr)1383 protected static void divide_INT_PACK_Data(WritableRaster wr) { 1384 // System.out.println("Divide Int"); 1385 1386 SinglePixelPackedSampleModel sppsm; 1387 sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel(); 1388 1389 final int width = wr.getWidth(); 1390 1391 final int scanStride = sppsm.getScanlineStride(); 1392 DataBufferInt db = (DataBufferInt)wr.getDataBuffer(); 1393 final int base 1394 = (db.getOffset() + 1395 sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(), 1396 wr.getMinY()-wr.getSampleModelTranslateY())); 1397 1398 // Access the pixel data array 1399 final int[] pixels = db.getBankData()[0]; 1400 for (int y=0; y<wr.getHeight(); y++) { 1401 int sp = base + y*scanStride; 1402 final int end = sp + width; 1403 while (sp < end) { 1404 int pixel = pixels[sp]; 1405 int a = pixel>>>24; 1406 if (a<=0) { 1407 pixels[sp] = 0x00FFFFFF; 1408 } else if (a<255) { 1409 int aFP = (0x00FF0000/a); 1410 pixels[sp] = 1411 ((a << 24) | 1412 (((((pixel&0xFF0000)>>16)*aFP)&0xFF0000) ) | 1413 (((((pixel&0x00FF00)>>8) *aFP)&0xFF0000)>>8 ) | 1414 (((((pixel&0x0000FF)) *aFP)&0xFF0000)>>16)); 1415 } 1416 sp++; 1417 } 1418 } 1419 } 1420 mult_INT_PACK_Data(WritableRaster wr)1421 protected static void mult_INT_PACK_Data(WritableRaster wr) { 1422 // System.out.println("Multiply Int: " + wr); 1423 1424 SinglePixelPackedSampleModel sppsm; 1425 sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel(); 1426 1427 final int width = wr.getWidth(); 1428 1429 final int scanStride = sppsm.getScanlineStride(); 1430 DataBufferInt db = (DataBufferInt)wr.getDataBuffer(); 1431 final int base 1432 = (db.getOffset() + 1433 sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(), 1434 wr.getMinY()-wr.getSampleModelTranslateY())); 1435 // Access the pixel data array 1436 final int[] pixels = db.getBankData()[0]; 1437 for (int y=0; y<wr.getHeight(); y++) { 1438 int sp = base + y*scanStride; 1439 final int end = sp + width; 1440 while (sp < end) { 1441 int pixel = pixels[sp]; 1442 int a = pixel>>>24; 1443 if ((a>=0) && (a<255)) { // this does NOT include a == 255 (0xff) ! 1444 pixels[sp] = ((a << 24) | 1445 ((((pixel&0xFF0000)*a)>>8)&0xFF0000) | 1446 ((((pixel&0x00FF00)*a)>>8)&0x00FF00) | 1447 ((((pixel&0x0000FF)*a)>>8)&0x0000FF)); 1448 } 1449 sp++; 1450 } 1451 } 1452 } 1453 1454 divide_BYTE_COMP_Data(WritableRaster wr)1455 protected static void divide_BYTE_COMP_Data(WritableRaster wr) { 1456 // System.out.println("Multiply Int: " + wr); 1457 1458 ComponentSampleModel csm; 1459 csm = (ComponentSampleModel)wr.getSampleModel(); 1460 1461 final int width = wr.getWidth(); 1462 1463 final int scanStride = csm.getScanlineStride(); 1464 final int pixStride = csm.getPixelStride(); 1465 final int [] bandOff = csm.getBandOffsets(); 1466 1467 DataBufferByte db = (DataBufferByte)wr.getDataBuffer(); 1468 final int base 1469 = (db.getOffset() + 1470 csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(), 1471 wr.getMinY()-wr.getSampleModelTranslateY())); 1472 1473 int aOff = bandOff[bandOff.length-1]; 1474 int bands = bandOff.length-1; 1475 1476 // Access the pixel data array 1477 final byte[] pixels = db.getBankData()[0]; 1478 for (int y=0; y<wr.getHeight(); y++) { 1479 int sp = base + y*scanStride; 1480 final int end = sp + width*pixStride; 1481 while (sp < end) { 1482 int a = pixels[sp+aOff]&0xFF; 1483 if (a==0) { 1484 for ( int b=0; b<bands; b++) 1485 pixels[sp+bandOff[b]] = (byte)0xFF; 1486 } else if (a<255) { // this does NOT include a == 255 (0xff) ! 1487 int aFP = (0x00FF0000/a); 1488 for ( int b=0; b<bands; b++) { 1489 int i = sp+bandOff[b]; 1490 pixels[i] = (byte)(((pixels[i]&0xFF)*aFP)>>>16); 1491 } 1492 } 1493 sp+=pixStride; 1494 } 1495 } 1496 } 1497 mult_BYTE_COMP_Data(WritableRaster wr)1498 protected static void mult_BYTE_COMP_Data(WritableRaster wr) { 1499 // System.out.println("Multiply Int: " + wr); 1500 1501 ComponentSampleModel csm; 1502 csm = (ComponentSampleModel)wr.getSampleModel(); 1503 1504 final int width = wr.getWidth(); 1505 1506 final int scanStride = csm.getScanlineStride(); 1507 final int pixStride = csm.getPixelStride(); 1508 final int [] bandOff = csm.getBandOffsets(); 1509 1510 DataBufferByte db = (DataBufferByte)wr.getDataBuffer(); 1511 final int base 1512 = (db.getOffset() + 1513 csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(), 1514 wr.getMinY()-wr.getSampleModelTranslateY())); 1515 1516 1517 int aOff = bandOff[bandOff.length-1]; 1518 int bands = bandOff.length-1; 1519 1520 // Access the pixel data array 1521 final byte[] pixels = db.getBankData()[0]; 1522 for (int y=0; y<wr.getHeight(); y++) { 1523 int sp = base + y*scanStride; 1524 final int end = sp + width*pixStride; 1525 while (sp < end) { 1526 int a = pixels[sp+aOff]&0xFF; 1527 if (a!=0xFF) 1528 for ( int b=0; b<bands; b++) { 1529 int i = sp+bandOff[b]; 1530 pixels[i] = (byte)(((pixels[i]&0xFF)*a)>>8); 1531 } 1532 sp+=pixStride; 1533 } 1534 } 1535 } 1536 1537 /* 1538 This is skanky debugging code that might be useful in the future: 1539 1540 if (count == 33) { 1541 String label = "sub [" + x + ", " + y + "]: "; 1542 org.ImageDisplay.showImage 1543 (label, subBI); 1544 org.ImageDisplay.printImage 1545 (label, subBI, 1546 new Rectangle(75-iR.x, 90-iR.y, 32, 32)); 1547 1548 } 1549 1550 1551 // if ((count++ % 50) == 10) 1552 // org.ImageDisplay.showImage("foo: ", subBI); 1553 1554 1555 Graphics2D realG2D = g2d; 1556 while (realG2D instanceof sun.java2d.ProxyGraphics2D) { 1557 realG2D = ((sun.java2d.ProxyGraphics2D)realG2D).getDelegate(); 1558 } 1559 if (realG2D instanceof sun.awt.image.BufferedImageGraphics2D) { 1560 count++; 1561 if (count == 34) { 1562 RenderedImage ri; 1563 ri = ((sun.awt.image.BufferedImageGraphics2D)realG2D).bufImg; 1564 // g2d.setComposite(SVGComposite.OVER); 1565 // org.ImageDisplay.showImage("Bar: " + count, cr); 1566 org.ImageDisplay.printImage("Bar: " + count, cr, 1567 new Rectangle(75, 90, 32, 32)); 1568 1569 org.ImageDisplay.showImage ("Foo: " + count, ri); 1570 org.ImageDisplay.printImage("Foo: " + count, ri, 1571 new Rectangle(75, 90, 32, 32)); 1572 1573 System.out.println("BI: " + ri); 1574 System.out.println("BISM: " + ri.getSampleModel()); 1575 System.out.println("BICM: " + ri.getColorModel()); 1576 System.out.println("BICM class: " + ri.getColorModel().getClass()); 1577 System.out.println("BICS: " + ri.getColorModel().getColorSpace()); 1578 System.out.println 1579 ("sRGB CS: " + 1580 ColorSpace.getInstance(ColorSpace.CS_sRGB)); 1581 System.out.println("G2D info"); 1582 System.out.println("\tComposite: " + g2d.getComposite()); 1583 System.out.println("\tTransform" + g2d.getTransform()); 1584 java.awt.RenderingHints rh = g2d.getRenderingHints(); 1585 java.util.Set keys = rh.keySet(); 1586 java.util.Iterator iter = keys.iterator(); 1587 while (iter.hasNext()) { 1588 Object o = iter.next(); 1589 1590 System.out.println("\t" + o.toString() + " -> " + 1591 rh.get(o).toString()); 1592 } 1593 1594 ri = cr; 1595 System.out.println("RI: " + ri); 1596 System.out.println("RISM: " + ri.getSampleModel()); 1597 System.out.println("RICM: " + ri.getColorModel()); 1598 System.out.println("RICM class: " + ri.getColorModel().getClass()); 1599 System.out.println("RICS: " + ri.getColorModel().getColorSpace()); 1600 } 1601 } 1602 */ 1603 1604 } 1605