Merge branch 'subtree'
[nikiroo-utils.git] / ui / ImageUtilsAwt.java
1 package be.nikiroo.utils.ui;
2
3 import java.awt.Dimension;
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.File;
9 import java.io.IOException;
10 import java.io.InputStream;
11
12 import javax.imageio.ImageIO;
13
14 import be.nikiroo.utils.IOUtils;
15 import be.nikiroo.utils.Image;
16 import be.nikiroo.utils.ImageUtils;
17 import be.nikiroo.utils.StringUtils;
18
19 /**
20 * This class offer some utilities based around images and uses java.awt.
21 *
22 * @author niki
23 */
24 public class ImageUtilsAwt extends ImageUtils {
25 /**
26 * A rotation to perform on an image.
27 *
28 * @author niki
29 */
30 public enum Rotation {
31 /** No rotation */
32 NONE,
33 /** Rotate the image to the right */
34 RIGHT,
35 /** Rotate the image to the left */
36 LEFT,
37 /** Rotate the image by 180° */
38 UTURN
39 }
40
41 @Override
42 protected boolean check() {
43 // Will not work if ImageIO is not available
44 ImageIO.getCacheDirectory();
45 return true;
46 }
47
48 @Override
49 public void saveAsImage(Image img, File target, String format)
50 throws IOException {
51 try {
52 BufferedImage image = fromImage(img);
53
54 boolean ok = false;
55 try {
56
57 ok = ImageIO.write(image, format, target);
58 } catch (IOException e) {
59 ok = false;
60 }
61
62 // Some formats are not reliable
63 // Second chance: PNG
64 if (!ok && !format.equals("png")) {
65 try {
66 ok = ImageIO.write(image, "png", target);
67 } catch (IllegalArgumentException e) {
68 throw e;
69 } catch (Exception e) {
70 throw new IOException("Undocumented exception occured, "
71 + "converting to IOException", e);
72 }
73 }
74
75 if (!ok) {
76 throw new IOException(
77 "Cannot find a writer for this image and format: "
78 + format);
79 }
80 } catch (IOException e) {
81 throw new IOException("Cannot write image to " + target, e);
82 }
83 }
84
85 /**
86 * Convert the given {@link Image} into a {@link BufferedImage} object,
87 * respecting the EXIF transformations if any.
88 *
89 * @param img
90 * the {@link Image}
91 *
92 * @return the {@link Image} object
93 *
94 * @throws IOException
95 * in case of IO error
96 */
97 public static BufferedImage fromImage(Image img) throws IOException {
98 return fromImage(img, Rotation.NONE);
99 }
100
101 /**
102 * Convert the given {@link Image} into a {@link BufferedImage} object,
103 * respecting the EXIF transformations if any.
104 *
105 * @param img
106 * the {@link Image}
107 * @param rotation
108 * the rotation to apply, if any (can be null, same as
109 * {@link Rotation#NONE})
110 *
111 * @return the {@link Image} object
112 *
113 * @throws IOException
114 * in case of IO error
115 */
116 public static BufferedImage fromImage(Image img, Rotation rotation)
117 throws IOException {
118 InputStream in = img.newInputStream();
119 BufferedImage image;
120 try {
121 int orientation;
122 try {
123 orientation = getExifTransorm(in);
124 } catch (Exception e) {
125 // no EXIF transform, ok
126 orientation = -1;
127 }
128
129 in.reset();
130
131 try {
132 image = ImageIO.read(in);
133 } catch (IllegalArgumentException e) {
134 throw e;
135 } catch (Exception e) {
136 throw new IOException("Undocumented exception occured, "
137 + "converting to IOException", e);
138 }
139
140 if (image == null) {
141 String extra = "";
142 if (img.getSize() <= 2048) {
143 try {
144 byte[] data = null;
145 InputStream inData = img.newInputStream();
146 try {
147 data = IOUtils.toByteArray(inData);
148 } finally {
149 inData.close();
150 }
151 extra = ", content: " + new String(data, "UTF-8");
152 } catch (Exception e) {
153 extra = ", content unavailable";
154 }
155 }
156 String ssize = StringUtils.formatNumber(img.getSize());
157 throw new IOException(
158 "Failed to convert input to image, size was: " + ssize
159 + extra);
160 }
161
162 // Note: this code has been found on Internet;
163 // thank you anonymous coder.
164 int width = image.getWidth();
165 int height = image.getHeight();
166 AffineTransform affineTransform = new AffineTransform();
167
168 switch (orientation) {
169 case 1:
170 affineTransform = null;
171 break;
172 case 2: // Flip X
173 affineTransform.scale(-1.0, 1.0);
174 affineTransform.translate(-width, 0);
175 break;
176 case 3: // PI rotation
177 affineTransform.translate(width, height);
178 affineTransform.rotate(Math.PI);
179 break;
180 case 4: // Flip Y
181 affineTransform.scale(1.0, -1.0);
182 affineTransform.translate(0, -height);
183 break;
184 case 5: // - PI/2 and Flip X
185 affineTransform.rotate(-Math.PI / 2);
186 affineTransform.scale(-1.0, 1.0);
187 break;
188 case 6: // -PI/2 and -width
189 affineTransform.translate(height, 0);
190 affineTransform.rotate(Math.PI / 2);
191 break;
192 case 7: // PI/2 and Flip
193 affineTransform.scale(-1.0, 1.0);
194 affineTransform.translate(-height, 0);
195 affineTransform.translate(0, width);
196 affineTransform.rotate(3 * Math.PI / 2);
197 break;
198 case 8: // PI / 2
199 affineTransform.translate(0, width);
200 affineTransform.rotate(3 * Math.PI / 2);
201 break;
202 default:
203 affineTransform = null;
204 break;
205 }
206
207 if (rotation == null)
208 rotation = Rotation.NONE;
209
210 switch (rotation) {
211 case RIGHT:
212 if (affineTransform == null) {
213 affineTransform = new AffineTransform();
214 }
215 affineTransform.translate(height, 0);
216 affineTransform.rotate(Math.PI / 2);
217
218 int tmp = width;
219 width = height;
220 height = tmp;
221
222 break;
223 case LEFT:
224 if (affineTransform == null) {
225 affineTransform = new AffineTransform();
226 }
227 affineTransform.translate(0, width);
228 affineTransform.rotate(3 * Math.PI / 2);
229
230 int temp = width;
231 width = height;
232 height = temp;
233
234 break;
235 case UTURN:
236 if (affineTransform == null) {
237 affineTransform = new AffineTransform();
238 }
239 affineTransform.translate(width, height);
240 affineTransform.rotate(Math.PI);
241 break;
242 default:
243 break;
244 }
245
246 if (affineTransform != null) {
247 AffineTransformOp affineTransformOp = new AffineTransformOp(
248 affineTransform, AffineTransformOp.TYPE_BILINEAR);
249
250 BufferedImage transformedImage = new BufferedImage(width,
251 height, image.getType());
252 transformedImage = affineTransformOp.filter(image,
253 transformedImage);
254
255 image = transformedImage;
256 }
257 //
258 } finally {
259 in.close();
260 }
261
262 return image;
263 }
264
265 /**
266 * Scale a dimension.
267 *
268 * @param imageSize
269 * the actual image size
270 * @param areaSize
271 * the base size of the target to get snap sizes for
272 * @param zoom
273 * the zoom factor (ignored on snap mode)
274 * @param snapMode
275 * NULL for no snap mode, TRUE to snap to width and FALSE for
276 * snap to height)
277 *
278 * @return the scaled (minimum is 1x1)
279 */
280 public static Dimension scaleSize(Dimension imageSize, Dimension areaSize,
281 double zoom, Boolean snapMode) {
282 Integer[] sz = scaleSize(imageSize.width, imageSize.height,
283 areaSize.width, areaSize.height, zoom, snapMode);
284 return new Dimension(sz[0], sz[1]);
285 }
286
287 /**
288 * Resize the given image.
289 *
290 * @param image
291 * the image to resize
292 * @param areaSize
293 * the base size of the target dimension for snap sizes
294 * @param zoom
295 * the zoom factor (ignored on snap mode)
296 * @param snapMode
297 * NULL for no snap mode, TRUE to snap to width and FALSE for
298 * snap to height)
299 *
300 * @return a new, resized image
301 */
302 public static BufferedImage scaleImage(BufferedImage image,
303 Dimension areaSize, double zoom, Boolean snapMode) {
304 Dimension scaledSize = scaleSize(
305 new Dimension(image.getWidth(), image.getHeight()), areaSize,
306 zoom, snapMode);
307
308 return scaleImage(image, scaledSize);
309 }
310
311 /**
312 * Resize the given image.
313 *
314 * @param image
315 * the image to resize
316 * @param targetSize
317 * the target size
318 *
319 * @return a new, resized image
320 */
321 public static BufferedImage scaleImage(BufferedImage image,
322 Dimension targetSize) {
323 BufferedImage resizedImage = new BufferedImage(targetSize.width,
324 targetSize.height, BufferedImage.TYPE_4BYTE_ABGR);
325 Graphics2D g = resizedImage.createGraphics();
326 try {
327 g.drawImage(image, 0, 0, targetSize.width, targetSize.height, null);
328 } finally {
329 g.dispose();
330 }
331
332 return resizedImage;
333 }
334 }