1 package com.atlassian.core.util.thumbnail;
2
3 import com.atlassian.core.util.ImageInfo;
4
5 import org.apache.log4j.Category;
6 import org.apache.log4j.Logger;
7
8 import java.awt.Graphics;
9 import java.awt.Image;
10 import java.awt.Toolkit;
11 import java.awt.image.AreaAveragingScaleFilter;
12 import java.awt.image.BufferedImage;
13 import java.awt.image.FilteredImageSource;
14 import java.awt.image.ImageProducer;
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.FileNotFoundException;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.net.MalformedURLException;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Arrays;
24 import java.util.Collections;
25
26 import javax.imageio.ImageIO;
27
28 import javax.imageio.ImageWriter;
29 import javax.imageio.ImageWriteParam;
30 import javax.imageio.IIOImage;
31 import javax.imageio.stream.FileImageOutputStream;
32
33
34
35
36 public class Thumber
37 {
38 private static final Logger log = Logger.getLogger(Thumber.class);
39 private ImageInfo imageInfo = new ImageInfo();
40
41 public static final List<String> THUMBNAIL_MIME_TYPES = Collections.unmodifiableList(Arrays.asList(ImageIO.getReaderMIMETypes()));
42 public static final List<String> THUMBNAIL_FORMATS = Collections.unmodifiableList(Arrays.asList(ImageIO.getReaderFormatNames()));
43
44 public static List<String> getThumbnailMimeTypes()
45 {
46 return THUMBNAIL_MIME_TYPES;
47 }
48
49 public static List<String> getThumbnailFormats()
50 {
51 return THUMBNAIL_FORMATS;
52 }
53
54 private float encodingQuality = 0.80f;
55
56
57
58
59 public boolean checkToolkit()
60 {
61 try
62 {
63 Toolkit.getDefaultToolkit();
64 }
65 catch (Throwable e)
66 {
67 log.error("Unable to acquire AWT default toolkit - thumbnails will not be displayed. Check DISPLAY variable or use setting -Djava.awt.headless=true.", e);
68 return false;
69 }
70 return true;
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84
85 public Thumbnail retrieveOrCreateThumbNail(File originalFile, File thumbnailFile, int maxWidth, int maxHeight, long thumbnailId) throws MalformedURLException
86 {
87 FileInputStream originalFileStream = null;
88 try
89 {
90 originalFileStream = new FileInputStream(originalFile);
91 return retrieveOrCreateThumbNail(originalFileStream, originalFile.getName(), thumbnailFile, maxWidth, maxHeight, thumbnailId);
92 }
93 catch (FileNotFoundException e)
94 {
95 log.error("Unable to create thumbnail: file not found: " + originalFile.getAbsolutePath());
96 }
97 finally
98 {
99 try
100 {
101 originalFileStream.close();
102 }
103 catch (IOException e)
104 {
105 log.warn(e, e);
106 }
107 }
108
109 return null;
110 }
111
112
113
114 public void storeImage(BufferedImage scaledImage, File file) throws FileNotFoundException
115 {
116 if (scaledImage == null)
117 {
118 log.warn("Can't store a null scaledImage.");
119 return;
120 }
121 FileImageOutputStream fout = null;
122 try
123 {
124 fout = new FileImageOutputStream((file));
125
126 ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
127
128 ImageWriteParam param = writer.getDefaultWriteParam();
129 param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
130 param.setCompressionQuality(encodingQuality);
131 writer.setOutput(fout);
132 writer.write(null,new IIOImage(scaledImage, null,null), param);
133 }
134 catch (IOException e)
135 {
136 log.error("Error encoding the thumbnail image", e);
137 }
138 finally
139 {
140 try
141 {
142 if (fout != null)
143 {
144 fout.close();
145 }
146 }
147 catch (IOException e)
148 {
149
150 }
151 }
152 }
153
154 public BufferedImage scaleImage(Image imageToScale, WidthHeightHelper newDimensions)
155 {
156
157
158
159
160
161
162 if (imageToScale instanceof BufferedImage)
163 {
164 BufferedImage bufferedImage = (BufferedImage) imageToScale;
165 if (!bufferedImage.getColorModel().getColorSpace().isCS_sRGB()) {
166 BufferedImage sRGBImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_INT_RGB);
167 Graphics g = sRGBImage.getGraphics();
168 g.drawImage(bufferedImage, 0, 0, null);
169 g.dispose();
170 imageToScale = sRGBImage;
171 }
172 }
173
174 AreaAveragingScaleFilter scaleFilter =
175 new AreaAveragingScaleFilter(newDimensions.getWidth(), newDimensions.getHeight());
176 ImageProducer producer = new FilteredImageSource(imageToScale.getSource(),
177 scaleFilter);
178
179 SimpleImageConsumer generator = new SimpleImageConsumer();
180 producer.startProduction(generator);
181 BufferedImage scaled = generator.getImage();
182
183
184 if (scaled == null)
185 log.warn("Unabled to create scaled image.");
186 else
187 scaled.flush();
188
189 return scaled;
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203 public Thumbnail retrieveOrCreateThumbNail(InputStream originalFileStream, String fileName, File thumbnailFile, int maxWidth, int maxHeight, long thumbnailId) throws MalformedURLException
204 {
205 Thumbnail thumbnail = null;
206 try
207 {
208 thumbnail = getThumbnail(thumbnailFile, fileName, thumbnailId);
209 }
210 catch (IOException e)
211 {
212 log.error("Unable to get thumbnail image for id " + thumbnailId, e);
213 return null;
214 }
215
216 if (thumbnail == null)
217 {
218 try
219 {
220 thumbnail = createThumbnail(originalFileStream, thumbnailFile, maxWidth, maxHeight, thumbnailId, fileName);
221 }
222 catch (IOException e)
223 {
224 log.error("Unable to create thumbnail image for id " + thumbnailId, e);
225 return null;
226 }
227 }
228
229 return thumbnail;
230 }
231
232
233 private BufferedImage scaleImage(Image originalImage, int maxWidth, int maxHeight)
234 {
235 return scaleImage(originalImage, determineScaleSize(maxWidth, maxHeight, originalImage));
236 }
237
238 private WidthHeightHelper determineScaleSize(int maxWidth, int maxHeight, Image image)
239 {
240 return determineScaleSize(maxWidth, maxHeight, image.getWidth(null), image.getHeight(null));
241 }
242
243 private Thumbnail createThumbnail(InputStream originalFile, File thumbnailFile, int maxWidth, int maxHeight, long thumbId, String fileName) throws IOException, FileNotFoundException
244 {
245
246 Image originalImage = getImage(originalFile);
247
248 BufferedImage scaledImage = scaleImage(originalImage, maxWidth, maxHeight);
249
250 int height = scaledImage.getHeight();
251 int width = scaledImage.getWidth();
252
253 storeImage(scaledImage, thumbnailFile);
254
255 return new Thumbnail(height, width, fileName, thumbId);
256 }
257
258 private Thumbnail getThumbnail(File thumbnailFile, String filename, long thumbId) throws IOException
259 {
260 if (thumbnailFile.exists())
261 {
262 final Image thumbImage = getImage(thumbnailFile);
263 return new Thumbnail(thumbImage.getHeight(null), thumbImage.getWidth(null), filename, thumbId);
264 }
265 return null;
266 }
267
268
269
270
271
272
273 public Image getImage(File file) throws IOException
274 {
275 return ImageIO.read(file);
276 }
277
278
279
280
281
282
283
284 public Image getImage(InputStream is) throws IOException
285 {
286 return ImageIO.read(is);
287 }
288
289
290
291
292
293 public void setEncodingQuality(float f)
294 {
295 if (f > 1.0f || f < 0.0f)
296 {
297 throw new IllegalArgumentException("Invalid quality setting '"+f+"', value must be between 0 and 1. ");
298 }
299 encodingQuality = f;
300 }
301
302 public WidthHeightHelper determineScaleSize(int maxWidth, int maxHeight, int imageWidth, int imageHeight)
303 {
304 if (maxHeight > imageHeight && maxWidth > imageWidth)
305 {
306 return new Thumber.WidthHeightHelper(imageWidth, imageHeight);
307 }
308
309
310 double thumbRatio = (double) maxWidth / (double) maxHeight;
311
312 double imageRatio = (double) imageWidth / (double) imageHeight;
313
314 if (thumbRatio < imageRatio)
315 {
316 return new Thumber.WidthHeightHelper(maxWidth, (int) (maxWidth / imageRatio));
317 }
318 else
319 {
320 return new Thumber.WidthHeightHelper((int) (maxHeight * imageRatio), maxHeight);
321 }
322 }
323
324 public boolean isFileSupportedImage(File file)
325 {
326 try
327 {
328 return isFileSupportedImage(new FileInputStream(file));
329 }
330 catch (FileNotFoundException e)
331 {
332 return false;
333 }
334 }
335
336 public boolean isFileSupportedImage(InputStream inputStream)
337 {
338 try
339 {
340 imageInfo.setInput(inputStream);
341 imageInfo.check();
342 return THUMBNAIL_FORMATS.contains(imageInfo.getFormatName());
343 }
344 finally
345 {
346 try
347 {
348 if (inputStream != null)
349 {
350 inputStream.close();
351 }
352 } catch (Exception e)
353 {
354 log.error(e, e);
355 }
356 }
357 }
358
359 public static class WidthHeightHelper
360 {
361 private int width;
362 private int height;
363
364 public WidthHeightHelper(int width, int height)
365 {
366 this.width = width;
367 this.height = height;
368 }
369
370 public int getWidth()
371 {
372 return width;
373 }
374
375 public void setWidth(int width)
376 {
377 this.width = width;
378 }
379
380 public int getHeight()
381 {
382 return height;
383 }
384
385 public void setHeight(int height)
386 {
387 this.height = height;
388 }
389 }
390 }