1 /* 2 * Copyright (c) 1995, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.image; 27 28 import java.awt.image.ImageConsumer; 29 import java.awt.image.ImageProducer; 30 import java.awt.image.ColorModel; 31 import java.util.Hashtable; 32 import java.util.Vector; 33 import java.util.Enumeration; 34 35 /** 36 * This class is an implementation of the ImageProducer interface which 37 * uses an array to produce pixel values for an Image. Here is an example 38 * which calculates a 100x100 image representing a fade from black to blue 39 * along the X axis and a fade from black to red along the Y axis: 40 * <pre>{@code 41 * 42 * int w = 100; 43 * int h = 100; 44 * int pix[] = new int[w * h]; 45 * int index = 0; 46 * for (int y = 0; y < h; y++) { 47 * int red = (y * 255) / (h - 1); 48 * for (int x = 0; x < w; x++) { 49 * int blue = (x * 255) / (w - 1); 50 * pix[index++] = (255 << 24) | (red << 16) | blue; 51 * } 52 * } 53 * Image img = createImage(new MemoryImageSource(w, h, pix, 0, w)); 54 * 55 * }</pre> 56 * The MemoryImageSource is also capable of managing a memory image which 57 * varies over time to allow animation or custom rendering. Here is an 58 * example showing how to set up the animation source and signal changes 59 * in the data (adapted from the MemoryAnimationSourceDemo by Garth Dickie): 60 * <pre>{@code 61 * 62 * int pixels[]; 63 * MemoryImageSource source; 64 * 65 * public void init() { 66 * int width = 50; 67 * int height = 50; 68 * int size = width * height; 69 * pixels = new int[size]; 70 * 71 * int value = getBackground().getRGB(); 72 * for (int i = 0; i < size; i++) { 73 * pixels[i] = value; 74 * } 75 * 76 * source = new MemoryImageSource(width, height, pixels, 0, width); 77 * source.setAnimated(true); 78 * image = createImage(source); 79 * } 80 * 81 * public void run() { 82 * Thread me = Thread.currentThread( ); 83 * me.setPriority(Thread.MIN_PRIORITY); 84 * 85 * while (true) { 86 * try { 87 * Thread.sleep(10); 88 * } catch( InterruptedException e ) { 89 * return; 90 * } 91 * 92 * // Modify the values in the pixels array at (x, y, w, h) 93 * 94 * // Send the new data to the interested ImageConsumers 95 * source.newPixels(x, y, w, h); 96 * } 97 * } 98 * 99 * }</pre> 100 * 101 * @see ImageProducer 102 * 103 * @author Jim Graham 104 * @author Animation capabilities inspired by the 105 * MemoryAnimationSource class written by Garth Dickie 106 */ 107 public class MemoryImageSource implements ImageProducer { 108 int width; 109 int height; 110 ColorModel model; 111 Object pixels; 112 int pixeloffset; 113 int pixelscan; 114 Hashtable properties; 115 Vector theConsumers = new Vector(); 116 boolean animating; 117 boolean fullbuffers; 118 119 /** 120 * Constructs an ImageProducer object which uses an array of bytes 121 * to produce data for an Image object. 122 * @param w the width of the rectangle of pixels 123 * @param h the height of the rectangle of pixels 124 * @param cm the specified <code>ColorModel</code> 125 * @param pix an array of pixels 126 * @param off the offset into the array of where to store the 127 * first pixel 128 * @param scan the distance from one row of pixels to the next in 129 * the array 130 * @see java.awt.Component#createImage 131 */ 132 public MemoryImageSource(int w, int h, ColorModel cm, 133 byte[] pix, int off, int scan) { 134 initialize(w, h, cm, (Object) pix, off, scan, null); 135 } 136 137 /** 138 * Constructs an ImageProducer object which uses an array of bytes 139 * to produce data for an Image object. 140 * @param w the width of the rectangle of pixels 141 * @param h the height of the rectangle of pixels 142 * @param cm the specified <code>ColorModel</code> 143 * @param pix an array of pixels 144 * @param off the offset into the array of where to store the 145 * first pixel 146 * @param scan the distance from one row of pixels to the next in 147 * the array 148 * @param props a list of properties that the <code>ImageProducer</code> 149 * uses to process an image 150 * @see java.awt.Component#createImage 151 */ 152 public MemoryImageSource(int w, int h, ColorModel cm, 153 byte[] pix, int off, int scan, 154 Hashtable<?,?> props) 155 { 156 initialize(w, h, cm, (Object) pix, off, scan, props); 157 } 158 159 /** 160 * Constructs an ImageProducer object which uses an array of integers 161 * to produce data for an Image object. 162 * @param w the width of the rectangle of pixels 163 * @param h the height of the rectangle of pixels 164 * @param cm the specified <code>ColorModel</code> 165 * @param pix an array of pixels 166 * @param off the offset into the array of where to store the 167 * first pixel 168 * @param scan the distance from one row of pixels to the next in 169 * the array 170 * @see java.awt.Component#createImage 171 */ 172 public MemoryImageSource(int w, int h, ColorModel cm, 173 int[] pix, int off, int scan) { 174 initialize(w, h, cm, (Object) pix, off, scan, null); 175 } 176 177 /** 178 * Constructs an ImageProducer object which uses an array of integers 179 * to produce data for an Image object. 180 * @param w the width of the rectangle of pixels 181 * @param h the height of the rectangle of pixels 182 * @param cm the specified <code>ColorModel</code> 183 * @param pix an array of pixels 184 * @param off the offset into the array of where to store the 185 * first pixel 186 * @param scan the distance from one row of pixels to the next in 187 * the array 188 * @param props a list of properties that the <code>ImageProducer</code> 189 * uses to process an image 190 * @see java.awt.Component#createImage 191 */ 192 public MemoryImageSource(int w, int h, ColorModel cm, 193 int[] pix, int off, int scan, 194 Hashtable<?,?> props) 195 { 196 initialize(w, h, cm, (Object) pix, off, scan, props); 197 } 198 199 private void initialize(int w, int h, ColorModel cm, 200 Object pix, int off, int scan, Hashtable props) { 201 width = w; 202 height = h; 203 model = cm; 204 pixels = pix; 205 pixeloffset = off; 206 pixelscan = scan; 207 if (props == null) { 208 props = new Hashtable(); 209 } 210 properties = props; 211 } 212 213 /** 214 * Constructs an ImageProducer object which uses an array of integers 215 * in the default RGB ColorModel to produce data for an Image object. 216 * @param w the width of the rectangle of pixels 217 * @param h the height of the rectangle of pixels 218 * @param pix an array of pixels 219 * @param off the offset into the array of where to store the 220 * first pixel 221 * @param scan the distance from one row of pixels to the next in 222 * the array 223 * @see java.awt.Component#createImage 224 * @see ColorModel#getRGBdefault 225 */ 226 public MemoryImageSource(int w, int h, int pix[], int off, int scan) { 227 initialize(w, h, ColorModel.getRGBdefault(), 228 (Object) pix, off, scan, null); 229 } 230 231 /** 232 * Constructs an ImageProducer object which uses an array of integers 233 * in the default RGB ColorModel to produce data for an Image object. 234 * @param w the width of the rectangle of pixels 235 * @param h the height of the rectangle of pixels 236 * @param pix an array of pixels 237 * @param off the offset into the array of where to store the 238 * first pixel 239 * @param scan the distance from one row of pixels to the next in 240 * the array 241 * @param props a list of properties that the <code>ImageProducer</code> 242 * uses to process an image 243 * @see java.awt.Component#createImage 244 * @see ColorModel#getRGBdefault 245 */ 246 public MemoryImageSource(int w, int h, int pix[], int off, int scan, 247 Hashtable<?,?> props) 248 { 249 initialize(w, h, ColorModel.getRGBdefault(), 250 (Object) pix, off, scan, props); 251 } 252 253 /** 254 * Adds an ImageConsumer to the list of consumers interested in 255 * data for this image. 256 * @param ic the specified <code>ImageConsumer</code> 257 * @throws NullPointerException if the specified 258 * <code>ImageConsumer</code> is null 259 * @see ImageConsumer 260 */ 261 public synchronized void addConsumer(ImageConsumer ic) { 262 if (theConsumers.contains(ic)) { 263 return; 264 } 265 theConsumers.addElement(ic); 266 try { 267 initConsumer(ic); 268 sendPixels(ic, 0, 0, width, height); 269 if (isConsumer(ic)) { 270 ic.imageComplete(animating 271 ? ImageConsumer.SINGLEFRAMEDONE 272 : ImageConsumer.STATICIMAGEDONE); 273 if (!animating && isConsumer(ic)) { 274 ic.imageComplete(ImageConsumer.IMAGEERROR); 275 removeConsumer(ic); 276 } 277 } 278 } catch (Exception e) { 279 if (isConsumer(ic)) { 280 ic.imageComplete(ImageConsumer.IMAGEERROR); 281 } 282 } 283 } 284 285 /** 286 * Determines if an ImageConsumer is on the list of consumers currently 287 * interested in data for this image. 288 * @param ic the specified <code>ImageConsumer</code> 289 * @return <code>true</code> if the <code>ImageConsumer</code> 290 * is on the list; <code>false</code> otherwise. 291 * @see ImageConsumer 292 */ 293 public synchronized boolean isConsumer(ImageConsumer ic) { 294 return theConsumers.contains(ic); 295 } 296 297 /** 298 * Removes an ImageConsumer from the list of consumers interested in 299 * data for this image. 300 * @param ic the specified <code>ImageConsumer</code> 301 * @see ImageConsumer 302 */ 303 public synchronized void removeConsumer(ImageConsumer ic) { 304 theConsumers.removeElement(ic); 305 } 306 307 /** 308 * Adds an ImageConsumer to the list of consumers interested in 309 * data for this image and immediately starts delivery of the 310 * image data through the ImageConsumer interface. 311 * @param ic the specified <code>ImageConsumer</code> 312 * image data through the ImageConsumer interface. 313 * @see ImageConsumer 314 */ 315 public void startProduction(ImageConsumer ic) { 316 addConsumer(ic); 317 } 318 319 /** 320 * Requests that a given ImageConsumer have the image data delivered 321 * one more time in top-down, left-right order. 322 * @param ic the specified <code>ImageConsumer</code> 323 * @see ImageConsumer 324 */ 325 public void requestTopDownLeftRightResend(ImageConsumer ic) { 326 // Ignored. The data is either single frame and already in TDLR 327 // format or it is multi-frame and TDLR resends aren't critical. 328 } 329 330 /** 331 * Changes this memory image into a multi-frame animation or a 332 * single-frame static image depending on the animated parameter. 333 * <p>This method should be called immediately after the 334 * MemoryImageSource is constructed and before an image is 335 * created with it to ensure that all ImageConsumers will 336 * receive the correct multi-frame data. If an ImageConsumer 337 * is added to this ImageProducer before this flag is set then 338 * that ImageConsumer will see only a snapshot of the pixel 339 * data that was available when it connected. 340 * @param animated <code>true</code> if the image is a 341 * multi-frame animation 342 */ 343 public synchronized void setAnimated(boolean animated) { 344 this.animating = animated; 345 if (!animating) { 346 Enumeration enum_ = theConsumers.elements(); 347 while (enum_.hasMoreElements()) { 348 ImageConsumer ic = (ImageConsumer) enum_.nextElement(); 349 ic.imageComplete(ImageConsumer.STATICIMAGEDONE); 350 if (isConsumer(ic)) { 351 ic.imageComplete(ImageConsumer.IMAGEERROR); 352 } 353 } 354 theConsumers.removeAllElements(); 355 } 356 } 357 358 /** 359 * Specifies whether this animated memory image should always be 360 * updated by sending the complete buffer of pixels whenever 361 * there is a change. 362 * This flag is ignored if the animation flag is not turned on 363 * through the setAnimated() method. 364 * <p>This method should be called immediately after the 365 * MemoryImageSource is constructed and before an image is 366 * created with it to ensure that all ImageConsumers will 367 * receive the correct pixel delivery hints. 368 * @param fullbuffers <code>true</code> if the complete pixel 369 * buffer should always 370 * be sent 371 * @see #setAnimated 372 */ 373 public synchronized void setFullBufferUpdates(boolean fullbuffers) { 374 if (this.fullbuffers == fullbuffers) { 375 return; 376 } 377 this.fullbuffers = fullbuffers; 378 if (animating) { 379 Enumeration enum_ = theConsumers.elements(); 380 while (enum_.hasMoreElements()) { 381 ImageConsumer ic = (ImageConsumer) enum_.nextElement(); 382 ic.setHints(fullbuffers 383 ? (ImageConsumer.TOPDOWNLEFTRIGHT | 384 ImageConsumer.COMPLETESCANLINES) 385 : ImageConsumer.RANDOMPIXELORDER); 386 } 387 } 388 } 389 390 /** 391 * Sends a whole new buffer of pixels to any ImageConsumers that 392 * are currently interested in the data for this image and notify 393 * them that an animation frame is complete. 394 * This method only has effect if the animation flag has been 395 * turned on through the setAnimated() method. 396 * @see #newPixels(int, int, int, int, boolean) 397 * @see ImageConsumer 398 * @see #setAnimated 399 */ 400 public void newPixels() { 401 newPixels(0, 0, width, height, true); 402 } 403 404 /** 405 * Sends a rectangular region of the buffer of pixels to any 406 * ImageConsumers that are currently interested in the data for 407 * this image and notify them that an animation frame is complete. 408 * This method only has effect if the animation flag has been 409 * turned on through the setAnimated() method. 410 * If the full buffer update flag was turned on with the 411 * setFullBufferUpdates() method then the rectangle parameters 412 * will be ignored and the entire buffer will always be sent. 413 * @param x the x coordinate of the upper left corner of the rectangle 414 * of pixels to be sent 415 * @param y the y coordinate of the upper left corner of the rectangle 416 * of pixels to be sent 417 * @param w the width of the rectangle of pixels to be sent 418 * @param h the height of the rectangle of pixels to be sent 419 * @see #newPixels(int, int, int, int, boolean) 420 * @see ImageConsumer 421 * @see #setAnimated 422 * @see #setFullBufferUpdates 423 */ 424 public synchronized void newPixels(int x, int y, int w, int h) { 425 newPixels(x, y, w, h, true); 426 } 427 428 /** 429 * Sends a rectangular region of the buffer of pixels to any 430 * ImageConsumers that are currently interested in the data for 431 * this image. 432 * If the framenotify parameter is true then the consumers are 433 * also notified that an animation frame is complete. 434 * This method only has effect if the animation flag has been 435 * turned on through the setAnimated() method. 436 * If the full buffer update flag was turned on with the 437 * setFullBufferUpdates() method then the rectangle parameters 438 * will be ignored and the entire buffer will always be sent. 439 * @param x the x coordinate of the upper left corner of the rectangle 440 * of pixels to be sent 441 * @param y the y coordinate of the upper left corner of the rectangle 442 * of pixels to be sent 443 * @param w the width of the rectangle of pixels to be sent 444 * @param h the height of the rectangle of pixels to be sent 445 * @param framenotify <code>true</code> if the consumers should be sent a 446 * {@link ImageConsumer#SINGLEFRAMEDONE SINGLEFRAMEDONE} notification 447 * @see ImageConsumer 448 * @see #setAnimated 449 * @see #setFullBufferUpdates 450 */ 451 public synchronized void newPixels(int x, int y, int w, int h, 452 boolean framenotify) { 453 if (animating) { 454 if (fullbuffers) { 455 x = y = 0; 456 w = width; 457 h = height; 458 } else { 459 if (x < 0) { 460 w += x; 461 x = 0; 462 } 463 if (x + w > width) { 464 w = width - x; 465 } 466 if (y < 0) { 467 h += y; 468 y = 0; 469 } 470 if (y + h > height) { 471 h = height - y; 472 } 473 } 474 if ((w <= 0 || h <= 0) && !framenotify) { 475 return; 476 } 477 Enumeration enum_ = theConsumers.elements(); 478 while (enum_.hasMoreElements()) { 479 ImageConsumer ic = (ImageConsumer) enum_.nextElement(); 480 if (w > 0 && h > 0) { 481 sendPixels(ic, x, y, w, h); 482 } 483 if (framenotify && isConsumer(ic)) { 484 ic.imageComplete(ImageConsumer.SINGLEFRAMEDONE); 485 } 486 } 487 } 488 } 489 490 /** 491 * Changes to a new byte array to hold the pixels for this image. 492 * If the animation flag has been turned on through the setAnimated() 493 * method, then the new pixels will be immediately delivered to any 494 * ImageConsumers that are currently interested in the data for 495 * this image. 496 * @param newpix the new pixel array 497 * @param newmodel the specified <code>ColorModel</code> 498 * @param offset the offset into the array 499 * @param scansize the distance from one row of pixels to the next in 500 * the array 501 * @see #newPixels(int, int, int, int, boolean) 502 * @see #setAnimated 503 */ 504 public synchronized void newPixels(byte[] newpix, ColorModel newmodel, 505 int offset, int scansize) { 506 this.pixels = newpix; 507 this.model = newmodel; 508 this.pixeloffset = offset; 509 this.pixelscan = scansize; 510 newPixels(); 511 } 512 513 /** 514 * Changes to a new int array to hold the pixels for this image. 515 * If the animation flag has been turned on through the setAnimated() 516 * method, then the new pixels will be immediately delivered to any 517 * ImageConsumers that are currently interested in the data for 518 * this image. 519 * @param newpix the new pixel array 520 * @param newmodel the specified <code>ColorModel</code> 521 * @param offset the offset into the array 522 * @param scansize the distance from one row of pixels to the next in 523 * the array 524 * @see #newPixels(int, int, int, int, boolean) 525 * @see #setAnimated 526 */ 527 public synchronized void newPixels(int[] newpix, ColorModel newmodel, 528 int offset, int scansize) { 529 this.pixels = newpix; 530 this.model = newmodel; 531 this.pixeloffset = offset; 532 this.pixelscan = scansize; 533 newPixels(); 534 } 535 536 private void initConsumer(ImageConsumer ic) { 537 if (isConsumer(ic)) { 538 ic.setDimensions(width, height); 539 } 540 if (isConsumer(ic)) { 541 ic.setProperties(properties); 542 } 543 if (isConsumer(ic)) { 544 ic.setColorModel(model); 545 } 546 if (isConsumer(ic)) { 547 ic.setHints(animating 548 ? (fullbuffers 549 ? (ImageConsumer.TOPDOWNLEFTRIGHT | 550 ImageConsumer.COMPLETESCANLINES) 551 : ImageConsumer.RANDOMPIXELORDER) 552 : (ImageConsumer.TOPDOWNLEFTRIGHT | 553 ImageConsumer.COMPLETESCANLINES | 554 ImageConsumer.SINGLEPASS | 555 ImageConsumer.SINGLEFRAME)); 556 } 557 } 558 559 private void sendPixels(ImageConsumer ic, int x, int y, int w, int h) { 560 int off = pixeloffset + pixelscan * y + x; 561 if (isConsumer(ic)) { 562 if (pixels instanceof byte[]) { 563 ic.setPixels(x, y, w, h, model, 564 ((byte[]) pixels), off, pixelscan); 565 } else { 566 ic.setPixels(x, y, w, h, model, 567 ((int[]) pixels), off, pixelscan); 568 } 569 } 570 } 571 }