View Javadoc

1   package de.desy.acop.video.timageio;
2   
3   import java.awt.Graphics2D;
4   import java.awt.Transparency;
5   import java.awt.color.ColorSpace;
6   import java.awt.image.BufferedImage;
7   import java.awt.image.ColorModel;
8   import java.awt.image.ComponentColorModel;
9   import java.awt.image.DataBuffer;
10  import java.awt.image.DataBufferByte;
11  import java.awt.image.DataBufferInt;
12  import java.awt.image.DataBufferUShort;
13  import java.awt.image.DirectColorModel;
14  import java.awt.image.Raster;
15  import java.awt.image.SampleModel;
16  import java.awt.image.WritableRaster;
17  import java.io.ByteArrayInputStream;
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.util.Arrays;
21  import java.util.Hashtable;
22  
23  import de.desy.acop.video.displayer.ColorMap;
24  import de.desy.acop.video.displayer.HuffmanDecompression;
25  import de.desy.acop.video.displayer.ImageFormat;
26  import de.desy.tine.types.IMAGE;
27  import de.desy.tine.types.IMAGE.FrameHeader;
28  
29  /**
30   * The <code>TBufferedImage</code> comprised of a {@link TImageMetadata} of
31   * image metadata and a {@link BufferedImage} of image data.
32   * 
33   * @author David Melkumyan, DESY Zeuthen
34   */
35  public class TBufferedImage {
36  
37  	/**
38  	 * Java <code>BufferedImage</code> with an accessible buffer of image data.
39  	 */
40  	private BufferedImage bufferedImage;
41  
42  	/**
43  	 * Image metadata (non-image data) associated with TINE images.
44  	 */
45  	private TImageMetadata tiMetadata;
46  
47  	/**
48  	 * Constructs a <code>TBufferedImage</code> from <code>BufferedImage</code>
49  	 * and <code>TImageMetadata</code>.
50  	 * 
51  	 * @note only for internal use
52  	 * 
53  	 * @param bufferedImage
54  	 *            - Java <code>BufferedImage</code>
55  	 * @param tiMetadata
56  	 *            - <code>TImageMetadata</code>
57  	 * 
58  	 * @see TImageMetadata
59  	 */
60  	public TBufferedImage(BufferedImage bufferedImage, TImageMetadata tiMetadata) {
61  		if (bufferedImage == null)
62  			throw new NullPointerException("bufferedImage == null!");
63  		this.bufferedImage = bufferedImage;
64  		this.tiMetadata = tiMetadata;
65  	}
66  
67  	/**
68  	 * Creates <code>TBufferedImage</code> instance from Tine IMAGE (in
69  	 * 'archival' mode).
70  	 * 
71  	 * @param ti
72  	 *            - TINE Image
73  	 */
74  	public TBufferedImage(IMAGE ti) {
75  		this(TBufferedImage.toBufferedImage(ti), new TImageMetadata(ti));
76  	}
77  
78  	/**
79  	 * Creates <code>TBufferedImage</code> instance from Tine IMAGE (in
80  	 * 'archival' mode).
81  	 * 
82  	 * @param ti
83  	 *            - TINE Image
84  	 */
85  	public TBufferedImage(IMAGE ti, ColorMap colorMap, boolean normalize, boolean aoiExpansion) {
86  		this(TBufferedImage.toBufferedImage(ti, colorMap, normalize, aoiExpansion), new TImageMetadata(ti));
87  	}
88  
89  	/**
90  	 * Returns Java BufferedImage with an accessible buffer of image data
91  	 * 
92  	 * @return BufferedImage
93  	 */
94  	public BufferedImage getBufferedImage() {
95  		return bufferedImage;
96  	}
97  
98  	public void setBufferedImage(BufferedImage image) {
99  		this.bufferedImage = image;
100 	}
101 
102 	/**
103 	 * Returns image metadata (non-image data) associated with TINE images.
104 	 * 
105 	 * @return TImageMetadata
106 	 */
107 	public TImageMetadata getMetadata() {
108 		return tiMetadata;
109 	}
110 
111 	/**
112 	 * Sets the TINE image metadata
113 	 * 
114 	 * @param metadata
115 	 *            - TINE image metadata
116 	 */
117 	public void setMetadata(TImageMetadata metadata) {
118 		this.tiMetadata = metadata;
119 	}
120 
121 	/**
122 	 * Creates <code>BufferedImage</code> from Tine IMAGE in 'archival' mode.
123 	 * 
124 	 * @param ti
125 	 *            - input Tine IMAGE
126 	 * @return Java Buffered Image
127 	 */
128 	public static BufferedImage toBufferedImage(IMAGE ti) {
129 		return toBufferedImage(ti, null, false, false);
130 	}
131 
132 	/**
133 	 * Creates <code>BufferedImage</code> from Tine IMAGE in 'archival'/'export'
134 	 * mode.
135 	 * 
136 	 * @param ti
137 	 *            - input Tine IMAGE
138 	 * 
139 	 * @param colorMap
140 	 *            - apply color map
141 	 * @param normalize
142 	 *            - apply the histogram equalization will be applied
143 	 * @param aoiExpansion
144 	 *            - apply the AOI expansion
145 	 * 
146 	 * @return Java Buffered Image
147 	 */
148 	public static BufferedImage toBufferedImage(IMAGE ti, ColorMap colorMap, boolean normalize, boolean aoiExpansion) {
149 		return toBufferedImage(ti, colorMap, normalize, -1, -1, aoiExpansion);
150 	}
151 
152 	public static BufferedImage toBufferedImage(IMAGE ti, ColorMap colorMap, boolean normalize, int min, int max,
153 			boolean aoiExpansion) {
154 		if (ti == null)
155 			throw new NullPointerException("ti == null!");
156 
157 		BufferedImage destImage = null;
158 
159 		final FrameHeader frmHdr = ti.getFrameHeader();
160 		if ((frmHdr.aoiWidth == -1 && frmHdr.aoiHeight != -1) || (frmHdr.aoiWidth != -1 && frmHdr.aoiHeight == -1))
161 			throw new IllegalArgumentException("aoiWidth=" + frmHdr.aoiWidth + ", aoiHeight=" + frmHdr.aoiHeight);
162 
163 		final boolean isAOI = (frmHdr.aoiWidth != -1);
164 
165 		final ImageFormat format = ImageFormat.valueOf(frmHdr.imageFormat);
166 		final int w = (isAOI ? frmHdr.aoiWidth : frmHdr.sourceWidth);
167 		final int h = (isAOI ? frmHdr.aoiHeight : frmHdr.sourceHeight);
168 		final int bpp = frmHdr.bytesPerPixel;
169 		final int bits = frmHdr.effectiveBitsPerPixel;
170 		final int appendedSize = frmHdr.appendedFrameSize;
171 		final int wh = w * h;
172 		byte[] byteArray = ti.getImageFrameBuffer();
173 
174 		if (bits < 8)
175 			throw new IllegalArgumentException("bits=" + bits);
176 		if (byteArray.length < appendedSize)
177 			throw new IllegalArgumentException("format=" + format + ", size=" + appendedSize //
178 					+ ", w=" + w + ", h=" + h + ", bpp=" + bpp + ", data.size=" + byteArray.length);
179 
180 		final Hashtable<?, ?> props = TImageMetadata.createProperties(ti);
181 
182 		switch (format) {
183 		case IMAGE_FORMAT_GRAY:
184 			if (appendedSize != wh * bpp)
185 				throw new IllegalArgumentException("format=" + format + ", size=" + appendedSize //
186 						+ ", w=" + w + ", h=" + h + ", bpp=" + bpp);
187 
188 			switch (bpp) {
189 			case 1: // 1 bytes per pixel
190 				// created buffered image: TYPE_BYTE_INDEXED / TYPE_BYTE_GRAY
191 				if (bits != 8)
192 					throw new IllegalArgumentException("format=" + format + ", bits=" + bits);
193 				if (normalize)
194 					byteArray = normalizeGray(Arrays.copyOf(byteArray, wh), //
195 							min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
196 				if (colorMap == null || colorMap.equals(ColorMap.NONE))
197 					destImage = createImageGray(w, h, bits, new DataBufferByte(byteArray, wh), props);
198 				else
199 					destImage = createImageIndexed(colorMap.getLUT(bits), w, h, new DataBufferByte(byteArray, wh),
200 							props);
201 				break;
202 
203 			case 2: // 2 bytes per pixel
204 				// created buffered image: TYPE_CUSTOM / TYPE_USHORT_GRAY
205 				if (bits > 16)
206 					throw new IllegalArgumentException("format=" + format + ", bits=" + bits);
207 				short[] shortArray = createShortArray(byteArray, wh, bits);
208 				if (normalize)
209 					normalizeGray(shortArray, (1 << bits) - 1, min == -1 ? 0x0 : min, max == -1 ? 0xffff : max);
210 
211 				if (colorMap == null || ColorMap.NONE.equals(colorMap) || ColorMap.GRAYSCALE.equals(colorMap))
212 					// #pixelBits = 16 numComponents = 1 maxBits = [1-16]
213 					destImage = createImageGray(w, h, bits, new DataBufferUShort(shortArray, wh), props);
214 				else
215 					// #pixelBits = [1-16] numComponents = 3
216 					destImage = createImageIndexed(colorMap.getLUT(bits), w, h, //
217 							new DataBufferUShort(shortArray, wh), props);
218 				break;
219 
220 			default:
221 				throw new IllegalArgumentException("format=" + format + ", bpp=" + bpp);
222 			}
223 			break;
224 
225 		case IMAGE_FORMAT_RGB:
226 			// created buffered image: TYPE_INT_RGB
227 			if (bpp != 3 || bits != 24)
228 				throw new IllegalArgumentException("format=" + format + ", bpp=" + bpp + ", bits=" + bits);
229 			if (appendedSize != wh * bpp)
230 				throw new IllegalArgumentException("format=" + format + ", size=" + appendedSize //
231 						+ ", w=" + w + ", h=" + h + ", bpp=" + bpp);
232 			int[] intArray = createIntArray(byteArray, wh, bpp);
233 			if (normalize)
234 				normalizeRGB(intArray, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
235 
236 			destImage = createImageRGB(w, h, new DataBufferInt(intArray, wh), props);
237 			break;
238 
239 		case IMAGE_FORMAT_RGBA:
240 			// created buffered image: TYPE_INT_RGB
241 			if (bpp != 4 || bits != 32)
242 				throw new IllegalArgumentException("format=" + format + ", bpp=" + bpp + ", bits=" + bits);
243 			if (appendedSize != wh * bpp)
244 				throw new IllegalArgumentException("format=" + format + ", size=" + appendedSize //
245 						+ ", w=" + w + ", h=" + h + ", bpp=" + bpp);
246 			intArray = createIntArray(byteArray, wh, bpp);
247 			if (normalize)
248 				normalizeRGB(intArray, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
249 
250 			destImage = createImageRGB(w, h, new DataBufferInt(intArray, wh), props);
251 			break;
252 
253 		case IMAGE_FORMAT_HUFFYUV:
254 			// created buffered image: TYPE_BYTE_INDEXED / TYPE_BYTE_GRAY
255 			if (bpp != 1 || bits != 8)
256 				throw new IllegalArgumentException("format=" + format + ", bpp=" + bpp + ", bits=" + bits);
257 			if (frmHdr.sourceFormat != 0) // IMAGE_FORMAT_GRAY
258 				throw new IllegalArgumentException("format=" + format + ", srcFormat="
259 						+ ImageFormat.valueOf(frmHdr.sourceFormat));
260 			byte[] compressed = Arrays.copyOf(byteArray, appendedSize);
261 			byte[] uncompressed = new byte[wh * bpp];
262 			HuffmanDecompression.decompressHuffYUV(compressed, 0, uncompressed, 0, uncompressed.length);
263 			if (normalize)
264 				normalizeGray(uncompressed, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
265 			if (colorMap == null || colorMap.equals(ColorMap.NONE))
266 				destImage = createImageGray(w, h, bits, new DataBufferByte(uncompressed, wh), props);
267 			else
268 				destImage = createImageIndexed(colorMap.getLUT(bits), w, h, new DataBufferByte(uncompressed, wh), props);
269 			break;
270 
271 		case IMAGE_FORMAT_JPEG:
272 			// created buffered image:
273 			// TYPE_BYTE_INDEXED / TYPE_BYTE_GRAY / TYPE_3BYTE_BGR /
274 			// TYPE_INT_RGB
275 			if ((bpp != 1 && bpp != 3) || (bpp == 1 && bits != 8) || (bpp == 3 && bits != 24))
276 				throw new IllegalArgumentException("format=" + format + ", bpp=" + bpp + ", bits=" + bits);
277 
278 			
279 			BufferedImage bi = createImageJPEG(w, wh, Arrays.copyOf(byteArray, appendedSize), props);
280 			switch (bi.getType()) {
281 			case BufferedImage.TYPE_BYTE_GRAY:
282 				byteArray = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
283 				if (normalize)
284 					normalizeGray(byteArray, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
285 				if (colorMap == null || colorMap.equals(ColorMap.NONE))
286 					destImage = bi;
287 				else
288 					destImage = new BufferedImage(colorMap.getLUT(bits), bi.getRaster(), false, props);
289 				break;
290 
291 			case BufferedImage.TYPE_3BYTE_BGR:
292 				byteArray = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
293 				if (normalize)
294 					normalizeBGR(byteArray, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
295 
296 				destImage = bi;
297 				break;
298 
299 			case BufferedImage.TYPE_INT_RGB:
300 				intArray = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
301 				if (normalize)
302 					normalizeRGB(intArray, min == -1 ? 0x0 : min, max == -1 ? 0xff : max);
303 
304 				destImage = bi;
305 				break;
306 
307 			default:
308 				throw new IllegalArgumentException("Unsupported image format: " + bi.getType());
309 			}
310 			break;
311 
312 		default:
313 			throw new IllegalArgumentException("tiFormat=" + format);
314 		}
315 		return (aoiExpansion && isAOI) ? expanseAOI(destImage, props) : destImage;
316 	}
317 
318 	/**
319 	 * Converts Java <code>BufferedImage</code> to TINE image
320 	 * 
321 	 * @param bi
322 	 *            - Java BufferedImage with/without TINE image metadata
323 	 * @return TINE image
324 	 */
325 	public static IMAGE toIMAGE(BufferedImage bi) {
326 		return toIMAGE(bi, false);
327 	}
328 
329 	/**
330 	 * Converts Java <code>BufferedImage</code> to TINE image
331 	 * 
332 	 * @param bi
333 	 *            - Java BufferedImage with/without TINE image metadata
334 	 * 
335 	 * @param rejectNonArchival
336 	 *            - if <code>true</code> and <code>bi</code> does not contain
337 	 *            TINE image metadata throws
338 	 *            <code>IllegalArgumentException</code>
339 	 * @return TINE image
340 	 */
341 	@SuppressWarnings("deprecation")
342 	public static IMAGE toIMAGE(BufferedImage bi, boolean rejectNonArchival) {
343 		TImageMetadata tiMetadata = new TImageMetadata(bi, rejectNonArchival);
344 		if (tiMetadata == null)
345 			throw new NullPointerException("tiMetadata == null!");
346 
347 		IMAGE ti = new IMAGE(0);
348 		tiMetadata.write(ti);
349 		ti.setImageFrameBuffer(getData(bi));
350 
351 		return ti;
352 	}
353 
354 	/**
355 	 * Represents <code>TBufferedImage</code> as TINE image
356 	 * 
357 	 * @return TINE image
358 	 */
359 	public IMAGE toIMAGE() {
360 		return toIMAGE(bufferedImage);
361 	}
362 
363 	/**
364 	 * Represents <code>TBufferedImage</code> as TINE image
365 	 * 
366 	 * @param rejectNonArchival
367 	 *            - if <code>true</code> and <code>TBufferedImage</code> does
368 	 *            not contain TINE image metadata throws
369 	 *            <code>IllegalArgumentException</code>
370 	 * @return TINE image
371 	 */
372 	public IMAGE toIMAGE(boolean rejectNonArchival) {
373 		return toIMAGE(bufferedImage, rejectNonArchival);
374 	}
375 
376 	/**
377 	 * Returns true if TINE image format is supported.
378 	 * 
379 	 * @param format
380 	 *            - an Image Format
381 	 * @return true if image format is supported, otherwise - false
382 	 * @throws IllegalArgumentException
383 	 *             if format is invalid
384 	 */
385 	public static boolean isSupported(int format) {
386 		return ImageFormat.valueOf(format).isSupported();
387 	}
388 
389 	/**
390 	 * Returns true if TINE image contains AOI, otherwise false.
391 	 * 
392 	 * @param ti
393 	 *            - TINE image
394 	 * @return true if AOI is present in the image, otherwise false
395 	 * @note TODO: have to be a part of the TINE Image interface
396 	 */
397 	public static boolean containsAOI(IMAGE ti) {
398 		return (ti.getFrameHeader().aoiHeight != -1 || ti.getFrameHeader().aoiWidth != -1);
399 	}
400 
401 	/**
402 	 * @param bi
403 	 *            - BufferedImage
404 	 * @return always <code>null</code> (!!!) while
405 	 *         <code>BufferedImage#getPropertyNames()</code> returns always
406 	 *         <code>null</code>
407 	 * @deprecated
408 	 * @see <code>BufferedImage#getPropertyNames()</code>
409 	 */
410 	public static Hashtable<String, Object> getProperties(BufferedImage bi) {
411 		String[] propertyNames = bi.getPropertyNames();
412 		if (propertyNames == null)
413 			return null;
414 
415 		Hashtable<String, Object> properties = new Hashtable<String, Object>(32);
416 		for (int i = 0; i < propertyNames.length; i++) {
417 			String name = propertyNames[i];
418 			properties.put(name, bi.getProperty(name));
419 		}
420 		return properties;
421 	}
422 
423 	private static BufferedImage createImageGray(int w, int h, int bits, DataBuffer db, Hashtable<?, ?> props) {
424 		WritableRaster raster = Raster.createInterleavedRaster(db, w, h, w, 1, new int[] { 0 }, null);
425 		ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), //
426 				new int[] { bits }, false, true, Transparency.OPAQUE, db.getDataType());
427 		return new BufferedImage(cm, raster, false, props);
428 	}
429 
430 	private static BufferedImage createImageIndexed(ColorModel cm, int w, int h, DataBuffer db, Hashtable<?, ?> props) {
431 		WritableRaster raster = Raster.createInterleavedRaster(db, w, h, w, 1, new int[] { 0 }, null);
432 		return new BufferedImage(cm, raster, false, props);
433 	}
434 
435 	private static BufferedImage createImageRGB(int w, int h, DataBuffer db, Hashtable<?, ?> props) {
436 		ColorModel cm = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
437 		SampleModel sm = cm.createCompatibleSampleModel(w, h);
438 		WritableRaster raster = WritableRaster.createWritableRaster(sm, db, null);
439 		return new BufferedImage(cm, raster, false, props);
440 	}
441 
442 	private static BufferedImage expanseAOI(BufferedImage srcImage, Hashtable<?, ?> props) {
443 		if (srcImage == null)
444 			throw new NullPointerException("srcImage == null!");
445 
446 		int srcWidth = (Integer) props.get(TImageMetadata.KEY_SOURCE_WIDTH);
447 		int srcHeight = (Integer) props.get(TImageMetadata.KEY_SOURCE_HEIGHT);
448 		int xStart = (Integer) props.get(TImageMetadata.KEY_XSTART);
449 		int yStart = (Integer) props.get(TImageMetadata.KEY_YSTART);
450 
451 		ColorModel cm = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
452 		WritableRaster raster = cm.createCompatibleWritableRaster(srcWidth, srcHeight);
453 		BufferedImage bi = new BufferedImage(cm, raster, false, props);
454 
455 		Graphics2D g2d = bi.createGraphics();
456 		g2d.drawImage(srcImage, xStart, yStart, null);
457 		srcImage.flush();
458 		g2d.dispose();
459 
460 		return bi;
461 	}
462 
463 	private static BufferedImage createImageJPEG(int w, int h, byte[] dataArray, Hashtable<?, ?> properties) {
464 
465 		javax.imageio.ImageIO.setUseCache(false); // don`t use cache file
466 
467 		InputStream bis = new ByteArrayInputStream(dataArray);
468 		BufferedImage bi;
469 		try {
470 			bi = javax.imageio.ImageIO.read(bis);
471 
472 		} catch (IOException e) {
473 			throw new IllegalArgumentException(e);
474 		} finally {
475 			try {
476 				bis.close();
477 			} catch (IOException ignored) {
478 			}
479 		}
480 		ColorModel cm = bi.getColorModel();
481 		return new BufferedImage(cm, bi.getRaster(), cm.isAlphaPremultiplied(), properties);
482 	}
483 
484 	private static short[] createShortArray(byte[] byteArray, int wh, int bits) {
485 		final int maxValue = (1 << bits) - 1;
486 		short[] shortArray = new short[wh];
487 		for (int i = 0, j = 0; i < wh;) {
488 			int gray0 = byteArray[j++] & 0xff;
489 			int gray1 = byteArray[j++] & 0xff;
490 			int pixel = ((gray1 << 8) | gray0);
491 			if (pixel > maxValue)
492 				throw new IllegalArgumentException("Invalid pixel at " + i + ": " + pixel);
493 			shortArray[i++] = (short) pixel;
494 		}
495 		return shortArray;
496 	}
497 
498 	private static int[] createIntArray(byte[] byteArray, int wh, int bpp) {
499 		int[] intArray = new int[wh];
500 		for (int i = 0, j = 0; i < wh; j += bpp) {
501 			int red = byteArray[j] & 0xff;
502 			int green = byteArray[j + 1] & 0xff;
503 			int blue = byteArray[j + 2] & 0xff;
504 			intArray[i++] = 0xff000000 | (red << 16) | (green << 8) | blue;
505 		}
506 		return intArray;
507 	}
508 
509 	private static int[] getExtrema(byte[] array) {
510 		int max = Byte.MIN_VALUE;
511 		int min = Byte.MAX_VALUE;
512 		for (int i : array) {
513 			max = (max >= i) ? max : i;
514 			min = (min <= i) ? min : i;
515 		}
516 		return (new int[] { min, max });
517 	}
518 
519 	private static int[] getExtrema(short[] array) {
520 		int max = Short.MIN_VALUE;
521 		int min = Short.MAX_VALUE;
522 		for (int i : array) {
523 			max = (max >= i) ? max : i;
524 			min = (min <= i) ? min : i;
525 		}
526 		return (new int[] { min, max });
527 	}
528 
529 	private static int[] getExtrema(int[] array) {
530 		int max = Integer.MIN_VALUE;
531 		int min = Integer.MAX_VALUE;
532 		for (int pixel : array) {
533 			pixel = (int) (0.299 * ((pixel >> 16) & 0xff) + 0.587 * ((pixel >> 8) & 0xff) + 0.114 * (pixel & 0xff));
534 			max = (max >= pixel) ? max : pixel;
535 			min = (min <= pixel) ? min : pixel;
536 		}
537 		return (new int[] { min, max });
538 	}
539 
540 	// private static int[][] getExtrema(int[] array) {
541 	// int maxR, maxG, maxB, minR, minG, minB;
542 	// maxR = maxG = maxB = Integer.MIN_VALUE;
543 	// minR = minG = minB = Integer.MAX_VALUE;
544 	// for (int pixel : array) {
545 	// int r = (pixel >> 16) & 0xff;
546 	// int g = (pixel >> 8) & 0xff;
547 	// int b = pixel & 0xff;
548 	// maxR = (maxR >= r) ? maxR : r;
549 	// minR = (minR <= r) ? minR : r;
550 	// maxG = (maxG >= g) ? maxG : g;
551 	// minG = (minG <= g) ? minG : g;
552 	// maxB = (maxB >= b) ? maxB : b;
553 	// minB = (minB <= b) ? minB : b;
554 	// }
555 	// return (new int[][] { { minR, maxR }, { minG, maxG }, { minG, maxG } });
556 	// }
557 
558 	private static byte[] normalizeGray(byte[] dataArray, final int lowest, final int highest) {
559 
560 		if (highest <= lowest)
561 			throw new IllegalArgumentException(highest + " <= " + lowest);
562 
563 		final int[] extrema = getExtrema(dataArray);
564 
565 		// no amplitude rescaling needed, return source image
566 		if (extrema[0] == extrema[1])
567 			return dataArray;
568 
569 		int min = Math.max(extrema[0], lowest);
570 		int max = Math.min(extrema[1], highest);
571 
572 		// calculate factor and offset of the amplitude rescaling
573 		double constant = 255D / (max - min);
574 		double offset = constant * min;
575 
576 		// amplitude rescaling of the image data
577 		int size = dataArray.length;
578 		for (int i = 0, pixel; i < size; i++) {
579 			pixel = (dataArray[i] & 0xff);
580 			if (pixel < min)
581 				pixel = min;
582 			if (pixel > max)
583 				pixel = max;
584 			dataArray[i] = (byte) (constant * pixel - offset);
585 		}
586 		return dataArray;
587 	}
588 
589 	private static short[] normalizeGray(short[] dataArray, int colors, int lowest, int highest) {
590 		if (highest <= lowest)
591 			throw new IllegalArgumentException(highest + " <= " + lowest);
592 
593 		final int[] extrema = getExtrema(dataArray);
594 
595 		// no amplitude rescaling needed, return source image
596 		if (extrema[0] == extrema[1])
597 			return dataArray;
598 
599 		int min = Math.max(extrema[0], lowest);
600 		int max = Math.min(extrema[1], Math.min(colors, highest));
601 
602 		// calculate factor and offset for amplitude rescaling
603 		double constant = 1D * colors / (max - min);
604 		double offset = constant * min;
605 
606 		// amplitude rescaling of the image data
607 		int size = dataArray.length;
608 		for (int i = 0, pixel; i < size; i++) {
609 			pixel = (dataArray[i] & colors);
610 			if (pixel < min)
611 				pixel = min;
612 			if (pixel > max)
613 				pixel = max;
614 			dataArray[i] = (short) (constant * pixel - offset);
615 		}
616 		return dataArray;
617 	}
618 
619 	private static int[] normalizeRGB(int[] dataArray, final int lowest, final int highest) {
620 
621 		// calculate min/max
622 		final int[] extrema = getExtrema(dataArray);
623 		int min = Math.max(extrema[0], lowest);
624 		int max = Math.min(extrema[1], highest);
625 
626 		// create scaling factor and offset for stretching
627 		final double constant = 255D / (max - min);
628 		final double offset = constant * min;
629 
630 		// normalize image data
631 		final int size = dataArray.length;
632 		for (int i = 0, pixel; i < size; i++) {
633 			pixel = dataArray[i];
634 			int r = (pixel >> 16) & 0xff; // red component
635 			int g = (pixel >> 8) & 0xff; // green component
636 			int b = pixel & 0xff; // blue component
637 
638 			double dY = constant - (offset / (0.299 * r + 0.587 * g + 0.114 * b));
639 			r *= dY;
640 			g *= dY;
641 			b *= dY;
642 
643 			if (r < 0)
644 				r = 0;
645 			if (g < 0)
646 				g = 0;
647 			if (b < 0)
648 				b = 0;
649 			if (r > 0xff)
650 				r = 0xff;
651 			if (g > 0xff)
652 				g = 0xff;
653 			if (b > 0xff)
654 				b = 0xff;
655 
656 			dataArray[i] = 0xff000000 | (r << 16) | (g << 8) | (b);
657 		}
658 		return dataArray;
659 	}
660 
661 	private static byte[] normalizeBGR(byte[] dataArray, int lowest, int highest) {
662 		// calculate min/max
663 		final int[] extrema = getExtrema(dataArray);
664 		int min = Math.max(extrema[0], lowest);
665 		int max = Math.min(extrema[1], highest);
666 
667 		// create scaling factor and offset for stretching
668 		final double constant = 255D / (max - min);
669 		final double offset = constant * min;
670 
671 		// normalize image data
672 		final int size = dataArray.length;
673 		for (int i = 0; i < size; i += 3) {
674 			int r = (dataArray[i + 2] & 0xff);
675 			int g = (dataArray[i + 1] & 0xff);
676 			int b = (dataArray[i] & 0xff);
677 
678 			double dY = constant - (offset / (0.299 * r + 0.587 * g + 0.114 * b));
679 			r *= dY;
680 			g *= dY;
681 			b *= dY;
682 
683 			if (r < 0)
684 				r = 0;
685 			if (g < 0)
686 				g = 0;
687 			if (b < 0)
688 				b = 0;
689 			if (r > 0xff)
690 				r = 0xff;
691 			if (g > 0xff)
692 				g = 0xff;
693 			if (b > 0xff)
694 				b = 0xff;
695 
696 			dataArray[i + 2] = (byte) r; // R
697 			dataArray[i + 1] = (byte) g; // G
698 			dataArray[i] = (byte) b; // B
699 		}
700 		return dataArray;
701 	}
702 
703 	/**
704 	 * Returns TINE image buffer byte-array data
705 	 * 
706 	 * @return image data
707 	 */
708 	private static byte[] getData(BufferedImage bi) {
709 
710 		final int[] intArray;
711 		final short[] shortArray;
712 		final byte[] byteArray;
713 
714 		final byte[] data;
715 
716 		int length;
717 
718 		final int imageType = bi.getType();
719 
720 		switch (imageType) {
721 		case BufferedImage.TYPE_BYTE_GRAY:
722 			return ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
723 
724 		case BufferedImage.TYPE_USHORT_GRAY:
725 			shortArray = ((DataBufferUShort) bi.getRaster().getDataBuffer()).getData();
726 			length = shortArray.length;
727 			data = new byte[2 * length];
728 			for (int i = 0, pos = 0; i < length; i++) {
729 				data[pos++] = (byte) (shortArray[i]);
730 				data[pos++] = (byte) (shortArray[i] >>> 8);
731 			}
732 			return data;
733 
734 		case BufferedImage.TYPE_INT_RGB:
735 		case BufferedImage.TYPE_INT_ARGB:
736 			intArray = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
737 			length = intArray.length;
738 			data = new byte[3 * length];
739 			for (int i = 0, pos = 0; i < length; i++) {
740 				data[pos++] = (byte) (intArray[i] >>> 16); // red
741 				data[pos++] = (byte) (intArray[i] >>> 8); // green
742 				data[pos++] = (byte) (intArray[i] & 0xff); // blue
743 			}
744 			return data;
745 
746 		case BufferedImage.TYPE_INT_BGR:
747 			intArray = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
748 			length = intArray.length;
749 			data = new byte[3 * length];
750 			for (int i = 0, pos = 0; i < length; i++) {
751 				data[pos++] = (byte) (intArray[i] & 0xff); // red
752 				data[pos++] = (byte) (intArray[i] >>> 8); // green
753 				data[pos++] = (byte) (intArray[i] >>> 16); // blue
754 			}
755 			return data;
756 
757 		case BufferedImage.TYPE_3BYTE_BGR:
758 			byteArray = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
759 			length = byteArray.length;
760 			data = new byte[length];
761 			for (int i = 0, pos = 0; pos < length; i += 3) {
762 				data[pos++] = byteArray[i + 2]; // red
763 				data[pos++] = byteArray[i + 1]; // green
764 				data[pos++] = byteArray[i]; // blue
765 			}
766 			return data;
767 
768 		case BufferedImage.TYPE_4BYTE_ABGR:
769 			byteArray = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
770 			length = byteArray.length / 4 * 3;
771 			data = new byte[length];
772 			for (int i = 0, pos = 0; pos < length; i += 4) {
773 				data[pos++] = byteArray[i + 3]; // red
774 				data[pos++] = byteArray[i + 2]; // green
775 				data[pos++] = byteArray[i + 1]; // blue
776 			}
777 			return data;
778 
779 		default:
780 			intArray = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), null, 0, bi.getWidth());
781 			length = intArray.length;
782 			data = new byte[3 * length];
783 			for (int i = 0, pos = 0; i < length; i++) {
784 				data[pos++] = (byte) (intArray[i] >>> 16); // red
785 				data[pos++] = (byte) (intArray[i] >>> 8); // green
786 				data[pos++] = (byte) (intArray[i] & 0xff); // blue
787 			}
788 			return data;
789 		}
790 	}
791 
792 	public static BufferedImage toImageRGB(BufferedImage src) {
793 		final int type = src.getType();
794 		if (type == BufferedImage.TYPE_INT_RGB)
795 			return src;
796 		BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
797 		dest.getGraphics().drawImage(src, 0, 0, null);
798 		src.flush();
799 		return dest;
800 	}
801 
802 }