1 package org
.argeo
.app
.image
;
4 import java
.awt
.Graphics2D
;
5 import java
.awt
.geom
.AffineTransform
;
6 import java
.awt
.image
.AffineTransformOp
;
7 import java
.awt
.image
.BufferedImage
;
8 import java
.io
.IOException
;
9 import java
.io
.InputStream
;
10 import java
.io
.OutputStream
;
11 import java
.nio
.file
.Files
;
12 import java
.nio
.file
.Path
;
13 import java
.nio
.file
.Paths
;
14 import java
.util
.List
;
15 import java
.util
.concurrent
.Callable
;
17 import javax
.imageio
.ImageIO
;
19 import org
.apache
.commons
.imaging
.ImageReadException
;
20 import org
.apache
.commons
.imaging
.Imaging
;
21 import org
.apache
.commons
.imaging
.common
.ImageMetadata
;
22 import org
.apache
.commons
.imaging
.common
.ImageMetadata
.ImageMetadataItem
;
23 import org
.apache
.commons
.imaging
.common
.RationalNumber
;
24 import org
.apache
.commons
.imaging
.formats
.jpeg
.JpegImageMetadata
;
25 import org
.apache
.commons
.imaging
.formats
.jpeg
.exif
.ExifRewriter
;
26 import org
.apache
.commons
.imaging
.formats
.tiff
.TiffField
;
27 import org
.apache
.commons
.imaging
.formats
.tiff
.TiffImageMetadata
;
28 import org
.apache
.commons
.imaging
.formats
.tiff
.constants
.ExifTagConstants
;
29 import org
.apache
.commons
.imaging
.formats
.tiff
.constants
.GpsTagConstants
;
30 import org
.apache
.commons
.imaging
.formats
.tiff
.constants
.TiffTagConstants
;
31 import org
.apache
.commons
.imaging
.formats
.tiff
.taginfos
.TagInfo
;
32 import org
.apache
.commons
.imaging
.formats
.tiff
.write
.TiffOutputDirectory
;
33 import org
.apache
.commons
.imaging
.formats
.tiff
.write
.TiffOutputSet
;
35 public class ImageProcessor
{
36 private Callable
<InputStream
> inSupplier
;
37 private Callable
<OutputStream
> outSupplier
;
39 public ImageProcessor(Callable
<InputStream
> inSupplier
, Callable
<OutputStream
> outSupplier
) {
41 this.inSupplier
= inSupplier
;
42 this.outSupplier
= outSupplier
;
45 public void process() {
47 ImageMetadata metadata
= null;
48 Integer orientation
= null;
49 try (InputStream in
= inSupplier
.call()) {
50 metadata
= Imaging
.getMetadata(in
, null);
51 orientation
= getOrientation(metadata
);
53 try (InputStream in
= inSupplier
.call()) {
54 if (orientation
!= null && orientation
!= TiffTagConstants
.ORIENTATION_VALUE_HORIZONTAL_NORMAL
) {
55 BufferedImage sourceImage
= ImageIO
.read(in
);
56 AffineTransform transform
= getExifTransformation(orientation
, sourceImage
.getWidth(),
57 sourceImage
.getHeight());
58 BufferedImage targetImage
= transformImage(sourceImage
, orientation
, transform
);
59 Path temp
= Files
.createTempFile("image", ".jpg");
61 try (OutputStream out
= Files
.newOutputStream(temp
)) {
62 ImageIO
.write(targetImage
, "jpeg", out
);
64 copyWithMetadata(() -> Files
.newInputStream(temp
), metadata
);
66 Files
.deleteIfExists(temp
);
69 try (OutputStream out
= outSupplier
.call()) {
70 copyWithMetadata(() -> in
, metadata
);
74 } catch (Exception e
) {
75 throw new RuntimeException("Cannot process image", e
);
79 protected void copyWithMetadata(Callable
<InputStream
> inSupplier
, ImageMetadata metadata
) {
80 try (InputStream in
= inSupplier
.call(); OutputStream out
= outSupplier
.call();) {
81 TiffOutputSet outputSet
= null;
82 if (metadata
!= null && metadata
instanceof JpegImageMetadata
) {
83 final TiffImageMetadata exif
= ((JpegImageMetadata
) metadata
).getExif();
86 outputSet
= exif
.getOutputSet();
87 // outputSet.getInteroperabilityDirectory().removeField(TiffTagConstants.TIFF_TAG_ORIENTATION);
89 for (TiffOutputDirectory dir
: outputSet
.getDirectories()) {
90 // TiffOutputField field = dir.findField(TiffTagConstants.TIFF_TAG_ORIENTATION);
91 // if (field != null) {
92 dir
.removeField(TiffTagConstants
.TIFF_TAG_ORIENTATION
);
93 dir
.removeField(TiffTagConstants
.TIFF_TAG_IMAGE_WIDTH
);
94 dir
.removeField(TiffTagConstants
.TIFF_TAG_IMAGE_LENGTH
);
95 dir
.removeField(ExifTagConstants
.EXIF_TAG_EXIF_IMAGE_WIDTH
);
96 dir
.removeField(ExifTagConstants
.EXIF_TAG_EXIF_IMAGE_LENGTH
);
97 // System.out.println("Removed orientation from " + dir.description());
103 if (null == outputSet
) {
104 outputSet
= new TiffOutputSet();
106 new ExifRewriter().updateExifMetadataLossless(in
, out
, outputSet
);
107 } catch (Exception e
) {
108 throw new RuntimeException("Could not update EXIF metadata", e
);
113 public static BufferedImage
transformImage(BufferedImage image
, Integer orientation
, AffineTransform transform
)
116 AffineTransformOp op
= new AffineTransformOp(transform
, AffineTransformOp
.TYPE_BICUBIC
);
118 int width
= image
.getWidth();
119 int height
= image
.getHeight();
120 switch (orientation
) {
121 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_180
:
122 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW
:
123 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_90_CW
:
124 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW
:
125 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_270_CW
:
126 width
= image
.getHeight();
127 height
= image
.getWidth();
131 BufferedImage destinationImage
= new BufferedImage(width
, height
, image
.getType());
133 Graphics2D g
= destinationImage
.createGraphics();
134 g
.setBackground(Color
.WHITE
);
135 g
.clearRect(0, 0, destinationImage
.getWidth(), destinationImage
.getHeight());
136 destinationImage
= op
.filter(image
, destinationImage
);
137 return destinationImage
;
140 public static int getOrientation(ImageMetadata metadata
) {
141 if (metadata
instanceof JpegImageMetadata
) {
142 JpegImageMetadata jpegMetadata
= (JpegImageMetadata
) metadata
;
143 TiffField field
= jpegMetadata
.findEXIFValue(TiffTagConstants
.TIFF_TAG_ORIENTATION
);
145 return TiffTagConstants
.ORIENTATION_VALUE_HORIZONTAL_NORMAL
;
147 return field
.getIntValue();
148 } catch (ImageReadException e
) {
149 throw new IllegalStateException(e
);
152 throw new IllegalArgumentException("Unsupported metadata format " + metadata
.getClass());
156 public static AffineTransform
getExifTransformation(Integer orientation
, int width
, int height
) {
158 AffineTransform t
= new AffineTransform();
160 switch (orientation
) {
161 case TiffTagConstants
.ORIENTATION_VALUE_HORIZONTAL_NORMAL
:
163 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_HORIZONTAL
: // Flip X
165 t
.translate(-width
, 0);
167 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_180
: // PI rotation
168 t
.translate(width
, height
);
171 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_VERTICAL
: // Flip Y
173 t
.translate(0, -height
);
175 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW
: // - PI/2 and Flip X
176 t
.rotate(-Math
.PI
/ 2);
179 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_90_CW
: // -PI/2 and -width
180 t
.translate(height
, 0);
181 t
.rotate(Math
.PI
/ 2);
183 case TiffTagConstants
.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW
: // PI/2 and Flip
185 t
.translate(-height
, 0);
186 t
.translate(0, width
);
187 t
.rotate(3 * Math
.PI
/ 2);
189 case TiffTagConstants
.ORIENTATION_VALUE_ROTATE_270_CW
: // PI / 2
190 t
.translate(0, width
);
191 t
.rotate(3 * Math
.PI
/ 2);
198 public static void main(String
[] args
) throws Exception
{
199 Path imagePath
= Paths
.get(args
[0]);
200 Path targetPath
= Paths
.get(args
[1]);
202 ImageProcessor imageProcessor
= new ImageProcessor(() -> Files
.newInputStream(imagePath
),
203 () -> Files
.newOutputStream(targetPath
));
204 imageProcessor
.process();
206 try (InputStream in
= Files
.newInputStream(targetPath
)) {
207 metadataExample(in
, null);
212 public static void metadataExample(InputStream in
, String fileName
) throws ImageReadException
, IOException
{
213 // get all metadata stored in EXIF format (ie. from JPEG or TIFF).
214 final ImageMetadata metadata
= Imaging
.getMetadata(in
, fileName
);
216 // System.out.println(metadata);
218 if (metadata
instanceof JpegImageMetadata
) {
219 final JpegImageMetadata jpegMetadata
= (JpegImageMetadata
) metadata
;
221 // Jpeg EXIF metadata is stored in a TIFF-based directory structure
222 // and is identified with TIFF tags.
223 // Here we look for the "x resolution" tag, but
224 // we could just as easily search for any other tag.
226 // see the TiffConstants file for a list of TIFF tags.
228 // System.out.println("file: " + file.getPath());
230 // print out various interesting EXIF tags.
231 printTagValue(jpegMetadata
, TiffTagConstants
.TIFF_TAG_XRESOLUTION
);
232 printTagValue(jpegMetadata
, TiffTagConstants
.TIFF_TAG_DATE_TIME
);
233 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_DATE_TIME_ORIGINAL
);
234 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_DATE_TIME_DIGITIZED
);
235 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_ISO
);
236 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_SHUTTER_SPEED_VALUE
);
237 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_APERTURE_VALUE
);
238 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_BRIGHTNESS_VALUE
);
239 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LATITUDE_REF
);
240 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LATITUDE
);
241 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LONGITUDE_REF
);
242 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LONGITUDE
);
244 System
.out
.println();
246 // simple interface to GPS data
247 final TiffImageMetadata exifMetadata
= jpegMetadata
.getExif();
248 if (null != exifMetadata
) {
249 final TiffImageMetadata
.GPSInfo gpsInfo
= exifMetadata
.getGPS();
250 if (null != gpsInfo
) {
251 final String gpsDescription
= gpsInfo
.toString();
252 final double longitude
= gpsInfo
.getLongitudeAsDegreesEast();
253 final double latitude
= gpsInfo
.getLatitudeAsDegreesNorth();
255 System
.out
.println(" " + "GPS Description: " + gpsDescription
);
256 System
.out
.println(" " + "GPS Longitude (Degrees East): " + longitude
);
257 System
.out
.println(" " + "GPS Latitude (Degrees North): " + latitude
);
261 // more specific example of how to manually access GPS values
262 final TiffField gpsLatitudeRefField
= jpegMetadata
263 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LATITUDE_REF
);
264 final TiffField gpsLatitudeField
= jpegMetadata
265 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LATITUDE
);
266 final TiffField gpsLongitudeRefField
= jpegMetadata
267 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LONGITUDE_REF
);
268 final TiffField gpsLongitudeField
= jpegMetadata
269 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LONGITUDE
);
270 if (gpsLatitudeRefField
!= null && gpsLatitudeField
!= null && gpsLongitudeRefField
!= null
271 && gpsLongitudeField
!= null) {
272 // all of these values are strings.
273 final String gpsLatitudeRef
= (String
) gpsLatitudeRefField
.getValue();
274 final RationalNumber
[] gpsLatitude
= (RationalNumber
[]) (gpsLatitudeField
.getValue());
275 final String gpsLongitudeRef
= (String
) gpsLongitudeRefField
.getValue();
276 final RationalNumber
[] gpsLongitude
= (RationalNumber
[]) gpsLongitudeField
.getValue();
278 final RationalNumber gpsLatitudeDegrees
= gpsLatitude
[0];
279 final RationalNumber gpsLatitudeMinutes
= gpsLatitude
[1];
280 final RationalNumber gpsLatitudeSeconds
= gpsLatitude
[2];
282 final RationalNumber gpsLongitudeDegrees
= gpsLongitude
[0];
283 final RationalNumber gpsLongitudeMinutes
= gpsLongitude
[1];
284 final RationalNumber gpsLongitudeSeconds
= gpsLongitude
[2];
286 // This will format the gps info like so:
288 // gpsLatitude: 8 degrees, 40 minutes, 42.2 seconds S
289 // gpsLongitude: 115 degrees, 26 minutes, 21.8 seconds E
291 System
.out
.println(" " + "GPS Latitude: " + gpsLatitudeDegrees
.toDisplayString() + " degrees, "
292 + gpsLatitudeMinutes
.toDisplayString() + " minutes, " + gpsLatitudeSeconds
.toDisplayString()
293 + " seconds " + gpsLatitudeRef
);
294 System
.out
.println(" " + "GPS Longitude: " + gpsLongitudeDegrees
.toDisplayString() + " degrees, "
295 + gpsLongitudeMinutes
.toDisplayString() + " minutes, " + gpsLongitudeSeconds
.toDisplayString()
296 + " seconds " + gpsLongitudeRef
);
300 System
.out
.println();
302 final List
<ImageMetadataItem
> items
= jpegMetadata
.getItems();
303 for (final ImageMetadataItem item
: items
) {
304 System
.out
.println(" " + "item: " + item
);
308 System
.out
.println();
312 private static void printTagValue(final JpegImageMetadata jpegMetadata
, final TagInfo tagInfo
) {
313 final TiffField field
= jpegMetadata
.findEXIFValueWithExactMatch(tagInfo
);
315 System
.out
.println(tagInfo
.name
+ ": " + "Not Found.");
317 System
.out
.println(tagInfo
.name
+ ": " + field
.getValueDescription());