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
{
200 throw new IllegalArgumentException(
201 "Usage: " + ImageProcessor
.class.getSimpleName() + " <source image> <target image>");
202 Path imagePath
= Paths
.get(args
[0]);
203 Path targetPath
= Paths
.get(args
[1]);
205 System
.out
.println("## Source metadata:");
206 try (InputStream in
= Files
.newInputStream(imagePath
)) {
207 metadataExample(in
, null);
210 ImageProcessor imageProcessor
= new ImageProcessor(() -> Files
.newInputStream(imagePath
),
211 () -> Files
.newOutputStream(targetPath
));
212 imageProcessor
.process();
214 System
.out
.println("## Target metadata:");
215 try (InputStream in
= Files
.newInputStream(targetPath
)) {
216 metadataExample(in
, null);
221 public static void metadataExample(InputStream in
, String fileName
) throws ImageReadException
, IOException
{
222 // get all metadata stored in EXIF format (ie. from JPEG or TIFF).
223 final ImageMetadata metadata
= Imaging
.getMetadata(in
, fileName
);
225 // System.out.println(metadata);
227 if (metadata
instanceof JpegImageMetadata
) {
228 final JpegImageMetadata jpegMetadata
= (JpegImageMetadata
) metadata
;
230 // Jpeg EXIF metadata is stored in a TIFF-based directory structure
231 // and is identified with TIFF tags.
232 // Here we look for the "x resolution" tag, but
233 // we could just as easily search for any other tag.
235 // see the TiffConstants file for a list of TIFF tags.
237 // System.out.println("file: " + file.getPath());
239 // print out various interesting EXIF tags.
240 printTagValue(jpegMetadata
, TiffTagConstants
.TIFF_TAG_XRESOLUTION
);
241 printTagValue(jpegMetadata
, TiffTagConstants
.TIFF_TAG_DATE_TIME
);
242 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_DATE_TIME_ORIGINAL
);
243 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_DATE_TIME_DIGITIZED
);
244 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_ISO
);
245 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_SHUTTER_SPEED_VALUE
);
246 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_APERTURE_VALUE
);
247 printTagValue(jpegMetadata
, ExifTagConstants
.EXIF_TAG_BRIGHTNESS_VALUE
);
248 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LATITUDE_REF
);
249 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LATITUDE
);
250 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LONGITUDE_REF
);
251 printTagValue(jpegMetadata
, GpsTagConstants
.GPS_TAG_GPS_LONGITUDE
);
253 System
.out
.println();
255 // simple interface to GPS data
256 final TiffImageMetadata exifMetadata
= jpegMetadata
.getExif();
257 if (null != exifMetadata
) {
258 final TiffImageMetadata
.GPSInfo gpsInfo
= exifMetadata
.getGPS();
259 if (null != gpsInfo
) {
260 final String gpsDescription
= gpsInfo
.toString();
261 final double longitude
= gpsInfo
.getLongitudeAsDegreesEast();
262 final double latitude
= gpsInfo
.getLatitudeAsDegreesNorth();
264 System
.out
.println(" " + "GPS Description: " + gpsDescription
);
265 System
.out
.println(" " + "GPS Longitude (Degrees East): " + longitude
);
266 System
.out
.println(" " + "GPS Latitude (Degrees North): " + latitude
);
270 // more specific example of how to manually access GPS values
271 final TiffField gpsLatitudeRefField
= jpegMetadata
272 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LATITUDE_REF
);
273 final TiffField gpsLatitudeField
= jpegMetadata
274 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LATITUDE
);
275 final TiffField gpsLongitudeRefField
= jpegMetadata
276 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LONGITUDE_REF
);
277 final TiffField gpsLongitudeField
= jpegMetadata
278 .findEXIFValueWithExactMatch(GpsTagConstants
.GPS_TAG_GPS_LONGITUDE
);
279 if (gpsLatitudeRefField
!= null && gpsLatitudeField
!= null && gpsLongitudeRefField
!= null
280 && gpsLongitudeField
!= null) {
281 // all of these values are strings.
282 final String gpsLatitudeRef
= (String
) gpsLatitudeRefField
.getValue();
283 final RationalNumber
[] gpsLatitude
= (RationalNumber
[]) (gpsLatitudeField
.getValue());
284 final String gpsLongitudeRef
= (String
) gpsLongitudeRefField
.getValue();
285 final RationalNumber
[] gpsLongitude
= (RationalNumber
[]) gpsLongitudeField
.getValue();
287 final RationalNumber gpsLatitudeDegrees
= gpsLatitude
[0];
288 final RationalNumber gpsLatitudeMinutes
= gpsLatitude
[1];
289 final RationalNumber gpsLatitudeSeconds
= gpsLatitude
[2];
291 final RationalNumber gpsLongitudeDegrees
= gpsLongitude
[0];
292 final RationalNumber gpsLongitudeMinutes
= gpsLongitude
[1];
293 final RationalNumber gpsLongitudeSeconds
= gpsLongitude
[2];
295 // This will format the gps info like so:
297 // gpsLatitude: 8 degrees, 40 minutes, 42.2 seconds S
298 // gpsLongitude: 115 degrees, 26 minutes, 21.8 seconds E
300 System
.out
.println(" " + "GPS Latitude: " + gpsLatitudeDegrees
.toDisplayString() + " degrees, "
301 + gpsLatitudeMinutes
.toDisplayString() + " minutes, " + gpsLatitudeSeconds
.toDisplayString()
302 + " seconds " + gpsLatitudeRef
);
303 System
.out
.println(" " + "GPS Longitude: " + gpsLongitudeDegrees
.toDisplayString() + " degrees, "
304 + gpsLongitudeMinutes
.toDisplayString() + " minutes, " + gpsLongitudeSeconds
.toDisplayString()
305 + " seconds " + gpsLongitudeRef
);
309 System
.out
.println();
311 final List
<ImageMetadataItem
> items
= jpegMetadata
.getItems();
312 for (final ImageMetadataItem item
: items
) {
313 System
.out
.println(" " + "item: " + item
);
317 System
.out
.println();
321 private static void printTagValue(final JpegImageMetadata jpegMetadata
, final TagInfo tagInfo
) {
322 final TiffField field
= jpegMetadata
.findEXIFValueWithExactMatch(tagInfo
);
324 System
.out
.println(tagInfo
.name
+ ": " + "Not Found.");
326 System
.out
.println(tagInfo
.name
+ ": " + field
.getValueDescription());