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