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