View Javadoc

1   package de.desy.acop.video.displayer;
2   
3   import java.awt.Image;
4   import java.awt.image.BufferedImage;
5   import java.io.BufferedWriter;
6   import java.io.DataInputStream;
7   import java.io.EOFException;
8   import java.io.File;
9   import java.io.FileInputStream;
10  import java.io.FileWriter;
11  import java.io.IOException;
12  import java.io.Writer;
13  import java.net.URL;
14  import java.text.DateFormat;
15  import java.text.SimpleDateFormat;
16  import java.util.Date;
17  
18  import javax.imageio.ImageIO;
19  
20  import de.desy.acop.video.displayer.timage.TImageUtils;
21  import de.desy.tine.types.IMAGE;
22  
23  public class ImageParser {
24  	
25  	private static ImageParser SINGLETON;
26  	static {
27  		SINGLETON = new ImageParser();
28  	}
29  
30  	public static ImageParser instance() {
31  		return SINGLETON;
32  	}
33  
34  	public static void refresh() {
35  		SINGLETON = new ImageParser();
36  	}
37  
38  	private ImageParser() {
39  	}
40  
41  	/**
42  	 * convenience overload, defaults grayscale conversion to false
43  	 * 
44  	 * @see loadImageFile( String, IMAGE, boolean )
45  	 * @deprecated use {@link #loadImageFile(URL, IMAGE, boolean)} instead
46  	 */
47  	public static final boolean loadImageFile(String aFileName, IMAGE aOutImage) {
48  		return loadImageFile(aFileName, aOutImage, false);
49  	}
50  
51  	/**
52  	 * loads an image file with a give filename from disk and puts its contents
53  	 * to TINE IMAGE data type <br>
54  	 * 
55  	 * @param aFileName
56  	 *            file name (may include path)
57  	 * @param aOutImage
58  	 *            TINE IMAGE data type to put image contents to
59  	 * @param aChangeToGrey
60  	 *            if true, image data is down scaled to grayscale before
61  	 *            returned
62  	 * @return true - image contents were successfully put into aOutImage<br>
63  	 *         false - some error (aOutImage is null, IOException,
64  	 *         URISyntaxException, error img.getRGB)
65  	 *         
66  	 * @deprecated use {@link #loadImageFile(URL, IMAGE, boolean)} instead
67  	 */
68  	public static final boolean loadImageFile(String aFileName, IMAGE aOutImage, boolean aChangeToGrey) {
69  		if (aOutImage == null)
70  			return false;
71  
72  		// (1) load file into java buffered image
73  		BufferedImage img = null;
74  		File imgFile = null;
75  		try {
76  			imgFile = new File(aFileName);
77  			img = ImageIO.read(imgFile);
78  		} catch (IOException ex) {
79  			return false;
80  		}
81  
82  		return postProcess(imgFile, img, aOutImage, aChangeToGrey);
83  	}
84  
85  	/**
86  	 * Loads an image file with a give filename from disk and puts its contents
87  	 * to TINE IMAGE data type <br>
88  	 * 
89  	 * @param url
90  	 *            input a <code>URL</code> to read from.
91  	 * @param outImage
92  	 *            TINE IMAGE data type to put image contents to
93  	 * @param toGrey
94  	 *            if true, image data is down scaled to grayscale before
95  	 *            returned
96  	 * @return true - image contents were successfully put into aOutImage<br>
97  	 *         false - some error (aOutImage is null, IOException,
98  	 *         URISyntaxException, error img.getRGB)
99  	 */
100 	public static final boolean loadImageFile(URL url, IMAGE outImage, boolean toGrey) {
101 		try {
102 			return postProcess(url.toString(), ImageIO.read(url), outImage, toGrey);
103 
104 		} catch (IOException ignored) {
105 		}
106 		return false;
107 	}
108 
109 	/*
110 	 * no javadoc
111 	 * 
112 	 * postprocesses the java buffered image and creates a VSv3 transport layer
113 	 * compatible image out of it, color-to-grayscale can be performed in this
114 	 * step on demand, File class is necessary for file name string only
115 	 * 
116 	 * returns false in the following cases img.getRGB error
117 	 */
118 	@Deprecated
119 	private static final boolean postProcess(File imgFile, BufferedImage img, IMAGE aOutImage, boolean aChangeToGrey) {
120 		// (1) obtain TYPE_INT_ARGB pixel array out of "img"
121 
122 		int imgWidth = img.getWidth();
123 		int imgHeight = img.getHeight();
124 
125 		int[] pixelArr = new int[imgWidth * imgHeight];
126 
127 		if (img.getRGB(0, 0, imgWidth, imgHeight, pixelArr, 0, imgWidth) == null)
128 			return false;
129 
130 		// (2) create TINE IMAGE type out of it
131 
132 		IMAGE.FrameHeader frameHeader = aOutImage.getFrameHeader();
133 		IMAGE.SourceHeader sourceHeader = aOutImage.getSourceHeader();
134 
135 		if (aChangeToGrey == true) {
136 			frameHeader.bytesPerPixel = 1;
137 			frameHeader.effectiveBitsPerPixel = 8;
138 			frameHeader.imageFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
139 			frameHeader.sourceFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
140 		} else {
141 			// keep RGB
142 			frameHeader.bytesPerPixel = 3;
143 			frameHeader.effectiveBitsPerPixel = 24;
144 			frameHeader.imageFormat = ImageFormat.IMAGE_FORMAT_RGB.getId();
145 			frameHeader.sourceFormat = ImageFormat.IMAGE_FORMAT_RGB.getId();
146 		}
147 
148 		frameHeader.appendedFrameSize = imgWidth * imgHeight * frameHeader.bytesPerPixel;
149 
150 		// mdavid: commented
151 		// frameHeader.imageFlags = (int)
152 		// VideoHeaderV3.CF_IMAGE_FLAG_IMAGE_LOSSLESS
153 		// | (int) VideoHeaderV3.CF_IMAGE_FLAG_LITTLE_ENDIAN_BYTE_ORDER;
154 
155 		// mdavid: changed
156 		frameHeader.imageFlags = ImageFlag.IMAGE_LOSSLESS.getId() | ImageFlag.LITTLE_ENDIAN_BYTE_ORDER.getId();
157 
158 		frameHeader.sourceHeight = imgHeight;
159 		frameHeader.sourceWidth = imgWidth;
160 		frameHeader.xScale = 1;
161 		frameHeader.yScale = 1;
162 
163 		// TODO: could be too long and truncated badly
164 		sourceHeader.cameraPortName = "file://" + imgFile.getName();
165 
166 		long msTimeEpoch = imgFile.lastModified();
167 		sourceHeader.timestampMicroseconds = (int) (((msTimeEpoch % ((long) 1000)) * ((long) 1000)));
168 		sourceHeader.timestampSeconds = (int) (((msTimeEpoch) / ((long) 1000)));
169 
170 		sourceHeader.totalLength = IMAGE.HEADER_SIZE + frameHeader.appendedFrameSize;
171 
172 		byte[] buf = new byte[frameHeader.appendedFrameSize];
173 
174 		// imperformant, but fast-to-implement solution:
175 		// Note: maybe make faster!
176 
177 		if (aChangeToGrey == true) {
178 			for (int i = 0; i < frameHeader.sourceWidth * frameHeader.sourceHeight; i++) {
179 				double r = (double) ((pixelArr[i] >> 16) & 0xff);
180 				double g = (double) ((pixelArr[i] >> 8) & 0xff);
181 				double b = (double) (pixelArr[i] & 0xff);
182 				buf[i] = (byte) (0.114 * b + 0.587 * g + 0.299 * r);
183 			}
184 		} else {
185 			int pos = 0;
186 			for (int i = 0; i < frameHeader.sourceWidth * frameHeader.sourceHeight; i++) {
187 				buf[pos++] = (byte) ((pixelArr[i] >> 16) & 0xff); // R
188 				buf[pos++] = (byte) ((pixelArr[i] >> 8) & 0xff); // G
189 				buf[pos++] = (byte) (pixelArr[i] & 0xff); // B
190 			}
191 		}
192 
193 		aOutImage.setImageFrameBuffer(buf);
194 
195 		return true;
196 	}
197 
198 	/*
199 	 * no javadoc
200 	 * 
201 	 * postprocesses the java buffered image and creates a VSv3 transport layer
202 	 * compatible image out of it, color-to-grayscale can be performed in this
203 	 * step on demand, File class is necessary for file name string only
204 	 * 
205 	 * returns false in the following cases img.getRGB error
206 	 */
207 	@SuppressWarnings("deprecation")
208 	private static final boolean postProcess(String defltCameraPortName, BufferedImage img, IMAGE aOutImage,
209 			boolean aChangeToGrey) {
210 		// (1) obtain TYPE_INT_ARGB pixel array out of "img"
211 
212 		int imgWidth = img.getWidth();
213 		int imgHeight = img.getHeight();
214 
215 		int[] pixelArr = new int[imgWidth * imgHeight];
216 
217 		if (img.getRGB(0, 0, imgWidth, imgHeight, pixelArr, 0, imgWidth) == null)
218 			return false;
219 
220 		// (2) create TINE IMAGE type out of it
221 
222 		IMAGE.FrameHeader frameHeader = aOutImage.getFrameHeader();
223 		IMAGE.SourceHeader sourceHeader = aOutImage.getSourceHeader();
224 
225 		if (aChangeToGrey == true) {
226 			frameHeader.bytesPerPixel = 1;
227 			frameHeader.effectiveBitsPerPixel = 8;
228 			frameHeader.imageFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
229 			frameHeader.sourceFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
230 		} else {
231 			// keep RGB
232 			frameHeader.bytesPerPixel = 3;
233 			frameHeader.effectiveBitsPerPixel = 24;
234 			frameHeader.imageFormat = ImageFormat.IMAGE_FORMAT_RGB.getId();
235 			frameHeader.sourceFormat = ImageFormat.IMAGE_FORMAT_RGB.getId();
236 		}
237 
238 		frameHeader.appendedFrameSize = imgWidth * imgHeight * frameHeader.bytesPerPixel;
239 
240 		// mdavid: commented
241 		// frameHeader.imageFlags = (int)
242 		// VideoHeaderV3.CF_IMAGE_FLAG_IMAGE_LOSSLESS
243 		// | (int) VideoHeaderV3.CF_IMAGE_FLAG_LITTLE_ENDIAN_BYTE_ORDER;
244 
245 		// mdavid: changed
246 		frameHeader.imageFlags = ImageFlag.IMAGE_LOSSLESS.getId() | ImageFlag.LITTLE_ENDIAN_BYTE_ORDER.getId();
247 
248 		frameHeader.sourceHeight = imgHeight;
249 		frameHeader.sourceWidth = imgWidth;
250 		frameHeader.xScale = 1;
251 		frameHeader.yScale = 1;
252 
253 		// TODO: could be too long and truncated badly
254 		sourceHeader.cameraPortName = defltCameraPortName;
255 
256 		long msTimeEpoch = System.currentTimeMillis();
257 		sourceHeader.timestampMicroseconds = (int) (((msTimeEpoch % ((long) 1000)) * ((long) 1000)));
258 		sourceHeader.timestampSeconds = (int) (((msTimeEpoch) / ((long) 1000)));
259 
260 		sourceHeader.totalLength = IMAGE.HEADER_SIZE + frameHeader.appendedFrameSize;
261 
262 		byte[] buf = new byte[frameHeader.appendedFrameSize];
263 
264 		// imperformant, but fast-to-implement solution:
265 		// Note: maybe make faster!
266 
267 		if (aChangeToGrey == true) {
268 			for (int i = 0; i < frameHeader.sourceWidth * frameHeader.sourceHeight; i++) {
269 				double r = (double) ((pixelArr[i] >> 16) & 0xff);
270 				double g = (double) ((pixelArr[i] >> 8) & 0xff);
271 				double b = (double) (pixelArr[i] & 0xff);
272 				buf[i] = (byte) (0.114 * b + 0.587 * g + 0.299 * r);
273 			}
274 		} else {
275 			int pos = 0;
276 			for (int i = 0; i < frameHeader.sourceWidth * frameHeader.sourceHeight; i++) {
277 				buf[pos++] = (byte) ((pixelArr[i] >> 16) & 0xff); // R
278 				buf[pos++] = (byte) ((pixelArr[i] >> 8) & 0xff); // G
279 				buf[pos++] = (byte) (pixelArr[i] & 0xff); // B
280 			}
281 		}
282 
283 		aOutImage.setImageFrameBuffer(buf);
284 		return true;
285 	}
286 
287 	/**
288 	 * loads an IMM (proprietary, VSV2, raw, greyscale) image file with a give
289 	 * filename from disk and puts its contents to TINE IMAGE data type. An IMM
290 	 * file can consist of multiple video frames glued together. In such case,
291 	 * only the first one is loaded! <br>
292 	 * 
293 	 * @param aFileName
294 	 *            file name (may include path)
295 	 * @param aOutImage
296 	 *            TINE IMAGE data type to put image contents to
297 	 * @return true - image contents were successfully put into aOutImage<br>
298 	 *         false - some error (aOutImage is null, IOException)
299 	 */
300 	@SuppressWarnings("deprecation")
301 	public static final boolean loadIMM(String aFileName, IMAGE aOutImage) {
302 		if (aOutImage == null)
303 			return false;
304 
305 		int width;
306 		int height;
307 		int bytesperpixel;
308 		int bitsperpixel;
309 		double scale;
310 		FileInputStream fis = null;
311 		DataInputStream dis = null;
312 		byte[] imagebits = null;
313 
314 		// read the full file into buffered array
315 
316 		// read in first four bytes
317 		// read in second four bytes
318 
319 		// Analyze
320 		// check whether file length is big enough
321 		// read in image bits
322 
323 		// read in 8 bytes -> double scale
324 
325 		width = -1;
326 		height = -1;
327 		bytesperpixel = -1;
328 		bitsperpixel = -1;
329 		scale = -1.0;
330 
331 		try {
332 			fis = new FileInputStream(aFileName);
333 			dis = new DataInputStream(fis);
334 
335 			long fsize = fis.getChannel().size();
336 
337 			width = dis.readUnsignedShort();
338 			width = ((width & 0xff) << 8) + (width >> 8);
339 			bytesperpixel = dis.readUnsignedShort();
340 			bytesperpixel = ((bytesperpixel & 0xff) << 8) + (bytesperpixel >> 8);
341 			height = dis.readUnsignedShort();
342 			height = ((height & 0xff) << 8) + (height >> 8);
343 			bitsperpixel = dis.readUnsignedShort();
344 			bitsperpixel = ((bitsperpixel & 0xff) << 8) + (bitsperpixel >> 8);
345 
346 			bytesperpixel /= 8;
347 			if (bytesperpixel == 0)
348 				bytesperpixel = 1;
349 
350 			if ((bitsperpixel == 0) || (bitsperpixel > (8 * bytesperpixel)))
351 				bitsperpixel = bytesperpixel * 8;
352 
353 			// now we can read in data, file size should be bigger than
354 			// width*height+16
355 
356 			if (fsize < (width * height * bytesperpixel + 16))
357 				throw new EOFException();
358 
359 			imagebits = new byte[width * height * bytesperpixel];
360 
361 			dis.read(imagebits, 0, imagebits.length);
362 
363 			byte[] buf = new byte[8];
364 
365 			dis.read(buf, 0, buf.length);
366 
367 			int off = 0;
368 			long scale_long = (((long) buf[off + 0]) & 0xFF) | ((((long) buf[off + 1]) & 0xFF) << 8)
369 					| ((((long) buf[off + 2]) & 0xFF) << 16) | ((((long) buf[off + 3]) & 0xFF) << 24)
370 					| ((((long) buf[off + 4]) & 0xFF) << 32) | ((((long) buf[off + 5]) & 0xFF) << 40)
371 					| ((((long) buf[off + 6]) & 0xFF) << 48) | ((((long) buf[off + 7]) & 0xFF) << 56);
372 
373 			scale = Double.longBitsToDouble(scale_long);
374 		} catch (EOFException eof) {
375 			// System.out.println( "EOF reached " );
376 			return false;
377 		} catch (IOException ioe) {
378 			// System.out.println( "IO error: " + ioe );
379 			return false;
380 		} finally {
381 			try {
382 				if (dis != null)
383 					dis.close();
384 			} catch (IOException ex) {
385 			}
386 			try {
387 				if (fis != null)
388 					fis.close();
389 			} catch (IOException ex) {
390 			}
391 			dis = null;
392 			fis = null;
393 		}
394 
395 		IMAGE.FrameHeader frameHeader = aOutImage.getFrameHeader();
396 		IMAGE.SourceHeader sourceHeader = aOutImage.getSourceHeader();
397 
398 		frameHeader.bytesPerPixel = bytesperpixel;
399 		frameHeader.effectiveBitsPerPixel = bitsperpixel;
400 		frameHeader.imageFormat = ImageFormat.IMAGE_FORMAT_GRAY.getId();
401 		frameHeader.sourceFormat = frameHeader.imageFormat;
402 
403 		frameHeader.appendedFrameSize = width * height * frameHeader.bytesPerPixel;
404 
405 		// mdavid: changed
406 		frameHeader.imageFlags |= ImageFlag.IMAGE_LOSSLESS.getId();
407 
408 		frameHeader.sourceHeight = height;
409 		frameHeader.sourceWidth = width;
410 		frameHeader.xScale = (float) scale;
411 		frameHeader.yScale = (float) scale;
412 
413 		File imgFile = new File(aFileName);
414 
415 		// TODO: could be too long and truncated badly
416 		sourceHeader.cameraPortName = "file://" + imgFile.getName();
417 
418 		long msTimeEpoch = imgFile.lastModified();
419 		sourceHeader.timestampMicroseconds = (int) (((msTimeEpoch % ((long) 1000)) * ((long) 1000)));
420 		sourceHeader.timestampSeconds = (int) (((msTimeEpoch) / ((long) 1000)));
421 
422 		sourceHeader.totalLength = IMAGE.HEADER_SIZE + frameHeader.appendedFrameSize;
423 
424 		aOutImage.setImageFrameBuffer(imagebits);
425 
426 		return true;
427 	}
428 
429 	/**
430 	 * prints all formats that Java ImageIO class is able to read to stdout.
431 	 * This helps when getting to know which file formats are supported.
432 	 */
433 	public static void printLoadableFormats() {
434 		String[] names = ImageIO.getReaderFormatNames();
435 		for (String name : names)
436 			System.out.println(name);
437 	}
438 
439 	/**
440 	 * convenience overload, accepts filename as String instead of File class
441 	 * 
442 	 * @see write( File, Image, VideoHeaderV3)
443 	 */
444 	// public static boolean write(String aFileName, Image aImg, VideoHeaderV3
445 	// aHdr) { // mdavid: commented
446 	public static boolean write(String aFileName, Image aImg, IMAGE timage) {
447 		return write(new File(aFileName), aImg, timage);
448 	}
449 
450 	/**
451 	 * saves the java.awt.Image passed to a newly created (overwritten) PNG
452 	 * file. Additional metadata describing the Image is taken from a special
453 	 * header and put into an additional textfile placed beside the PNG file. <br>
454 	 * 
455 	 * @param aFile
456 	 *            java.io.File where to store the png image
457 	 * @param aImg
458 	 *            Image data to be written to PNG
459 	 * @param aHdr
460 	 *            contains meta properties of Image that will be written to .txt
461 	 *            file
462 	 * @return true - image contents and text file were successfully saved<br>
463 	 *         false - some error (IOException on png or txt)
464 	 */
465 	public static boolean write(File aFile, Image aImg, IMAGE timage) {
466 		try {
467 			ImageIO.write(TImageUtils.toBufferedImage(aImg), "png", aFile);
468 
469 			// add .txt header afterwards
470 			String wholename = aFile.getAbsolutePath();
471 			wholename += ".txt";
472 
473 			Writer output = null;
474 			try {
475 				// use buffering
476 				// FileWriter always assumes default encoding is OK!
477 				File txtF = new File(wholename);
478 				txtF.createNewFile();
479 
480 				String n = System.getProperty("line.separator");
481 				DateFormat dfmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
482 
483 				output = new BufferedWriter(new FileWriter(txtF));
484 
485 				IMAGE.SourceHeader srcH = timage.getSourceHeader();
486 				IMAGE.FrameHeader frmH = timage.getFrameHeader();
487 
488 				output.write("SourceHeader." + n);
489 				output.write(" .version             " + srcH.versionTag + n);
490 				output.write(" .timestamp           "
491 						+ dfmt.format(new Date((srcH.timestampSeconds * 1000L) + (srcH.timestampMicroseconds / 1000L)))
492 						+ n);
493 				output.write(" .cameraport          \"" + srcH.cameraPortName + "\"" + n);
494 				output.write(" .total_length        " + srcH.totalLength + n);
495 
496 				output.write(" FrameHeader" + n);
497 				output.write("       .sourceWidth         	" + frmH.sourceWidth + n);
498 				output.write("       .sourceHeight        	" + frmH.sourceHeight + n);
499 				output.write("       .aoiWidth     			" + frmH.aoiWidth + n);
500 				output.write("       .aoiHeight    			" + frmH.aoiHeight + n);
501 				output.write("       .xStart       			" + frmH.xStart + n);
502 				output.write("       .yStart       			" + frmH.yStart + n);
503 				output.write("       .bytesPerPixel    		" + frmH.bytesPerPixel + n);
504 				output.write("       .effectiveBitsPerPixel 	" + frmH.effectiveBitsPerPixel + n);
505 				output.write("       .horizontalBinning   	" + frmH.horizontalBinning + n);
506 				output.write("       .verticalBinning  		" + frmH.verticalBinning + n);
507 				output.write("       .sourceFormat    		" + ImageFormat.valueOf(frmH.sourceFormat) + n);
508 				output.write("       .currentFormat    		" + ImageFormat.valueOf(frmH.imageFormat) + n);
509 				output.write("       .frameNumber   			" + frmH.frameNumber + n);
510 				output.write("       .eventNumer    			" + frmH.eventNumber + n);
511 				output.write("       .xScale       			" + frmH.xScale + n);
512 				output.write("       .yScale       			" + frmH.yScale + n);
513 				output.write("       .imageRotation   		" + frmH.imageRotation + n);
514 
515 				// mdavid: commented
516 				// output.write("       .imageFlags   			" +
517 				// VideoHeaderV3.flagsToString(frmH.imageFlags) + n);
518 
519 				// mdavid: changed
520 				output.write("       .imageFlags   			" + ImageFlag.toString(frmH.imageFlags) + n);
521 
522 				output.write("       .appendedFrameSize 		" + frmH.appendedFrameSize + n);
523 				output.write("Header END" + n);
524 			} catch (IOException ex1) {
525 				return false;
526 				
527 			} finally {
528 				// flush and close both "output" and its underlying FileWriter
529 				if (output != null)
530 					output.close();
531 			}
532 
533 		} catch (IOException ex) {
534 			return false;
535 		}
536 
537 		return true;
538 	}
539 }