Skip to content

Commit ef70247

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

File tree

9 files changed

+303
-0
lines changed

9 files changed

+303
-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: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
4+
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
5+
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.fugerit.java.doc.val.core.DocTypeValidationResult;
8+
import org.w3c.dom.NamedNodeMap;
9+
import org.w3c.dom.Node;
10+
11+
import javax.imageio.ImageIO;
12+
import javax.imageio.ImageReader;
13+
import javax.imageio.metadata.IIOMetadata;
14+
import javax.imageio.stream.ImageInputStream;
15+
import java.io.InputStream;
16+
import java.util.ArrayList;
17+
import java.util.Iterator;
18+
import java.util.List;
19+
20+
@Slf4j
21+
public class ImageIOTiffUtils {
22+
23+
private ImageIOTiffUtils() {}
24+
25+
public static DocTypeValidationResult validateTiffAndMetadata(InputStream is, int... idTags) {
26+
ImageReader reader = null;
27+
try (ImageInputStream iis = ImageIO.createImageInputStream(is)) {
28+
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
29+
if (!readers.hasNext()) {
30+
log.warn("No ImageReader available for input stream");
31+
return DocTypeValidationResult.newFail().withValidationMessage( "No ImageReader available" );
32+
}
33+
34+
reader = readers.next();
35+
reader.setInput(iis, true);
36+
37+
IIOMetadata meta = reader.getImageMetadata(0);
38+
39+
// TIFF plugin metadata format name
40+
String[] formatNames = meta.getMetadataFormatNames();
41+
for (String format : formatNames) {
42+
log.info("Metadata format: {}", format);
43+
Node root = meta.getAsTree(format);
44+
dumpNode(root, 0);
45+
}
46+
47+
List<String> missingTags = new ArrayList<>();
48+
49+
if (meta instanceof TIFFImageMetadata) {
50+
for ( int idTag : idTags ) {
51+
TIFFImageMetadata tiffMeta = (TIFFImageMetadata) meta;
52+
TIFFEntry entry = (TIFFEntry) tiffMeta.getTIFFField(idTag);
53+
if (entry == null) {
54+
missingTags.add( ImageIOTiffTags.tagDescription(idTag) );
55+
}
56+
}
57+
} else {
58+
return DocTypeValidationResult.newFail().withValidationMessage( "No TIFF metadata available" );
59+
}
60+
61+
if (missingTags.isEmpty()) {
62+
return DocTypeValidationResult.newOk();
63+
} else {
64+
return DocTypeValidationResult.newFail().withValidationMessage( String.format( "Missing tags : %s", missingTags ) );
65+
}
66+
67+
} catch (Exception e) {
68+
String message = String.format( "Error reading TIFF metadata from stream %s", e );
69+
log.error( message, e );
70+
return DocTypeValidationResult.newFail().withMainException( e );
71+
} finally {
72+
if (reader != null) {
73+
reader.dispose();
74+
}
75+
}
76+
}
77+
78+
private static void dumpNode(Node node, int level) {
79+
StringBuilder sb = new StringBuilder();
80+
for (int i = 0; i < level; i++) {
81+
sb.append(" ");
82+
}
83+
String indent = sb.toString();
84+
log.info("{}Node: {}", indent, node.getNodeName());
85+
86+
NamedNodeMap attributes = node.getAttributes();
87+
if (attributes != null) {
88+
for (int i = 0; i < attributes.getLength(); i++) {
89+
Node attr = attributes.item(i);
90+
log.info("{} @{} = {}", indent, attr.getNodeName(), attr.getNodeValue());
91+
}
92+
}
93+
94+
Node child = node.getFirstChild();
95+
while (child != null) {
96+
dumpNode(child, level + 1);
97+
child = child.getNextSibling();
98+
}
99+
}
100+
101+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.fugerit.java.doc.val.imageio.tiff;
2+
3+
import com.twelvemonkeys.imageio.metadata.exif.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.IOException;
9+
import java.io.InputStream;
10+
import java.util.Set;
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+
public static final int[] 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+
private int[] idTags;
22+
23+
public ImageIOTiffValidator( int[] idTags ) {
24+
super( MIME_TYPE, EXTENSION );
25+
this.idTags = idTags;
26+
}
27+
28+
public ImageIOTiffValidator() {
29+
this( DEFAULT_ID_TAGS );
30+
}
31+
32+
@Override
33+
public DocTypeValidationResult validate(InputStream is) {
34+
return ImageIOTiffUtils.validateTiffAndMetadata( is, this.idTags );
35+
}
36+
37+
}
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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.fugerit.java.doc.val.core.DocTypeValidationResult;
6+
import org.junit.jupiter.api.Assertions;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.io.InputStream;
10+
11+
class TestImageIOTiffValidator {
12+
13+
private ImageIOTiffValidator validator = new ImageIOTiffValidator();
14+
15+
@Test
16+
void testValidTiff() throws Exception {
17+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/file_example_TIFF_1MB.tiff" )) {
18+
Assertions.assertEquals(Result.RESULT_CODE_OK, validator.validate( is ).getResultCode() );
19+
}
20+
}
21+
22+
@Test
23+
void testInvalidMetadataTiff() throws Exception {
24+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/image-ko.tiff" )) {
25+
Assertions.assertEquals("Missing tags : [282 (XResolution), 283 (YResolution), 34675 (ICCProfile)]", validator.validate( is ).getValidationMessage() );
26+
}
27+
}
28+
29+
@Test
30+
void testPngAsTiff() throws Exception {
31+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/png_as_tiff.tiff" )) {
32+
Assertions.assertEquals("No TIFF metadata available", validator.validate( is ).getValidationMessage() );
33+
}
34+
}
35+
36+
@Test
37+
void testPdfAsTiff() throws Exception {
38+
try (InputStream is = ClassHelper.loadFromDefaultClassLoader( "sample/pdf_as_tiff.tiff" )) {
39+
Assertions.assertEquals("No ImageReader available", validator.validate( is ).getValidationMessage() );
40+
}
41+
}
42+
43+
@Test
44+
void testNullStream() throws Exception {
45+
Assertions.assertEquals("input == null!", validator.validate( null ).getValidationMessage() );
46+
}
47+
48+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)