Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / ImageUtils.java
CommitLineData
b771aed5
NR
1package be.nikiroo.utils;
2
b771aed5 3import java.io.File;
b771aed5
NR
4import java.io.IOException;
5import java.io.InputStream;
6
80500544 7import be.nikiroo.utils.serial.SerialUtils;
b771aed5
NR
8
9/**
10 * This class offer some utilities based around images.
11 *
12 * @author niki
13 */
80500544
NR
14public abstract class ImageUtils {
15 private static ImageUtils instance = newObject();
b771aed5
NR
16
17 /**
80500544
NR
18 * Get a (unique) instance of an {@link ImageUtils} compatible with your
19 * system.
b771aed5 20 *
80500544 21 * @return an {@link ImageUtils}
b771aed5 22 */
80500544
NR
23 public static ImageUtils getInstance() {
24 return instance;
b771aed5
NR
25 }
26
27 /**
80500544
NR
28 * Save the given resource as an image on disk using the given image format
29 * for content, or with "png" format if it fails.
b771aed5 30 *
80500544
NR
31 * @param img
32 * the resource
33 * @param target
34 * the target file
35 * @param format
36 * the file format ("png", "jpeg", "bmp"...)
4b7d32e7
NR
37 *
38 * @throws IOException
80500544 39 * in case of I/O error
4b7d32e7 40 */
80500544
NR
41 public abstract void saveAsImage(Image img, File target, String format)
42 throws IOException;
b771aed5 43
b771aed5
NR
44 /**
45 * Return the EXIF transformation flag of this image if any.
46 *
47 * <p>
48 * Note: this code has been found on internet; thank you anonymous coder.
49 * </p>
50 *
51 * @param in
52 * the data {@link InputStream}
53 *
54 * @return the transformation flag if any
55 *
56 * @throws IOException
57 * in case of IO error
58 */
80500544 59 protected static int getExifTransorm(InputStream in) throws IOException {
b771aed5
NR
60 int[] exif_data = new int[100];
61 int set_flag = 0;
62 int is_motorola = 0;
63
64 /* Read File head, check for JPEG SOI + Exif APP1 */
65 for (int i = 0; i < 4; i++)
66 exif_data[i] = in.read();
67
68 if (exif_data[0] != 0xFF || exif_data[1] != 0xD8
69 || exif_data[2] != 0xFF || exif_data[3] != 0xE1)
70 return -2;
71
72 /* Get the marker parameter length count */
73 int length = (in.read() << 8 | in.read());
74
75 /* Length includes itself, so must be at least 2 */
76 /* Following Exif data length must be at least 6 */
77 if (length < 8)
78 return -1;
79 length -= 8;
80 /* Read Exif head, check for "Exif" */
81 for (int i = 0; i < 6; i++)
82 exif_data[i] = in.read();
83
84 if (exif_data[0] != 0x45 || exif_data[1] != 0x78
85 || exif_data[2] != 0x69 || exif_data[3] != 0x66
86 || exif_data[4] != 0 || exif_data[5] != 0)
87 return -1;
88
89 /* Read Exif body */
90 length = length > exif_data.length ? exif_data.length : length;
91 for (int i = 0; i < length; i++)
92 exif_data[i] = in.read();
93
94 if (length < 12)
95 return -1; /* Length of an IFD entry */
96
97 /* Discover byte order */
98 if (exif_data[0] == 0x49 && exif_data[1] == 0x49)
99 is_motorola = 0;
100 else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D)
101 is_motorola = 1;
102 else
103 return -1;
104
105 /* Check Tag Mark */
106 if (is_motorola == 1) {
107 if (exif_data[2] != 0)
108 return -1;
109 if (exif_data[3] != 0x2A)
110 return -1;
111 } else {
112 if (exif_data[3] != 0)
113 return -1;
114 if (exif_data[2] != 0x2A)
115 return -1;
116 }
117
118 /* Get first IFD offset (offset to IFD0) */
119 int offset;
120 if (is_motorola == 1) {
121 if (exif_data[4] != 0)
122 return -1;
123 if (exif_data[5] != 0)
124 return -1;
125 offset = exif_data[6];
126 offset <<= 8;
127 offset += exif_data[7];
128 } else {
129 if (exif_data[7] != 0)
130 return -1;
131 if (exif_data[6] != 0)
132 return -1;
133 offset = exif_data[5];
134 offset <<= 8;
135 offset += exif_data[4];
136 }
137 if (offset > length - 2)
138 return -1; /* check end of data segment */
139
140 /* Get the number of directory entries contained in this IFD */
141 int number_of_tags;
142 if (is_motorola == 1) {
143 number_of_tags = exif_data[offset];
144 number_of_tags <<= 8;
145 number_of_tags += exif_data[offset + 1];
146 } else {
147 number_of_tags = exif_data[offset + 1];
148 number_of_tags <<= 8;
149 number_of_tags += exif_data[offset];
150 }
151 if (number_of_tags == 0)
152 return -1;
153 offset += 2;
154
155 /* Search for Orientation Tag in IFD0 */
156 for (;;) {
157 if (offset > length - 12)
158 return -1; /* check end of data segment */
159 /* Get Tag number */
160 int tagnum;
161 if (is_motorola == 1) {
162 tagnum = exif_data[offset];
163 tagnum <<= 8;
164 tagnum += exif_data[offset + 1];
165 } else {
166 tagnum = exif_data[offset + 1];
167 tagnum <<= 8;
168 tagnum += exif_data[offset];
169 }
170 if (tagnum == 0x0112)
171 break; /* found Orientation Tag */
172 if (--number_of_tags == 0)
173 return -1;
174 offset += 12;
175 }
176
177 /* Get the Orientation value */
178 if (is_motorola == 1) {
179 if (exif_data[offset + 8] != 0)
180 return -1;
181 set_flag = exif_data[offset + 9];
182 } else {
183 if (exif_data[offset + 9] != 0)
184 return -1;
185 set_flag = exif_data[offset + 8];
186 }
187 if (set_flag > 8)
188 return -1;
189
190 return set_flag;
191 }
80500544 192
10b6023d
NR
193 /**
194 * Check that the class can operate (for instance, that all the required
195 * libraries or frameworks are present).
196 *
197 * @return TRUE if it works
198 */
199 abstract protected boolean check();
200
80500544
NR
201 /**
202 * Create a new {@link ImageUtils}.
203 *
204 * @return the {@link ImageUtils}
205 */
206 private static ImageUtils newObject() {
e8aa5bf9
NR
207 for (String clazz : new String[] { "be.nikiroo.utils.ui.ImageUtilsAwt",
208 "be.nikiroo.utils.android.ImageUtilsAndroid" }) {
80500544 209 try {
10b6023d
NR
210 ImageUtils obj = (ImageUtils) SerialUtils.createObject(clazz);
211 if (obj.check()) {
212 return obj;
213 }
d01bd74e 214 } catch (Throwable e) {
80500544
NR
215 }
216 }
217
218 return null;
219 }
b771aed5 220}