Skip to content

Commit 11581a8

Browse files
committed
feat: add fj-doc-val-imageio-tiff validator
1 parent dad43c8 commit 11581a8

File tree

9 files changed

+307
-0
lines changed

9 files changed

+307
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
public class ImageIOTiffTags {
9+
10+
private ImageIOTiffTags() {}
11+
12+
public static String tagLabel( int idTag ) {
13+
return TIFF_TAG_NAMES.get( idTag );
14+
}
15+
16+
public static String tagDescription( int idTag ) {
17+
String label = tagLabel( idTag );
18+
if ( label == null ) {
19+
return String.valueOf( idTag );
20+
} else {
21+
return String.format( "%s (%s)", idTag, label );
22+
}
23+
}
24+
25+
private static final Map<Integer, String> TIFF_TAG_NAMES = new HashMap<>();
26+
static {
27+
TIFF_TAG_NAMES.put(TIFF.TAG_EXIF_IFD, "ExifIFD");
28+
TIFF_TAG_NAMES.put(TIFF.TAG_GPS_IFD, "GpsIFD");
29+
TIFF_TAG_NAMES.put(TIFF.TAG_INTEROP_IFD, "InteropIFD");
30+
TIFF_TAG_NAMES.put(TIFF.TAG_IMAGE_WIDTH, "ImageWidth");
31+
TIFF_TAG_NAMES.put(TIFF.TAG_IMAGE_HEIGHT, "ImageHeight");
32+
TIFF_TAG_NAMES.put(TIFF.TAG_BITS_PER_SAMPLE, "BitsPerSample");
33+
TIFF_TAG_NAMES.put(TIFF.TAG_COMPRESSION, "Compression");
34+
TIFF_TAG_NAMES.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
35+
TIFF_TAG_NAMES.put(TIFF.TAG_FILL_ORDER, "FillOrder");
36+
TIFF_TAG_NAMES.put(TIFF.TAG_ORIENTATION, "Orientation");
37+
TIFF_TAG_NAMES.put(TIFF.TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel");
38+
TIFF_TAG_NAMES.put(TIFF.TAG_PLANAR_CONFIGURATION, "PlanarConfiguration");
39+
TIFF_TAG_NAMES.put(TIFF.TAG_SAMPLE_FORMAT, "SampleFormat");
40+
TIFF_TAG_NAMES.put(TIFF.TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling");
41+
TIFF_TAG_NAMES.put(TIFF.TAG_YCBCR_POSITIONING, "YCbCrPositioning");
42+
TIFF_TAG_NAMES.put(TIFF.TAG_X_RESOLUTION, "XResolution");
43+
TIFF_TAG_NAMES.put(TIFF.TAG_Y_RESOLUTION, "YResolution");
44+
TIFF_TAG_NAMES.put(TIFF.TAG_X_POSITION, "XPosition");
45+
TIFF_TAG_NAMES.put(TIFF.TAG_Y_POSITION, "YPosition");
46+
TIFF_TAG_NAMES.put(TIFF.TAG_RESOLUTION_UNIT, "ResolutionUnit");
47+
TIFF_TAG_NAMES.put(TIFF.TAG_STRIP_OFFSETS, "StripOffsets");
48+
TIFF_TAG_NAMES.put(TIFF.TAG_ROWS_PER_STRIP, "RowsPerStrip");
49+
TIFF_TAG_NAMES.put(TIFF.TAG_STRIP_BYTE_COUNTS, "StripByteCounts");
50+
TIFF_TAG_NAMES.put(TIFF.TAG_FREE_OFFSETS, "FreeOffsets");
51+
TIFF_TAG_NAMES.put(TIFF.TAG_FREE_BYTE_COUNTS, "FreeByteCounts");
52+
TIFF_TAG_NAMES.put(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, "JPEGInterchangeFormat");
53+
TIFF_TAG_NAMES.put(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, "JPEGInterchangeFormatLength");
54+
TIFF_TAG_NAMES.put(TIFF.TAG_GROUP3OPTIONS, "Group3Options");
55+
TIFF_TAG_NAMES.put(TIFF.TAG_GROUP4OPTIONS, "Group4Options");
56+
TIFF_TAG_NAMES.put(TIFF.TAG_TRANSFER_FUNCTION, "TransferFunction");
57+
TIFF_TAG_NAMES.put(TIFF.TAG_PREDICTOR, "Predictor");
58+
TIFF_TAG_NAMES.put(TIFF.TAG_WHITE_POINT, "WhitePoint");
59+
TIFF_TAG_NAMES.put(TIFF.TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities");
60+
TIFF_TAG_NAMES.put(TIFF.TAG_COLOR_MAP, "ColorMap");
61+
TIFF_TAG_NAMES.put(TIFF.TAG_INK_SET, "InkSet");
62+
TIFF_TAG_NAMES.put(TIFF.TAG_INK_NAMES, "InkNames");
63+
TIFF_TAG_NAMES.put(TIFF.TAG_NUMBER_OF_INKS, "NumberOfInks");
64+
TIFF_TAG_NAMES.put(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples");
65+
TIFF_TAG_NAMES.put(TIFF.TAG_TRANSFER_RANGE, "TransferRange");
66+
TIFF_TAG_NAMES.put(TIFF.TAG_YCBCR_COEFFICIENTS, "YCbCrCoefficients");
67+
TIFF_TAG_NAMES.put(TIFF.TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite");
68+
TIFF_TAG_NAMES.put(TIFF.TAG_DATE_TIME, "DateTime");
69+
TIFF_TAG_NAMES.put(TIFF.TAG_DOCUMENT_NAME, "DocumentName");
70+
TIFF_TAG_NAMES.put(TIFF.TAG_IMAGE_DESCRIPTION, "ImageDescription");
71+
TIFF_TAG_NAMES.put(TIFF.TAG_MAKE, "Make");
72+
TIFF_TAG_NAMES.put(TIFF.TAG_MODEL, "Model");
73+
TIFF_TAG_NAMES.put(TIFF.TAG_PAGE_NAME, "PageName");
74+
TIFF_TAG_NAMES.put(TIFF.TAG_PAGE_NUMBER, "PageNumber");
75+
TIFF_TAG_NAMES.put(TIFF.TAG_SOFTWARE, "Software");
76+
TIFF_TAG_NAMES.put(TIFF.TAG_ARTIST, "Artist");
77+
TIFF_TAG_NAMES.put(TIFF.TAG_HOST_COMPUTER, "HostComputer");
78+
TIFF_TAG_NAMES.put(TIFF.TAG_COPYRIGHT, "Copyright");
79+
TIFF_TAG_NAMES.put(TIFF.TAG_SUBFILE_TYPE, "SubfileType");
80+
TIFF_TAG_NAMES.put(TIFF.TAG_OLD_SUBFILE_TYPE, "OldSubfileType");
81+
TIFF_TAG_NAMES.put(TIFF.TAG_SUB_IFD, "SubIFD");
82+
TIFF_TAG_NAMES.put(TIFF.TAG_XMP, "XMP");
83+
TIFF_TAG_NAMES.put(TIFF.TAG_IPTC, "IPTC");
84+
TIFF_TAG_NAMES.put(TIFF.TAG_PHOTOSHOP, "Photoshop");
85+
TIFF_TAG_NAMES.put(TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA, "PhotoshopImageSourceData");
86+
TIFF_TAG_NAMES.put(TIFF.TAG_PHOTOSHOP_ANNOTATIONS, "PhotoshopAnnotations");
87+
TIFF_TAG_NAMES.put(TIFF.TAG_ICC_PROFILE, "ICCProfile");
88+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_BLC, "ModiBLC");
89+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_VECTOR, "ModiVector");
90+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_PTC, "ModiPTC");
91+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_PLAIN_TEXT, "ModiPlainText");
92+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_OLE_PROPERTY_SET, "ModiOLEPropertySet");
93+
TIFF_TAG_NAMES.put(TIFF.TAG_MODI_TEXT_POS_INFO, "ModiTextPosInfo");
94+
TIFF_TAG_NAMES.put(TIFF.TAG_TILE_WIDTH, "TileWidth");
95+
TIFF_TAG_NAMES.put(TIFF.TAG_TILE_HEIGTH, "TileHeight");
96+
TIFF_TAG_NAMES.put(TIFF.TAG_TILE_OFFSETS, "TileOffsets");
97+
TIFF_TAG_NAMES.put(TIFF.TAG_TILE_BYTE_COUNTS, "TileByteCounts");
98+
TIFF_TAG_NAMES.put(TIFF.TAG_JPEG_TABLES, "JPEGTables");
99+
TIFF_TAG_NAMES.put(TIFF.TAG_OLD_JPEG_PROC, "OldJPEGProc");
100+
TIFF_TAG_NAMES.put(TIFF.TAG_OLD_JPEG_Q_TABLES, "OldJPEGQTables");
101+
TIFF_TAG_NAMES.put(TIFF.TAG_OLD_JPEG_DC_TABLES, "OldJPEGDCTables");
102+
}
103+
104+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
4+
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.fugerit.java.doc.val.core.DocTypeValidationResult;
7+
import org.w3c.dom.NamedNodeMap;
8+
import org.w3c.dom.Node;
9+
10+
import javax.imageio.ImageIO;
11+
import javax.imageio.ImageReader;
12+
import javax.imageio.metadata.IIOMetadata;
13+
import javax.imageio.stream.ImageInputStream;
14+
import java.io.InputStream;
15+
import java.util.ArrayList;
16+
import java.util.Iterator;
17+
import java.util.List;
18+
19+
@Slf4j
20+
public class ImageIOTiffUtils {
21+
22+
private ImageIOTiffUtils() {}
23+
24+
public static DocTypeValidationResult validateTiffAndMetadata(InputStream is, Integer[] idTags) {
25+
ImageReader reader = null;
26+
try (ImageInputStream iis = ImageIO.createImageInputStream(is)) {
27+
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
28+
if (!readers.hasNext()) {
29+
log.warn("No ImageReader available for input stream");
30+
return DocTypeValidationResult.newFail().withValidationMessage( "No ImageReader available" );
31+
}
32+
33+
reader = readers.next();
34+
reader.setInput(iis, true);
35+
36+
IIOMetadata meta = reader.getImageMetadata(0);
37+
38+
// TIFF plugin metadata format name
39+
String[] formatNames = meta.getMetadataFormatNames();
40+
for (String format : formatNames) {
41+
log.info("Metadata format: {}", format);
42+
Node root = meta.getAsTree(format);
43+
dumpNode(root, 0);
44+
}
45+
46+
List<String> missingTags = new ArrayList<>();
47+
48+
if (meta instanceof TIFFImageMetadata) {
49+
for ( int idTag : idTags ) {
50+
TIFFImageMetadata tiffMeta = (TIFFImageMetadata) meta;
51+
TIFFEntry entry = (TIFFEntry) tiffMeta.getTIFFField(idTag);
52+
if (entry == null) {
53+
missingTags.add( ImageIOTiffTags.tagDescription(idTag) );
54+
}
55+
}
56+
} else {
57+
return DocTypeValidationResult.newFail().withValidationMessage( "No TIFF metadata available" );
58+
}
59+
60+
if (missingTags.isEmpty()) {
61+
return DocTypeValidationResult.newOk();
62+
} else {
63+
return DocTypeValidationResult.newFail().withValidationMessage( String.format( "Missing tags : %s", missingTags ) );
64+
}
65+
66+
} catch (Exception e) {
67+
String message = String.format( "Error reading TIFF metadata from stream %s", e );
68+
log.error( message, e );
69+
return DocTypeValidationResult.newFail().withMainException( e );
70+
} finally {
71+
if (reader != null) {
72+
reader.dispose();
73+
}
74+
}
75+
}
76+
77+
private static void dumpNode(Node node, int level) {
78+
StringBuilder sb = new StringBuilder();
79+
for (int i = 0; i < level; i++) {
80+
sb.append(" ");
81+
}
82+
String indent = sb.toString();
83+
log.info("{}Node: {}", indent, node.getNodeName());
84+
85+
NamedNodeMap attributes = node.getAttributes();
86+
if (attributes != null) {
87+
for (int i = 0; i < attributes.getLength(); i++) {
88+
Node attr = attributes.item(i);
89+
log.info("{} @{} = {}", indent, attr.getNodeName(), attr.getNodeValue());
90+
}
91+
}
92+
93+
Node child = node.getFirstChild();
94+
while (child != null) {
95+
dumpNode(child, level + 1);
96+
child = child.getNextSibling();
97+
}
98+
}
99+
100+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
4+
import org.fugerit.java.doc.val.core.DocTypeValidationResult;
5+
import org.fugerit.java.doc.val.core.basic.AbstractDocTypeValidator;
6+
import org.fugerit.java.doc.val.core.basic.ImageValidator;
7+
8+
import java.io.InputStream;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
12+
public class ImageIOTiffValidator extends AbstractDocTypeValidator {
13+
14+
public static final String EXTENSION = ImageValidator.FORMAT_TIFF;
15+
16+
public static final String MIME_TYPE = ImageValidator.MIME_TIFF;
17+
18+
private static final Integer[] DEFAULT_ID_TAGS = { TIFF.TAG_X_RESOLUTION, TIFF.TAG_Y_RESOLUTION,
19+
TIFF.TAG_ICC_PROFILE, TIFF.TAG_IMAGE_WIDTH, TIFF.TAG_IMAGE_HEIGHT, TIFF.TAG_SAMPLES_PER_PIXEL };
20+
21+
public static final List<Integer> DEFAULT_IT_TAG_LIST = Arrays.asList( DEFAULT_ID_TAGS );
22+
23+
private Integer[] idTags;
24+
25+
public ImageIOTiffValidator( Integer[] idTags ) {
26+
super( MIME_TYPE, EXTENSION );
27+
this.idTags = idTags;
28+
}
29+
30+
public ImageIOTiffValidator() {
31+
this( DEFAULT_ID_TAGS );
32+
}
33+
34+
@Override
35+
public DocTypeValidationResult validate(InputStream is) {
36+
return ImageIOTiffUtils.validateTiffAndMetadata( is, this.idTags );
37+
}
38+
39+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Test;
5+
6+
class TestImageIOTiffTags {
7+
8+
@Test
9+
void testUnknownTag() {
10+
Assertions.assertEquals( String.valueOf( Integer.MIN_VALUE ), ImageIOTiffTags.tagDescription( Integer.MIN_VALUE ) );
11+
}
12+
13+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import org.fugerit.java.core.lang.helpers.ClassHelper;
4+
import org.fugerit.java.core.util.result.Result;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.InputStream;
9+
10+
class TestImageIOTiffValidator {
11+
12+
private ImageIOTiffValidator validator = new ImageIOTiffValidator();
13+
14+
@Test
15+
void testValidTiff() throws Exception {
16+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/file_example_TIFF_1MB.tiff" )) {
17+
Assertions.assertEquals(Result.RESULT_CODE_OK, validator.validate( is ).getResultCode() );
18+
}
19+
}
20+
21+
@Test
22+
void testInvalidMetadataTiff() throws Exception {
23+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/image-ko.tiff" )) {
24+
Assertions.assertEquals("Missing tags : [282 (XResolution), 283 (YResolution), 34675 (ICCProfile)]", validator.validate( is ).getValidationMessage() );
25+
}
26+
}
27+
28+
@Test
29+
void testPngAsTiff() throws Exception {
30+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/png_as_tiff.tiff" )) {
31+
Assertions.assertEquals("No TIFF metadata available", validator.validate( is ).getValidationMessage() );
32+
}
33+
}
34+
35+
@Test
36+
void testPdfAsTiff() throws Exception {
37+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/pdf_as_tiff.tiff" )) {
38+
Assertions.assertEquals("No ImageReader available", validator.validate( is ).getValidationMessage() );
39+
}
40+
}
41+
42+
@Test
43+
void testNullStream() {
44+
Assertions.assertEquals("input == null!", validator.validate( null ).getValidationMessage() );
45+
}
46+
47+
void testDefaultTags() {
48+
Assertions.assertNotEquals( 0, ImageIOTiffValidator.DEFAULT_IT_TAG_LIST.size() );
49+
}
50+
51+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)