View Javadoc

1   package de.desy.acop.video.displayer;
2   
3   import java.awt.Graphics2D;
4   import java.awt.Image;
5   import java.awt.MediaTracker;
6   import java.awt.Toolkit;
7   import java.awt.image.BufferedImage;
8   import java.awt.image.MemoryImageSource;
9   import java.util.Arrays;
10  
11  import de.desy.tine.types.IMAGE;
12  import de.desy.tine.types.IMAGE.FrameHeader;
13  
14  /**
15   * The <code>ImageBuffer</code> class describes an {@link java.awt.Image Image}
16   * with an accessible buffer of image data.
17   * 
18   * @author mdavid, sweisse
19   * @deprecated replaced by TBufferedImage.toBufferedImage
20   */
21  public class ImageBuffer {
22  
23  	/**
24  	 * Array of RGB pixels
25  	 */
26  	private int[] _pixels;
27  
28  	/**
29  	 * Memory image which varies over time to allow animation or custom
30  	 * rendering
31  	 */
32  	private MemoryImageSource _memorySource;
33  
34  	/**
35  	 * Image instance buffer for rendered video image contents ready for drawing
36  	 */
37  	private Image _image;
38  
39  	/**
40  	 * Array for false color lookup table.
41  	 */
42  	private int[] _colorLookupTable;
43  
44  	/**
45  	 * Set to true if histogram equalization (normalization) should be applied,
46  	 * otherwise false.
47  	 */
48  	private boolean _enabledNormalization;
49  
50  	/**
51  	 * Dimension of last created memory source data image
52  	 */
53  	private int _width, _height;
54  
55  	/**
56  	 * Constructor (default)
57  	 */
58  	public ImageBuffer() {
59  		super();
60  	}
61  
62  	/**
63  	 * Get array of pixels
64  	 * 
65  	 * @return array of RGB pixels
66  	 */
67  	public int[] getPixels() {
68  		return _pixels;
69  	}
70  
71  	/**
72  	 * Sets array of RGB pixels
73  	 * 
74  	 * @param pixels
75  	 */
76  	public void setPixels(int[] pixels) {
77  		_pixels = pixels;
78  	}
79  
80  	/**
81  	 * Returns whether histogram equalization (normalization) is currently
82  	 * enabled.
83  	 * 
84  	 * @return <code>true</code> if histogram equalization is enabled, otherwise
85  	 *         <code>false</code>
86  	 */
87  	public boolean isEnabledNormalization() {
88  		return _enabledNormalization;
89  	}
90  
91  	/**
92  	 * Sets whether or not the histogram equalization (normalization) is
93  	 * enabled.
94  	 * 
95  	 * @param enabledNormalization
96  	 *            true if normalization should be enabled, false otherwise
97  	 */
98  	public void setEnabledNormalization(boolean enabledNormalization) {
99  		_enabledNormalization = enabledNormalization;
100 	}
101 
102 	/**
103 	 * @return false color lookup table
104 	 */
105 	public int[] getColorLookupTable() {
106 		return _colorLookupTable;
107 	}
108 
109 	/**
110 	 * @param rgbArray
111 	 *            - false color lookup table
112 	 */
113 	public void setColorLookupTable(int[] rgbArray) {
114 		_colorLookupTable = rgbArray;
115 	}
116 
117 	/**
118 	 * updates JAVA TYPE_INT_ARGB displayImageBuffer based on any input data and
119 	 * header the image displayer is capable of processing. In addition,
120 	 * normalization of given input data type is applied on demand.<br>
121 	 * 
122 	 * @param timage
123 	 *            RGB, ARGB, JPEG or Luminosity (Grayscale) TINE IMAGE
124 	 * @return Java graphical image
125 	 * 
126 	 */
127 	public Image createImage(IMAGE timage) {
128 		if (timage == null)
129 			throw new IllegalArgumentException("timage == null!");
130 
131 		FrameHeader hdr = timage.getFrameHeader();
132 
133 		// uncompress image data if necessary
134 		if (ImageFormat.IMAGE_FORMAT_HUFFYUV.equals(hdr.imageFormat)
135 				&& ImageFormat.IMAGE_FORMAT_GRAY.equals(hdr.sourceFormat))
136 			uncompressImage(timage);
137 
138 		ImageFormat imageFormat = ImageFormat.valueOf(hdr.imageFormat);
139 		if (!imageFormat.isSupported())
140 			throw new IllegalArgumentException("unsupported image format: " + imageFormat);
141 
142 		int width = (hdr.aoiWidth != -1) ? hdr.aoiWidth : hdr.sourceWidth;
143 		int height = (hdr.aoiHeight != -1) ? hdr.aoiHeight : hdr.sourceHeight;
144 
145 		if (_pixels == null || width != _width || height != _height) {
146 			_pixels = new int[width * height];
147 			_memorySource = null;
148 		}
149 		_width = width;
150 		_height = height;
151 
152 		byte[] buff = timage.getImageFrameBuffer();
153 		buff = Arrays.copyOf(buff, hdr.appendedFrameSize);
154 		if (buff == null)
155 			throw new NullPointerException("buff == null!");
156 
157 		switch (imageFormat) {
158 		case IMAGE_FORMAT_GRAY:
159 			fillGray(buff, hdr.bytesPerPixel, hdr.effectiveBitsPerPixel);
160 			break;
161 
162 		case IMAGE_FORMAT_RGB:
163 			fillRGB(buff);
164 			break;
165 
166 		case IMAGE_FORMAT_RGBA:
167 			fillRGBA(buff);
168 			break;
169 
170 		case IMAGE_FORMAT_JPEG:
171 			fillJPEG(buff, hdr.bytesPerPixel, hdr.effectiveBitsPerPixel);
172 			break;
173 
174 		default:
175 			break;
176 		}
177 
178 		// renew image memory source
179 		if (_memorySource == null) {
180 			_memorySource = new MemoryImageSource(_width, _height, _pixels, 0, _width);
181 			_memorySource.setAnimated(true);
182 			_memorySource.setFullBufferUpdates(true);
183 			_image = Toolkit.getDefaultToolkit().createImage(_memorySource);
184 
185 		} else
186 			_memorySource.newPixels(); // TODO: mdavid check this
187 
188 		return _image;
189 	}
190 
191 	/**
192 	 * Uncompresses the TINE image data.
193 	 * 
194 	 * @param timage
195 	 *            - input/output TINE IMAGE
196 	 */
197 	private void uncompressImage(IMAGE timage) {
198 
199 		FrameHeader fHdr = timage.getFrameHeader();
200 		int uncompressed_byte_length = fHdr.bytesPerPixel //
201 				* ((fHdr.aoiWidth != -1) ? fHdr.aoiWidth : fHdr.sourceWidth) //
202 				* ((fHdr.aoiHeight != -1) ? fHdr.aoiHeight : fHdr.sourceHeight);
203 
204 		byte[] compressedBuf = new byte[fHdr.appendedFrameSize];
205 
206 		System.arraycopy(timage.getImageFrameBuffer(), 0, compressedBuf, 0, fHdr.appendedFrameSize);
207 
208 		// VideoHeaderV3.HDRSIZE
209 		HuffmanDecompression.decompressHuffYUV(compressedBuf, 0, timage.getImageFrameBuffer(), 0,
210 				uncompressed_byte_length);
211 
212 		// adjust header after compression
213 		fHdr.imageFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
214 		fHdr.appendedFrameSize = uncompressed_byte_length;
215 		timage.getSourceHeader().totalLength = uncompressed_byte_length + IMAGE.HEADER_SIZE;
216 	}
217 
218 	/**
219 	 * Fills array of RGB pixels based TINE image buffer passed as parameter.<br>
220 	 * 
221 	 * @param buff
222 	 *            marshaled RGB (24 bits per pixel RGB) TINE image buffer
223 	 */
224 	private void fillRGB(byte[] buff) {
225 		// transform RGB24 color into ARGB32 color for java Image
226 		int index = 0;
227 		int offset = 0;
228 		try {
229 			for (;;) {
230 				int red = buff[offset++] & 0xFF;
231 				int green = buff[offset++] & 0xFF;
232 				int blue = buff[offset++] & 0xFF;
233 				_pixels[index++] = 0xFF000000 | (red << 16) | (green << 8) | blue;
234 			}
235 		} catch (ArrayIndexOutOfBoundsException ignored) {
236 			// ignored for speeding up
237 		}
238 
239 		// normalize
240 		if (_enabledNormalization)
241 			normalize();
242 	}
243 
244 	/**
245 	 * Fills array of RGB pixels based TINE image buffer passed as parameter.<br>
246 	 * 
247 	 * @param buff
248 	 *            marshaled RGB (32bpp a-r-g-b) TINE image buffer, alpha is
249 	 *            <b>not</b> taken into account
250 	 */
251 	private void fillRGBA(byte[] buff) {
252 		System.arraycopy(java.nio.ByteBuffer.wrap(buff).asIntBuffer(), 0, _pixels, 0, _pixels.length);
253 
254 		// normalize
255 		if (_enabledNormalization)
256 			normalize();
257 	}
258 
259 	/**
260 	 * updates JAVA TYPE_INT_ARGB displayImageBuffer based on JPEG file bits
261 	 * data and header passed as parameter. In addition, color normalization is
262 	 * applied on demand.<br>
263 	 * 
264 	 * @param buff
265 	 *            marshaled JPEG file bits (whole file attached)TINE image
266 	 *            buffer
267 	 * 
268 	 * @return <ul>
269 	 *         <li>false - no update was done, error on creation or conversion
270 	 *         <li>true - success
271 	 *         </ul>
272 	 * @param bytesPerPixel
273 	 *            - physical bytes per pixel
274 	 * @param effBitsPerPixel
275 	 *            - effective bits per pixel
276 	 */
277 	private boolean fillJPEG(byte[] buff, int bytesPerPixel, int effBitsPerPixel) {
278 		BufferedImage bi = null;
279 		Image image = Toolkit.getDefaultToolkit().createImage(buff);
280 		MediaTracker tracker = new MediaTracker(null);
281 		tracker.addImage(image, 0);
282 		try {
283 			tracker.waitForID(0);
284 
285 		} catch (InterruptedException e) {
286 			throw new RuntimeException("Image creation has been interrupted, message: " + e.getMessage());
287 		}
288 
289 		if (bytesPerPixel == 1) { // it is grayscale / it is color JPEG image
290 			bi = new BufferedImage(_width, _height, BufferedImage.TYPE_BYTE_GRAY);
291 
292 			Graphics2D bufImageGraphics = bi.createGraphics();
293 			bufImageGraphics.drawImage(image, 0, 0, null);
294 
295 			// get ARGB data from java image into display buffer
296 			int imgWidth = bi.getWidth();
297 			int imgHeight = bi.getHeight();
298 
299 			if (imgWidth != _width || imgHeight != _height)
300 				return false; // TODO: mdavid check this
301 
302 			byte[] buffNew = new byte[imgWidth * imgHeight];
303 			byte[] pix = new byte[1];
304 
305 			int i = 0;
306 
307 			// swaps the rows
308 			for (int y = 0; y < imgHeight; y++) {
309 				// swaps the columns
310 				for (int x = 0; x < imgWidth; x++) {
311 					bi.getRaster().getDataElements(x, y, pix);
312 					buffNew[i++] = pix[0];
313 				}
314 
315 			}
316 			fillGray(buffNew, bytesPerPixel, effBitsPerPixel);
317 
318 		} else {
319 			bi = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_ARGB);
320 
321 			Graphics2D bufImageGraphics = bi.createGraphics();
322 			bufImageGraphics.drawImage(image, 0, 0, null);
323 
324 			// get ARGB data from java image into display buffer
325 			int imgWidth = bi.getWidth();
326 			int imgHeight = bi.getHeight();
327 
328 			if (imgWidth != _width || imgHeight != _height
329 					|| bi.getRGB(0, 0, imgWidth, imgHeight, _pixels, 0, imgWidth) == null)
330 				throw new RuntimeException("create JPEG image failed.");
331 
332 			if (_enabledNormalization)
333 				normalize();
334 		}
335 		return true;
336 	}
337 
338 	/**
339 	 * updates JAVA TYPE_INT_ARGB displayImageBuffer based on grayscale data
340 	 * passed as parameter. In addition, false color normalization is applied.
341 	 * The normalization is numerically precise and quite good, however
342 	 * consideration of algorithm modification is considered for future.<br>
343 	 * <p>
344 	 * support also jpeg 1bypp == grayscale jpeg
345 	 * 
346 	 * @param buff
347 	 *            - marshaled grayscale TINE image buffer
348 	 * @param bytesPerPixel
349 	 *            - physical bytes per pixel
350 	 * @param effBitsPerPixel
351 	 *            - effective bits per pixel
352 	 */
353 	private void fillGray(byte[] buff, int bytesPerPixel, int effBitsPerPixel) {
354 		// normalize
355 		if (_enabledNormalization)
356 			normalizeGray(buff, bytesPerPixel, effBitsPerPixel);
357 
358 		int[] colorLookupTable = _colorLookupTable;
359 		if (colorLookupTable == null)
360 			colorLookupTable = (new ColorLookupTable(ColorMap.GRAYSCALE, bytesPerPixel, effBitsPerPixel)).getRGB();
361 
362 		// convert to image using false-color table
363 		// in other words: transform grayscale to ARGB32 values for java display
364 		// distinct for each supported bytes per pixel setting (1, 2, 3)
365 
366 		int index = 0;
367 		int i = 0;
368 		if (bytesPerPixel == 1) {
369 			try {
370 				for (;;) {
371 					_pixels[index] = colorLookupTable[buff[index] & 0xff];
372 					index++;
373 				}
374 			} catch (ArrayIndexOutOfBoundsException ignored) {
375 				// ignored for speeding up
376 			}
377 
378 		} else if (bytesPerPixel == 2) {
379 			try {
380 				for (;;) {
381 					i = 2 * index;
382 					_pixels[index++] = colorLookupTable[(buff[i] & 0xff) + ((buff[i + 1] & 0xff) << 8)];
383 				}
384 			} catch (ArrayIndexOutOfBoundsException ignored) {
385 				// ignored for speeding up
386 			}
387 
388 		} else { // 3 bytes per pixel
389 			try {
390 				for (;;) {
391 					i = 3 * index;
392 					_pixels[index++] = colorLookupTable[(buff[i] & 0xff) + ((buff[i + 1] & 0xff) << 8)
393 							+ ((buff[i + 2] & 0xff) << 16)];
394 				}
395 			} catch (ArrayIndexOutOfBoundsException ignored) {
396 				// ignored for speeding up
397 			}
398 		}
399 	}
400 
401 	/**
402 	 * Apply RGB color histogram equalization to the display image buffer. This
403 	 * method is used for JPEG, RGB and ARGB input data types. This function is
404 	 * used/necessary for RGB color image normalization.
405 	 * <p>
406 	 * Note: </b>Quality of current color histogram equalization algorithm is
407 	 * not very good and subject to further improvement.
408 	 * <p>
409 	 * TODO: the current normalization algorithm makes the image a little bit
410 	 * too bright (clipping occurs) IDEA: maybe use Adobe Photoshop algorithm:
411 	 * http://www.lonestardigital.com/autocontrast.htm display image buffer
412 	 * contains ARGB
413 	 * 
414 	 * @return <code>TRUE</code> in case normalization was applied<br>
415 	 *         <code>FALSE</code> in case normalization was not applied (no
416 	 *         error, already good image, normalization not necessary/possible)
417 	 */
418 	private boolean normalize() {
419 
420 		// 1) calculate min/max of Y (luminosity) from RGB values of this buffer
421 
422 		double minY = 256.0;
423 		double maxY = 0;
424 		int x = 0;
425 
426 		try {
427 			for (;;) {
428 				int pixel = _pixels[x++];
429 
430 				int r = (pixel >> 16) & 0xff;
431 				int g = (pixel >> 8) & 0xff;
432 				int b = pixel & 0xff;
433 
434 				double Y = 0.299 * r + 0.587 * g + 0.114 * b;
435 				if (Y < minY)
436 					minY = Y;
437 				if (Y > maxY)
438 					maxY = Y;
439 			}
440 
441 		} catch (ArrayIndexOutOfBoundsException ignored) {
442 			// ignored for speeding up
443 		}
444 
445 		// 2) create scaling factor and offset for stretching
446 
447 		int lowest = (int) minY;
448 		int highest = (int) maxY;
449 
450 		if (lowest == highest)
451 			return false;
452 
453 		double step = (255.0) / ((double) (maxY - minY));
454 		double offset = -1.0 * step * minY;
455 
456 		// 3) render stretched display image buffer
457 
458 		x = 0;
459 		try {
460 			for (;;) {
461 				int pixel = _pixels[x];
462 
463 				int r = (pixel >> 16) & 0xff;
464 				int g = (pixel >> 8) & 0xff;
465 				int b = pixel & 0xff;
466 
467 				double Y = 0.299 * r + 0.587 * g + 0.114 * b;
468 				double Ynew = Y * step + offset;
469 				double YnewdY = Ynew / Y;
470 
471 				int r2 = (int) (YnewdY * r);
472 				int g2 = (int) (YnewdY * g);
473 				int b2 = (int) (YnewdY * b);
474 
475 				if (r2 < 0)
476 					r2 = 0;
477 				if (g2 < 0)
478 					g2 = 0;
479 				if (b2 < 0)
480 					b2 = 0;
481 				if (r2 > 255)
482 					r2 = 255;
483 				if (g2 > 255)
484 					g2 = 255;
485 				if (b2 > 255)
486 					b2 = 255;
487 
488 				_pixels[x++] = 0xFF000000 | (r2 << 16) | (g2 << 8) | (b2);
489 			}
490 
491 		} catch (ArrayIndexOutOfBoundsException ignored) {
492 			// ignored for speeding up
493 		}
494 
495 		return true;
496 	}
497 
498 	/**
499 	 * Performs histogram equalization on single-z-dimension (e.g. luminosity
500 	 * only) data
501 	 * 
502 	 * @param buff
503 	 *            - marshaled grayscale TINE image buffer
504 	 */
505 	private void normalizeGray(byte[] buff, int bytesPerPixel, int effBitsPerPixel) {
506 
507 		int maxcol = (1 << effBitsPerPixel) - 1;
508 
509 		// with histogram equalization (normalize upper and lower)
510 		int lowest = maxcol;
511 		int highest = 0;
512 
513 		// (1) perform getting of lowest and highest intensity value
514 		// distinct for each supported bytes per pixel setting (1, 2, 3)
515 
516 		int index = 0; // performance-tip
517 		int bytelength = _pixels.length;
518 
519 		if (bytesPerPixel == 1) {
520 			while (index < bytelength) {
521 				int pixel = (buff[index] & maxcol);
522 				if (pixel < lowest)
523 					lowest = pixel;
524 				if (pixel > highest && pixel <= maxcol)
525 					highest = pixel;
526 				index++;
527 			}
528 
529 		} else if (bytesPerPixel == 2) {
530 			bytelength *= 2;
531 			while (index < bytelength) {
532 				int pixel = ((buff[index]) & 0xff) + (((buff[index + 1]) & 0xff) << 8);
533 				if (pixel < lowest)
534 					lowest = pixel;
535 				if (pixel > highest && pixel <= maxcol)
536 					highest = pixel;
537 				index += 2;
538 			}
539 
540 		} else { // 3 bytes per pixel
541 			bytelength *= 3;
542 			while (index < bytelength) {
543 				int pixel = ((buff[index]) & 0xff) + (((buff[index + 1]) & 0xff) << 8)
544 						+ (((buff[index + 2]) & 0xff) << 16);
545 				if (pixel < lowest)
546 					lowest = pixel;
547 				if (pixel > highest && pixel <= maxcol)
548 					highest = pixel;
549 				index += 3;
550 			}
551 		}
552 
553 		if (lowest == highest)
554 			return;
555 
556 		// rescale the buffer if it is necessary (!)
557 
558 		// calculate factor and offset for scaling
559 		double step = (double) maxcol / ((double) (highest - lowest));
560 		int offset = (int) (-1.0 * step * lowest);
561 
562 		// (1) perform conversion of data (span over whole z-dimension)
563 		// distinct for each supported bytes per pixel setting (1, 2, 3)
564 		index = 0; // performance-tip
565 		if (bytesPerPixel == 1) {
566 			while (index < bytelength) {
567 				int pixel = (buff[index] & 0xff);
568 				if (pixel <= maxcol)
569 					pixel = ((int) (step * (double) pixel)) + offset;
570 				buff[index] = (byte) pixel;
571 				index++;
572 			}
573 
574 		} else if (bytesPerPixel == 2) {
575 			while (index < bytelength) {
576 				int pixel = (buff[index] & 0xff) + ((buff[index + 1] & 0xff) << 8);
577 				if (pixel <= maxcol)
578 					pixel = ((int) (step * (double) pixel)) + offset;
579 				buff[index] = (byte) (pixel & 0xff);
580 				buff[index + 1] = (byte) ((pixel >> 8) & 0xff);
581 				index += 2;
582 			}
583 		} else { // 3 bytes per pixel
584 			while (index < bytelength) {
585 				int pixel = (buff[index] & 0xff) + ((buff[index + 1] & 0xff) << 8) + ((buff[index + 2] & 0xff) << 16);
586 				if (pixel <= maxcol)
587 					pixel = ((int) (step * (double) pixel)) + offset;
588 				buff[index] = (byte) (pixel & 0xff);
589 				buff[index + 1] = (byte) ((pixel >> 8) & 0xff);
590 				buff[index + 2] = (byte) ((pixel >> 16) & 0xff);
591 				index += 3;
592 			}
593 		}
594 	}
595 
596 	/**
597 	 * Resets all buffers
598 	 */
599 	public void reset() {
600 		this._memorySource = null;
601 		this._pixels = null;
602 		this._image = null;
603 	}
604 
605 	/**
606 	 * @return last rendered Image
607 	 */
608 	public Image getImage() {
609 		return _image;
610 	}
611 
612 }