1 package de.desy.acop.video.timageio;
2
3 import java.awt.image.BufferedImage;
4 import java.math.BigInteger;
5 import java.security.MessageDigest;
6 import java.security.NoSuchAlgorithmException;
7 import java.util.Calendar;
8 import java.util.GregorianCalendar;
9 import java.util.HashMap;
10 import java.util.Hashtable;
11 import java.util.Map;
12
13 import javax.imageio.metadata.IIOMetadata;
14
15 import com.sun.imageio.plugins.png.PNGMetadata;
16
17 import de.desy.acop.video.displayer.ImageFlag;
18 import de.desy.acop.video.displayer.ImageFormat;
19 import de.desy.acop.video.displayer.timage.TImageUtils;
20 import de.desy.tine.types.IMAGE;
21 import de.desy.tine.types.IMAGE.FrameHeader;
22 import de.desy.tine.types.IMAGE.SourceHeader;
23
24
25
26
27
28
29
30 public class TImageMetadata {
31
32 private static final String DEFLT_SOFTWARE = "TImageIO - Java TINE Image I/O API for loading/saving TINE images (betta ver. 0.0.02)";
33 private static final String DEFLT_COMMENT = "Betta version: 0.0.03";
34
35 public static final String KEY_SOFTWARE = "Software";
36 public static final String KEY_COMMENT = "Comment";
37
38 public static final String KEY_CAMERA_PORT_ID = "VSv3.hdr.src.cameraPortId";
39 public static final String KEY_VERSION_TAG = "VSv3.hdr.src.versionTag";
40 public static final String KEY_TIMESTAMP_SECONDS = "VSv3.hdr.src.timestampSeconds";
41 public static final String KEY_TIMESTAMP_MICROSECONDS = "VSv3.hdr.src.timestampMicroseconds";
42 public static final String KEY_CAMERA_PORT_NAME = "VSv3.hdr.src.cameraPortName";
43 public static final String KEY_SOURCE_WIDTH = "VSv3.hdr.frm.sourceWidth";
44 public static final String KEY_SOURCE_HEIGHT = "VSv3.hdr.frm.sourceHeight";
45 public static final String KEY_AOI_WIDTH = "VSv3.hdr.frm.aoiWidth";
46 public static final String KEY_AOI_HEIGHT = "VSv3.hdr.frm.aoiHeight";
47 public static final String KEY_XSTART = "VSv3.hdr.frm.xStart";
48 public static final String KEY_YSTART = "VSv3.hdr.frm.yStart";
49 public static final String KEY_BYTES_PER_PIXEL = "VSv3.hdr.frm.bytesPerPixel";
50 public static final String KEY_EFFECTIVE_BITS_PER_PIXEL = "VSv3.hdr.frm.effectiveBitsPerPixel";
51 public static final String KEY_HORIZONTAL_BINNING = "VSv3.hdr.frm.horizontalBinning";
52 public static final String KEY_VERTICAL_BINNING = "VSv3.hdr.frm.verticalBinning";
53 public static final String KEY_SOURCE_FORMAT = "VSv3.hdr.frm.sourceFormat";
54 public static final String KEY_IMAGE_FORMAT = "VSv3.hdr.frm.imageFormat";
55 public static final String KEY_FRAME_NUMBER = "VSv3.hdr.frm.frameNumber";
56 public static final String KEY_EVENT_NUMBER = "VSv3.hdr.frm.eventNumber";
57 public static final String KEY_XSCALE = "VSv3.hdr.frm.xScale";
58 public static final String KEY_YSCALE = "VSv3.hdr.frm.yScale";
59 public static final String KEY_IMAGE_ROTATION = "VSv3.hdr.frm.imageRotation";
60 public static final String KEY_IMAGE_FLAGS = "VSv3.hdr.frm.imageFlags";
61 public static final String KEY_APPENDED_FRAME_SIZE = "VSv3.hdr.frm.appendedFrameSize";
62 public static final String KEY_MD5HASH = "VSV3.frameBuffer.md5hash";
63 public static final String KEY_ISPARE1 = "VSv3.hdr.frm.ispare1";
64 public static final String KEY_ISPARE2 = "VSv3.hdr.frm.ispare2";
65 public static final String KEY_ISPARE3 = "VSv3.hdr.frm.ispare3";
66 public static final String KEY_FSPARE1 = "VSv3.hdr.frm.fspare1";
67 public static final String KEY_FSPARE2 = "VSv3.hdr.frm.fspare2";
68 public static final String KEY_FSPARE3 = "VSv3.hdr.frm.fspare3";
69 public static final String KEY_OPT_TEXT = "VSv3.opt.text";
70 public static final String KEY_VTOB = "vtOb";
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public String software = DEFLT_SOFTWARE;
89
90
91
92
93 public String comment = DEFLT_COMMENT;
94
95
96
97
98 public Long cameraPortId;
99
100
101
102
103 public Long versionTag;
104
105
106
107
108 public Integer timestampSeconds;
109
110
111
112
113 public Integer timestampMicroseconds;
114
115
116
117
118 public String cameraPortName;
119
120
121
122
123 public Integer sourceWidth;
124
125
126
127
128 public Integer sourceHeight;
129
130
131
132
133 public Integer aoiWidth;
134
135
136
137
138 public Integer aoiHeight;
139
140
141
142
143 public Integer xStart;
144
145
146
147
148 public Integer yStart;
149
150
151
152
153 public Integer bytesPerPixel;
154
155
156
157
158 public Integer effectiveBitsPerPixel;
159
160
161
162
163 public Integer horizontalBinning;
164
165
166
167
168 public Integer verticalBinning;
169
170
171
172
173 public ImageFormat sourceFormat;
174
175
176
177
178 public ImageFormat imageFormat;
179
180
181
182
183 public Long frameNumber;
184
185
186
187
188 public Long eventNumber;
189
190
191
192
193 public Float xScale;
194
195
196
197
198 public Float yScale;
199
200
201
202
203 public Float imageRotation;
204
205
206
207
208 public Integer imageFlags;
209
210
211
212
213 public Integer appendedFrameSize;
214
215
216
217
218
219
220
221 public String md5hash;
222
223
224
225
226
227
228
229
230
231
232
233 public Map<String, String> optVars = new HashMap<String, String>();
234
235 public int ispare1 = -1;
236 public int ispare2 = -1;
237 public int ispare3 = -1;
238
239 public float fspare1 = -1;
240 public float fspare2 = -1;
241 public float fspare3 = -1;
242
243
244
245
246
247
248
249 private byte[] vtOb;
250
251
252
253
254
255
256
257
258 public TImageMetadata(BufferedImage bi) {
259 read(bi, true);
260 }
261
262
263
264
265
266
267
268
269 public TImageMetadata(BufferedImage bi, boolean rejectNonArchival) {
270 read(bi, rejectNonArchival);
271 }
272
273
274
275
276
277
278
279
280
281
282
283
284 public TImageMetadata(IIOMetadata iioMetadata) {
285 if (iioMetadata == null)
286 throw new NullPointerException("iioMetadata == null!");
287
288 if (iioMetadata instanceof PNGMetadata)
289 read((PNGMetadata) iioMetadata);
290 else
291 throw new IllegalArgumentException("Unsupported class of metadata: " + iioMetadata.getClass().getName());
292 }
293
294
295
296
297
298
299
300 public TImageMetadata(PNGMetadata pngMetadata) {
301 read(pngMetadata);
302 }
303
304
305
306
307
308
309
310 public TImageMetadata(IMAGE timage) {
311 read(timage);
312 }
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 private void read(PNGMetadata pngMetadata) {
330 if (pngMetadata == null)
331 throw new NullPointerException("pngMetadata == null!");
332
333 int size = pngMetadata.tEXt_keyword.size();
334 for (int index = 0; index < size; index++) {
335 String key = (String) pngMetadata.tEXt_keyword.get(index);
336
337 if (KEY_SOFTWARE.equals(key)) {
338 software = (String) pngMetadata.tEXt_text.get(index);
339 } else if (KEY_COMMENT.equals(key)) {
340 comment = (String) pngMetadata.tEXt_text.get(index);
341 } else if (KEY_CAMERA_PORT_ID.equals(key)) {
342 cameraPortId = Long.valueOf((String) pngMetadata.tEXt_text.get(index));
343 } else if (KEY_VERSION_TAG.equals(key)) {
344 versionTag = Long.decode((String) pngMetadata.tEXt_text.get(index));
345 } else if (KEY_TIMESTAMP_SECONDS.equals(key)) {
346 timestampSeconds = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
347 } else if (KEY_TIMESTAMP_MICROSECONDS.equals(key)) {
348 timestampMicroseconds = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
349 } else if (KEY_CAMERA_PORT_NAME.equals(key)) {
350 cameraPortName = (String) pngMetadata.tEXt_text.get(index);
351 } else if (KEY_SOURCE_WIDTH.equals(key)) {
352 sourceWidth = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
353 } else if (KEY_SOURCE_HEIGHT.equals(key)) {
354 sourceHeight = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
355 } else if (KEY_AOI_WIDTH.equals(key)) {
356 aoiWidth = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
357 } else if (KEY_AOI_HEIGHT.equals(key)) {
358 aoiHeight = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
359 } else if (KEY_XSTART.equals(key)) {
360 xStart = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
361 } else if (KEY_YSTART.equals(key)) {
362 yStart = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
363 } else if (KEY_BYTES_PER_PIXEL.equals(key)) {
364 bytesPerPixel = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
365 } else if (KEY_EFFECTIVE_BITS_PER_PIXEL.equals(key)) {
366 effectiveBitsPerPixel = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
367 } else if (KEY_HORIZONTAL_BINNING.equals(key)) {
368 horizontalBinning = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
369 } else if (KEY_VERTICAL_BINNING.equals(key)) {
370 verticalBinning = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
371 } else if (KEY_SOURCE_FORMAT.equals(key)) {
372 sourceFormat = ImageFormat.valueOf((String) pngMetadata.tEXt_text.get(index));
373 } else if (KEY_IMAGE_FORMAT.equals(key)) {
374 imageFormat = ImageFormat.valueOf((String) pngMetadata.tEXt_text.get(index));
375 } else if (KEY_FRAME_NUMBER.equals(key)) {
376 frameNumber = Long.valueOf((String) pngMetadata.tEXt_text.get(index));
377 } else if (KEY_EVENT_NUMBER.equals(key)) {
378 eventNumber = Long.valueOf((String) pngMetadata.tEXt_text.get(index));
379 } else if (KEY_XSCALE.equals(key)) {
380 xScale = Float.valueOf((String) pngMetadata.tEXt_text.get(index));
381 } else if (KEY_YSCALE.equals(key)) {
382 yScale = Float.valueOf((String) pngMetadata.tEXt_text.get(index));
383 } else if (KEY_IMAGE_ROTATION.equals(key)) {
384 imageRotation = Float.valueOf((String) pngMetadata.tEXt_text.get(index));
385 } else if (KEY_IMAGE_FLAGS.equals(key)) {
386 imageFlags = Integer.decode((String) pngMetadata.tEXt_text.get(index));
387 } else if (KEY_APPENDED_FRAME_SIZE.equals(key)) {
388 appendedFrameSize = Integer.valueOf((String) pngMetadata.tEXt_text.get(index));
389 } else if (KEY_MD5HASH.equals(key)) {
390 md5hash = (new BigInteger((String) pngMetadata.tEXt_text.get(index), 16)).toString(16);
391 } else if (KEY_ISPARE1.equals(key)) {
392 ispare1 = Integer.parseInt((String) pngMetadata.tEXt_text.get(index));
393 } else if (KEY_ISPARE2.equals(key)) {
394 ispare2 = Integer.parseInt((String) pngMetadata.tEXt_text.get(index));
395 } else if (KEY_ISPARE3.equals(key)) {
396 ispare3 = Integer.parseInt((String) pngMetadata.tEXt_text.get(index));
397 } else if (KEY_FSPARE1.equals(key)) {
398 fspare1 = Float.parseFloat((String) pngMetadata.tEXt_text.get(index));
399 } else if (KEY_FSPARE2.equals(key)) {
400 fspare2 = Float.parseFloat((String) pngMetadata.tEXt_text.get(index));
401 } else if (KEY_FSPARE3.equals(key)) {
402 fspare3 = Float.parseFloat((String) pngMetadata.tEXt_text.get(index));
403 } else if (key.startsWith(KEY_OPT_TEXT)) {
404 if (optVars.containsKey(key))
405 throw new IllegalArgumentException("Duplicate keyword: " + key);
406 optVars.put(key, (String) pngMetadata.tEXt_text.get(index));
407 }
408 }
409
410
411 size = pngMetadata.unknownChunkType.size();
412 for (int index = 0; index < size; index++) {
413 String type = (String) pngMetadata.unknownChunkType.get(index);
414 if (KEY_VTOB.equals(type))
415 vtOb = (byte[]) pngMetadata.unknownChunkData.get(index);
416 }
417
418 if (!isArchival())
419 setDefault(pngMetadata);
420 validateArchivalMetadata(pngMetadata);
421 }
422
423
424
425
426
427
428
429
430
431 private void read(IMAGE timage) throws IllegalArgumentException {
432 if (timage == null)
433 throw new NullPointerException("timage == null!");
434
435
436 SourceHeader srcHeader = timage.getSourceHeader();
437 cameraPortId = srcHeader.cameraPortId;
438 cameraPortName = srcHeader.cameraPortName;
439 versionTag = srcHeader.versionTag;
440 timestampSeconds = srcHeader.timestampSeconds;
441 timestampMicroseconds = srcHeader.timestampMicroseconds;
442
443
444 FrameHeader frameHeader = timage.getFrameHeader();
445 sourceWidth = frameHeader.sourceWidth;
446 sourceHeight = frameHeader.sourceHeight;
447 aoiWidth = frameHeader.aoiWidth;
448 aoiHeight = frameHeader.aoiHeight;
449 xStart = frameHeader.xStart;
450 yStart = frameHeader.yStart;
451 bytesPerPixel = frameHeader.bytesPerPixel;
452 effectiveBitsPerPixel = frameHeader.effectiveBitsPerPixel;
453 horizontalBinning = frameHeader.horizontalBinning;
454 verticalBinning = frameHeader.verticalBinning;
455 sourceFormat = ImageFormat.valueOf(frameHeader.sourceFormat);
456 imageFormat = ImageFormat.valueOf(frameHeader.imageFormat);
457 frameNumber = frameHeader.frameNumber;
458 eventNumber = frameHeader.eventNumber;
459 xScale = frameHeader.xScale;
460 yScale = frameHeader.yScale;
461 imageRotation = frameHeader.imageRotation;
462 imageFlags = frameHeader.imageFlags;
463 appendedFrameSize = frameHeader.appendedFrameSize;
464 ispare1 = frameHeader.ispare1;
465 ispare2 = frameHeader.ispare2;
466 ispare3 = frameHeader.ispare3;
467 fspare1 = frameHeader.fspare1;
468 fspare2 = frameHeader.fspare2;
469 fspare3 = frameHeader.fspare3;
470
471 md5hash = md5hash(timage.getImageFrameBuffer());
472 }
473
474 private Object readProperty(BufferedImage bi, String name, Class<?> clazz, Object deflt, boolean reject)
475 throws IllegalArgumentException {
476 Object obj = bi.getProperty(name);
477 if (obj == null || obj == java.awt.Image.UndefinedProperty) {
478 if (reject)
479 throw new IllegalArgumentException("Undefined archival metadata: " + name);
480 return deflt;
481 }
482 try {
483 return clazz.cast(obj);
484 } catch (ClassCastException e) {
485 throw new IllegalArgumentException("Illegal archival metadata: " + name);
486 }
487 }
488
489
490
491
492
493
494
495
496
497
498
499
500 private void read(BufferedImage bi, boolean rejectNonArchival) throws IllegalArgumentException {
501
502 if (bi == null)
503 throw new NullPointerException("image == null!");
504
505 frameNumber = (Long) readProperty(bi, KEY_FRAME_NUMBER, Long.class, 0L, rejectNonArchival);
506
507 eventNumber = (Long) readProperty(bi, KEY_EVENT_NUMBER, Long.class, 0L, rejectNonArchival);
508
509 xStart = (Integer) readProperty(bi, KEY_XSTART, Integer.class, 0, rejectNonArchival);
510 yStart = (Integer) readProperty(bi, KEY_YSTART, Integer.class, 0, rejectNonArchival);
511
512 xScale = (Float) readProperty(bi, KEY_XSCALE, Float.class, -1F, rejectNonArchival);
513 yScale = (Float) readProperty(bi, KEY_YSCALE, Float.class, -1F, rejectNonArchival);
514
515 imageRotation = (Float) readProperty(bi, KEY_IMAGE_ROTATION, Float.class, 0F, rejectNonArchival);
516
517 horizontalBinning = (Integer) readProperty(bi, KEY_HORIZONTAL_BINNING, Integer.class, 0, rejectNonArchival);
518 verticalBinning = (Integer) readProperty(bi, KEY_VERTICAL_BINNING, Integer.class, 0, rejectNonArchival);
519
520 imageFlags = (Integer) readProperty(bi, KEY_IMAGE_FLAGS, Integer.class, 0x1, rejectNonArchival);
521
522 ispare1 = (Integer) readProperty(bi, KEY_ISPARE1, Integer.class, -1, rejectNonArchival);
523 ispare2 = (Integer) readProperty(bi, KEY_ISPARE2, Integer.class, -1, rejectNonArchival);
524 ispare3 = (Integer) readProperty(bi, KEY_ISPARE3, Integer.class, -1, rejectNonArchival);
525
526 fspare1 = (Float) readProperty(bi, KEY_FSPARE1, Float.class, -1F, rejectNonArchival);
527 fspare2 = (Float) readProperty(bi, KEY_FSPARE2, Float.class, -1F, rejectNonArchival);
528 fspare3 = (Float) readProperty(bi, KEY_FSPARE3, Float.class, -1F, rejectNonArchival);
529
530 cameraPortName = (String) readProperty(bi, KEY_CAMERA_PORT_NAME, String.class, IMAGE.DEFAULT_CAMERA_PORT_NAME,
531 rejectNonArchival);
532 cameraPortId = (Long) readProperty(bi, KEY_CAMERA_PORT_ID, Long.class, IMAGE.DEFAULT_CAMERA_PORT_ID,
533 rejectNonArchival);
534
535 versionTag = (Long) readProperty(bi, KEY_VERSION_TAG, Long.class, IMAGE.IMAGE_INITIAL_VERSION,
536 rejectNonArchival);
537
538 timestampSeconds = (Integer) readProperty(bi, KEY_TIMESTAMP_SECONDS, Integer.class,
539 (int) (new GregorianCalendar().getTimeInMillis() / 1000), rejectNonArchival);
540
541 timestampMicroseconds = (Integer) readProperty(bi, KEY_TIMESTAMP_MICROSECONDS, Integer.class, 0,
542 rejectNonArchival);
543
544 Integer value = (Integer) readProperty(bi, KEY_IMAGE_FORMAT, Integer.class, null, rejectNonArchival);
545 imageFormat = (value == null ? getImageFormat(bi) : ImageFormat.valueOf(value));
546
547 value = (Integer) readProperty(bi, KEY_SOURCE_FORMAT, Integer.class, null, rejectNonArchival);
548 sourceFormat = (value == null ? imageFormat : ImageFormat.valueOf(value));
549
550 bytesPerPixel = (Integer) readProperty(bi, KEY_BYTES_PER_PIXEL, Integer.class, null, rejectNonArchival);
551 if (bytesPerPixel == null)
552 bytesPerPixel = getBytesPerPixel(bi);
553
554 effectiveBitsPerPixel = (Integer) readProperty(bi, KEY_EFFECTIVE_BITS_PER_PIXEL, Integer.class, null,
555 rejectNonArchival);
556 if (effectiveBitsPerPixel == null)
557 effectiveBitsPerPixel = getEffBytesPerPixel(bi);
558
559 sourceWidth = (Integer) readProperty(bi, KEY_SOURCE_WIDTH, Integer.class, bi.getWidth(), rejectNonArchival);
560 sourceHeight = (Integer) readProperty(bi, KEY_SOURCE_HEIGHT, Integer.class, bi.getHeight(), rejectNonArchival);
561
562 aoiWidth = (Integer) readProperty(bi, KEY_AOI_WIDTH, Integer.class, -1, rejectNonArchival);
563 if (aoiWidth != -1 && aoiWidth != sourceWidth)
564 throw new IllegalArgumentException("Invalid archival metadata: aoiWidth == " + aoiWidth);
565
566 aoiHeight = (Integer) readProperty(bi, KEY_AOI_HEIGHT, Integer.class, -1, rejectNonArchival);
567 if (aoiHeight != -1 && aoiHeight != sourceWidth)
568 throw new IllegalArgumentException("Invalid archival metadata: aoiHeight == " + aoiHeight);
569
570 appendedFrameSize = bytesPerPixel * sourceWidth * sourceHeight;
571 }
572
573 private ImageFormat getImageFormat(BufferedImage bi) {
574 int imageType = bi.getType();
575 switch (imageType) {
576 case BufferedImage.TYPE_BYTE_GRAY:
577 case BufferedImage.TYPE_USHORT_GRAY:
578 return ImageFormat.IMAGE_FORMAT_GRAY;
579 default:
580 return ImageFormat.IMAGE_FORMAT_RGB;
581 }
582 }
583
584 private int getBytesPerPixel(BufferedImage bi) {
585 int imageType = bi.getType();
586 switch (imageType) {
587 case BufferedImage.TYPE_BYTE_GRAY:
588 return 1;
589 case BufferedImage.TYPE_USHORT_GRAY:
590 return 2;
591 default:
592 return 3;
593 }
594 }
595
596 private int getEffBytesPerPixel(BufferedImage bi) {
597 int[] cs = bi.getColorModel().getComponentSize();
598 int effBits = 0;
599 for (int componentSize : cs)
600 effBits += componentSize;
601 return Math.min(effBits, 24);
602 }
603
604 public void write(IMAGE ti) {
605 if (ti == null)
606 throw new NullPointerException("ti == null!");
607
608
609 IMAGE.FrameHeader frmHdr = ti.getFrameHeader();
610 frmHdr.bytesPerPixel = bytesPerPixel;
611 frmHdr.effectiveBitsPerPixel = effectiveBitsPerPixel;
612 frmHdr.appendedFrameSize = appendedFrameSize;
613 frmHdr.imageFormat = imageFormat.getId();
614 frmHdr.sourceFormat = sourceFormat.getId();
615 frmHdr.sourceWidth = sourceWidth;
616 frmHdr.sourceHeight = sourceHeight;
617 frmHdr.frameNumber = frameNumber;
618 frmHdr.eventNumber = eventNumber;
619 frmHdr.aoiHeight = aoiHeight;
620 frmHdr.aoiWidth = aoiWidth;
621 frmHdr.xStart = xStart;
622 frmHdr.yStart = yStart;
623 frmHdr.xScale = xScale;
624 frmHdr.yScale = yScale;
625 frmHdr.ispare1 = ispare1;
626 frmHdr.ispare2 = ispare2;
627 frmHdr.ispare3 = ispare3;
628 frmHdr.fspare1 = fspare1;
629 frmHdr.fspare2 = fspare2;
630 frmHdr.fspare3 = fspare3;
631 frmHdr.horizontalBinning = horizontalBinning;
632 frmHdr.verticalBinning = verticalBinning;
633 frmHdr.imageFlags = imageFlags;
634
635
636 SourceHeader srcHdr = ti.getSourceHeader();
637 srcHdr.cameraPortName = cameraPortName;
638 srcHdr.timestampSeconds = timestampSeconds;
639 srcHdr.timestampMicroseconds = timestampMicroseconds;
640 srcHdr.cameraPortId = cameraPortId;
641 srcHdr.versionTag = versionTag;
642 srcHdr.totalLength = IMAGE.HEADER_SIZE + frmHdr.appendedFrameSize;
643 }
644
645 static Hashtable<String, Object> createProperties(IMAGE ti) {
646 Hashtable<String, Object> props = new Hashtable<String, Object>(32);
647
648
649 SourceHeader srcHdr = ti.getSourceHeader();
650 props.put(KEY_CAMERA_PORT_ID, srcHdr.cameraPortId);
651 props.put(KEY_CAMERA_PORT_NAME, srcHdr.cameraPortName);
652 props.put(KEY_VERSION_TAG, srcHdr.versionTag);
653 props.put(KEY_TIMESTAMP_SECONDS, srcHdr.timestampSeconds);
654 props.put(KEY_TIMESTAMP_MICROSECONDS, srcHdr.timestampMicroseconds);
655
656
657 FrameHeader frmHdr = ti.getFrameHeader();
658 props.put(KEY_SOURCE_WIDTH, frmHdr.sourceWidth);
659 props.put(KEY_SOURCE_HEIGHT, frmHdr.sourceHeight);
660 props.put(KEY_AOI_WIDTH, frmHdr.aoiWidth);
661 props.put(KEY_AOI_HEIGHT, frmHdr.aoiHeight);
662 props.put(KEY_XSTART, frmHdr.xStart);
663 props.put(KEY_YSTART, frmHdr.yStart);
664 props.put(KEY_BYTES_PER_PIXEL, frmHdr.bytesPerPixel);
665 props.put(KEY_EFFECTIVE_BITS_PER_PIXEL, frmHdr.effectiveBitsPerPixel);
666 props.put(KEY_HORIZONTAL_BINNING, frmHdr.horizontalBinning);
667 props.put(KEY_VERTICAL_BINNING, frmHdr.verticalBinning);
668 props.put(KEY_SOURCE_FORMAT, frmHdr.sourceFormat);
669 props.put(KEY_IMAGE_FORMAT, frmHdr.imageFormat);
670 props.put(KEY_FRAME_NUMBER, frmHdr.frameNumber);
671 props.put(KEY_EVENT_NUMBER, frmHdr.eventNumber);
672 props.put(KEY_XSCALE, frmHdr.xScale);
673 props.put(KEY_YSCALE, frmHdr.yScale);
674 props.put(KEY_IMAGE_ROTATION, frmHdr.imageRotation);
675 props.put(KEY_IMAGE_FLAGS, frmHdr.imageFlags);
676 props.put(KEY_APPENDED_FRAME_SIZE, frmHdr.appendedFrameSize);
677 props.put(KEY_ISPARE1, frmHdr.ispare1);
678 props.put(KEY_ISPARE2, frmHdr.ispare2);
679 props.put(KEY_ISPARE3, frmHdr.ispare3);
680 props.put(KEY_FSPARE1, frmHdr.fspare1);
681 props.put(KEY_FSPARE2, frmHdr.fspare2);
682 props.put(KEY_FSPARE3, frmHdr.fspare3);
683
684 return props;
685 }
686
687
688
689
690
691
692 public PNGMetadata toPngMetadata() {
693 PNGMetadata pngMetadata = new PNGMetadata();
694 addText(pngMetadata, KEY_SOFTWARE, software);
695 addText(pngMetadata, KEY_COMMENT, comment);
696 if (isArchival()) {
697 addText(pngMetadata, KEY_CAMERA_PORT_ID, cameraPortId.toString());
698 addText(pngMetadata, KEY_VERSION_TAG, String.format("0x%01x", versionTag));
699 addText(pngMetadata, KEY_TIMESTAMP_SECONDS, timestampSeconds.toString());
700 addText(pngMetadata, KEY_TIMESTAMP_MICROSECONDS, timestampMicroseconds.toString());
701 addText(pngMetadata, KEY_CAMERA_PORT_NAME, cameraPortName);
702 addText(pngMetadata, KEY_SOURCE_WIDTH, sourceWidth.toString());
703 addText(pngMetadata, KEY_SOURCE_HEIGHT, sourceHeight.toString());
704 addText(pngMetadata, KEY_AOI_WIDTH, aoiWidth.toString());
705 addText(pngMetadata, KEY_AOI_HEIGHT, aoiHeight.toString());
706 addText(pngMetadata, KEY_XSTART, xStart.toString());
707 addText(pngMetadata, KEY_YSTART, yStart.toString());
708 addText(pngMetadata, KEY_BYTES_PER_PIXEL, bytesPerPixel.toString());
709 addText(pngMetadata, KEY_EFFECTIVE_BITS_PER_PIXEL, effectiveBitsPerPixel.toString());
710 addText(pngMetadata, KEY_HORIZONTAL_BINNING, horizontalBinning.toString());
711 addText(pngMetadata, KEY_VERTICAL_BINNING, verticalBinning.toString());
712 addText(pngMetadata, KEY_SOURCE_FORMAT, sourceFormat.toString());
713 addText(pngMetadata, KEY_IMAGE_FORMAT, imageFormat.toString());
714 addText(pngMetadata, KEY_FRAME_NUMBER, frameNumber.toString());
715 addText(pngMetadata, KEY_EVENT_NUMBER, eventNumber.toString());
716 addText(pngMetadata, KEY_XSCALE, xScale.toString());
717 addText(pngMetadata, KEY_YSCALE, yScale.toString());
718 addText(pngMetadata, KEY_IMAGE_ROTATION, imageRotation.toString());
719 addText(pngMetadata, KEY_IMAGE_FLAGS, String.format("0x%01x", imageFlags |= ImageFlag.IMAGE_LOSSLESS
720 .getId()));
721 addText(pngMetadata, KEY_APPENDED_FRAME_SIZE, appendedFrameSize.toString());
722 addText(pngMetadata, KEY_ISPARE1, Integer.toString(ispare1));
723 addText(pngMetadata, KEY_ISPARE2, Integer.toString(ispare2));
724 addText(pngMetadata, KEY_ISPARE3, Integer.toString(ispare3));
725 addText(pngMetadata, KEY_FSPARE1, Float.toString(fspare1));
726 addText(pngMetadata, KEY_FSPARE2, Float.toString(fspare2));
727 addText(pngMetadata, KEY_FSPARE3, Float.toString(fspare3));
728
729 if (imageFormat == ImageFormat.IMAGE_FORMAT_GRAY && effectiveBitsPerPixel < 8 * bytesPerPixel) {
730 pngMetadata.sBIT_present = true;
731 pngMetadata.sBIT_grayBits = effectiveBitsPerPixel;
732 }
733
734 if (xScale != -1 || yScale != -1) {
735 pngMetadata.pHYs_present = true;
736 pngMetadata.pHYs_unitSpecifier = 1;
737 pngMetadata.pHYs_pixelsPerUnitXAxis = (int) (1000.0F * xScale);
738 pngMetadata.pHYs_pixelsPerUnitYAxis = (int) (1000.0F * yScale);
739 }
740 }
741
742
743 addTimeChunk(pngMetadata, System.currentTimeMillis());
744
745
746
747
748 if (md5hash != null)
749 addText(pngMetadata, KEY_MD5HASH, md5hash);
750
751
752 for (String key : optVars.keySet()) {
753 String value = optVars.get(key);
754 if (value != null)
755 addText(pngMetadata, key, value);
756 }
757
758
759 if (vtOb != null)
760 addUserChunk(pngMetadata, KEY_VTOB, vtOb);
761
762 return pngMetadata;
763 }
764
765 @SuppressWarnings("unchecked")
766 private void addText(PNGMetadata pngMetadata, String keyword, String text) {
767 pngMetadata.tEXt_keyword.add(keyword);
768 pngMetadata.tEXt_text.add(text);
769 }
770
771 @SuppressWarnings("unchecked")
772 private void addUserChunk(PNGMetadata pngMetadata, String keyword, byte[] data) {
773 pngMetadata.unknownChunkType.add(keyword);
774 pngMetadata.unknownChunkData.add(data);
775 }
776
777 private void addTimeChunk(PNGMetadata pngMetadata, long millis) {
778 Calendar cal = new GregorianCalendar();
779 cal.setTimeInMillis(millis);
780 pngMetadata.tIME_year = cal.get(Calendar.YEAR);
781 pngMetadata.tIME_month = cal.get(Calendar.MONTH) + 1;
782 pngMetadata.tIME_day = cal.get(Calendar.DAY_OF_MONTH);
783 pngMetadata.tIME_hour = cal.get(Calendar.HOUR_OF_DAY);
784 pngMetadata.tIME_minute = cal.get(Calendar.MINUTE);
785 pngMetadata.tIME_second = cal.get(Calendar.SECOND);
786 pngMetadata.tIME_present = true;
787 }
788
789 @Override
790 public String toString() {
791
792 StringBuilder sb = new StringBuilder();
793
794 if (software != null)
795 sb.append(KEY_SOFTWARE).append(" = ").append(software).append("\n");
796 if (comment != null)
797 sb.append(KEY_COMMENT).append(" = ").append(comment).append("\n");
798
799 if (versionTag != null)
800 sb.append(KEY_VERSION_TAG).append(" = ").append(String.format("0x%01x", versionTag)).append("\n");
801 if (cameraPortId != null)
802 sb.append(KEY_CAMERA_PORT_ID).append(" = ").append(cameraPortId).append("\n");
803 if (cameraPortName != null)
804 sb.append(KEY_CAMERA_PORT_NAME).append(" = ").append(cameraPortName).append("\n");
805
806 if (timestampSeconds != null)
807 sb.append(KEY_TIMESTAMP_SECONDS).append(" = ").append(timestampSeconds).append("\n");
808 if (timestampMicroseconds != null)
809 sb.append(KEY_TIMESTAMP_MICROSECONDS).append(" = ").append(timestampMicroseconds).append("\n");
810
811 if (sourceFormat != null)
812 sb.append(KEY_SOURCE_FORMAT).append(" = ").append(sourceFormat).append("\n");
813 if (sourceWidth != null)
814 sb.append(KEY_SOURCE_WIDTH).append(" = ").append(sourceWidth).append("\n");
815 if (sourceHeight != null)
816 sb.append(KEY_SOURCE_HEIGHT).append(" = ").append(sourceHeight).append("\n");
817
818 if (aoiWidth != null)
819 sb.append(KEY_AOI_WIDTH).append(" = ").append(aoiWidth).append("\n");
820 if (aoiHeight != null)
821 sb.append(KEY_AOI_HEIGHT).append(" = ").append(aoiHeight).append("\n");
822
823 if (xStart != null)
824 sb.append(KEY_XSTART).append(" = ").append(xStart).append("\n");
825 if (yStart != null)
826 sb.append(KEY_YSTART).append(" = ").append(yStart).append("\n");
827
828 if (bytesPerPixel != null)
829 sb.append(KEY_BYTES_PER_PIXEL).append(" = ").append(bytesPerPixel).append("\n");
830 if (effectiveBitsPerPixel != null)
831 sb.append(KEY_EFFECTIVE_BITS_PER_PIXEL).append(" = ").append(effectiveBitsPerPixel).append("\n");
832
833 if (horizontalBinning != null)
834 sb.append(KEY_HORIZONTAL_BINNING).append(" = ").append(horizontalBinning).append("\n");
835 if (verticalBinning != null)
836 sb.append(KEY_VERTICAL_BINNING).append(" = ").append(verticalBinning).append("\n");
837
838 if (imageFormat != null)
839 sb.append(KEY_IMAGE_FORMAT).append(" = ").append(imageFormat).append("\n");
840
841 if (frameNumber != null)
842 sb.append(KEY_FRAME_NUMBER).append(" = ").append(frameNumber).append("\n");
843 if (eventNumber != null)
844 sb.append(KEY_EVENT_NUMBER).append(" = ").append(eventNumber).append("\n");
845
846 if (xScale != null)
847 sb.append(KEY_XSCALE).append(" = ").append(xScale).append("\n");
848 if (yScale != null)
849 sb.append(KEY_YSCALE).append(" = ").append(yScale).append("\n");
850
851 if (imageRotation != null)
852 sb.append(KEY_IMAGE_ROTATION).append(" = ").append(imageRotation).append("\n");
853 if (imageFlags != null)
854 sb.append(KEY_IMAGE_FLAGS).append(" = ").append(String.format("0x%01x", imageFlags)).append("\n");
855
856 if (appendedFrameSize != null)
857 sb.append(KEY_APPENDED_FRAME_SIZE).append(" = ").append(appendedFrameSize).append("\n");
858
859 sb.append(KEY_ISPARE1).append(" = ").append(ispare1).append("\n");
860 sb.append(KEY_ISPARE2).append(" = ").append(ispare2).append("\n");
861 sb.append(KEY_ISPARE3).append(" = ").append(ispare3).append("\n");
862
863 sb.append(KEY_FSPARE1).append(" = ").append(ispare1).append("\n");
864 sb.append(KEY_FSPARE2).append(" = ").append(ispare2).append("\n");
865 sb.append(KEY_FSPARE3).append(" = ").append(ispare3).append("\n");
866
867 if (md5hash != null)
868 sb.append(KEY_MD5HASH).append(" = ").append(TImageUtils.padString(md5hash, 32, '0', true)).append("\n");
869
870 if (vtOb != null)
871 sb.append(KEY_VTOB).append(" = ").append(new String(vtOb)).append("\n");
872
873 for (String key : optVars.keySet())
874 sb.append(key).append(" = ").append(optVars.get(key)).append("\n");
875
876 return sb.toString();
877 }
878
879
880
881
882
883
884
885 private boolean validateArchivalMetadata(PNGMetadata pngMetadata) throws IllegalArgumentException {
886
887 int bits = bytesPerPixel * 8;
888 int effbits = effectiveBitsPerPixel;
889 if (effbits > bits)
890 throw new IllegalArgumentException("effectiveBitsPerPixel == " + effbits + ", max: " + bits);
891
892 int pngColorType = pngMetadata.IHDR_colorType;
893 switch (pngColorType) {
894 case 0:
895 if (imageFormat != ImageFormat.IMAGE_FORMAT_GRAY)
896 throw new IllegalArgumentException("imageFormat == " + imageFormat + ", expected: "
897 + ImageFormat.IMAGE_FORMAT_GRAY);
898
899 if (effbits != pngMetadata.IHDR_bitDepth)
900 throw new IllegalArgumentException("bitDepth == " + effbits + ", expected: "
901 + pngMetadata.IHDR_bitDepth);
902
903 if (pngMetadata.sBIT_present && effbits != pngMetadata.sBIT_grayBits)
904 throw new IllegalArgumentException("effectiveBitsPerPixel == " + effbits + ", expected: "
905 + pngMetadata.sBIT_grayBits);
906
907 break;
908
909 case 2:
910 case 6:
911 case 3:
912 if (imageFormat != ImageFormat.IMAGE_FORMAT_RGB
913 && (imageFormat != ImageFormat.IMAGE_FORMAT_GRAY && bytesPerPixel == 3))
914 throw new IllegalArgumentException("imageFormat == " + imageFormat + ", expected: "
915 + (bytesPerPixel == 3 ? ImageFormat.IMAGE_FORMAT_GRAY : ImageFormat.IMAGE_FORMAT_RGB));
916
917 break;
918
919 default:
920 throw new IllegalArgumentException("pngColorType == " + pngColorType);
921 }
922
923 if (aoiWidth <= 0) {
924 if (aoiHeight > 0)
925 throw new IllegalArgumentException("aoiWidth <= 0, but aoiHeight = " + aoiHeight);
926 if (sourceWidth != pngMetadata.IHDR_width)
927 throw new IllegalArgumentException("sourceWidth == " + sourceWidth + ", excepted value: "
928 + pngMetadata.IHDR_width);
929 if (xStart > 0)
930 throw new IllegalArgumentException("aoiWidth <= 0, but xStart = " + xStart);
931 }
932
933 if (aoiHeight <= 0) {
934 if (aoiWidth > 0)
935 throw new IllegalArgumentException("aoiHeight <= 0, but aoiWidth = " + aoiWidth);
936 if (sourceHeight != pngMetadata.IHDR_height)
937 throw new IllegalArgumentException("sourceHeight == " + sourceHeight + ", excepted value: "
938 + pngMetadata.IHDR_height);
939 if (yStart > 0)
940 throw new IllegalArgumentException("aoiHeight <= 0, but yStart = " + yStart);
941 }
942
943 if (pngMetadata.pHYs_present && pngMetadata.pHYs_unitSpecifier == 1) {
944 if (xScale != 1000.0F / pngMetadata.pHYs_pixelsPerUnitXAxis)
945 throw new IllegalArgumentException("xScale == " + xScale + ", expected value: " + 1000.0F
946 / pngMetadata.pHYs_pixelsPerUnitXAxis);
947
948 if (yScale != 1000.0F / pngMetadata.pHYs_pixelsPerUnitYAxis)
949 throw new IllegalArgumentException("yScale == " + yScale + ", expected value: " + 1000.0F
950 / pngMetadata.pHYs_pixelsPerUnitYAxis);
951
952 } else if (xScale != -1F)
953 throw new IllegalArgumentException("yScale == " + yScale + ", expected value: " + (-1F));
954
955 else if (yScale != -1F)
956 throw new IllegalArgumentException("yScale == " + yScale + ", expected value: " + (-1F));
957
958
959
960 return true;
961 }
962
963
964
965
966 private void setDefault(PNGMetadata pngMetadata) {
967 versionTag = IMAGE.IMAGE_VERSION;
968 cameraPortId = IMAGE.DEFAULT_CAMERA_PORT_ID;
969 cameraPortName = IMAGE.DEFAULT_CAMERA_PORT_NAME;
970 aoiWidth = aoiHeight = -1;
971 xStart = yStart = 0;
972 horizontalBinning = verticalBinning = 0;
973 frameNumber = eventNumber = 0L;
974 imageRotation = 0F;
975 imageFlags = ImageFlag.LITTLE_ENDIAN_BYTE_ORDER.getId();
976 ispare1 = ispare2 = ispare3 = -1;
977 fspare1 = fspare2 = fspare3 = -1f;
978
979 sourceWidth = pngMetadata.IHDR_width;
980 sourceHeight = pngMetadata.IHDR_height;
981
982 Calendar cal = pngMetadata.tIME_present ? new GregorianCalendar(pngMetadata.tIME_year,
983 pngMetadata.tIME_month - 1, pngMetadata.tIME_day,
984 pngMetadata.tIME_hour, pngMetadata.tIME_minute, pngMetadata.tIME_second) : new GregorianCalendar();
985
986 timestampSeconds = (int) (cal.getTimeInMillis() / 1000);
987 timestampMicroseconds = 0;
988
989 if (pngMetadata.pHYs_present && pngMetadata.pHYs_unitSpecifier == 1) {
990 xScale = pngMetadata.pHYs_pixelsPerUnitXAxis / 1000.0F;
991 yScale = pngMetadata.pHYs_pixelsPerUnitYAxis / 1000.0F;
992 } else
993 xScale = yScale = -1F;
994
995 int pngColorType = pngMetadata.IHDR_colorType;
996 switch (pngColorType) {
997 case 0:
998 case 4:
999 sourceFormat = imageFormat = ImageFormat.IMAGE_FORMAT_GRAY;
1000 bytesPerPixel = pngMetadata.IHDR_bitDepth <= 8 ? 1 : 2;
1001 effectiveBitsPerPixel = pngMetadata.IHDR_bitDepth;
1002 break;
1003
1004 case 2:
1005 case 6:
1006 case 3:
1007 sourceFormat = imageFormat = ImageFormat.IMAGE_FORMAT_RGB;
1008 bytesPerPixel = 3;
1009 effectiveBitsPerPixel = 24;
1010 break;
1011
1012 default:
1013 throw new IllegalArgumentException("pngColorType == " + pngColorType);
1014 }
1015 appendedFrameSize = sourceWidth * sourceHeight * bytesPerPixel;
1016 }
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054 public static String md5hash(byte[] data) {
1055 MessageDigest m;
1056 try {
1057 m = MessageDigest.getInstance("MD5");
1058
1059 } catch (NoSuchAlgorithmException e) {
1060 throw new RuntimeException("Invalid cryptographic algorithm: " + e.getMessage());
1061 }
1062
1063 m.update(data);
1064 byte[] digest = m.digest();
1065 BigInteger bigInt = new BigInteger(1, digest);
1066 return bigInt.toString(16);
1067 }
1068
1069
1070
1071
1072
1073
1074
1075
1076 public boolean isArchival() {
1077 return versionTag != null
1078 && cameraPortId != null
1079 && cameraPortName != null
1080 && timestampSeconds != null
1081 && timestampMicroseconds != null
1082 && sourceWidth != null
1083 && sourceHeight != null
1084 && aoiWidth != null
1085 && aoiHeight != null
1086 && xStart != null
1087 && yStart != null
1088 && bytesPerPixel != null
1089 && effectiveBitsPerPixel != null
1090 && horizontalBinning != null
1091 && verticalBinning != null
1092 && sourceFormat != null
1093 && imageFormat != null
1094 && frameNumber != null
1095 && eventNumber != null
1096 && xScale != null
1097 && yScale != null
1098 && imageRotation != null
1099 && imageFlags != null
1100 && appendedFrameSize != null;
1101 }
1102
1103 }