Rev 1254 | Rev 1272 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
936 | dev | 1 | package ak.photoalbum.images; |
2 | |||
3 | import java.util.Map; |
||
4 | import java.util.HashMap; |
||
5 | import java.util.Comparator; |
||
6 | import java.util.Arrays; |
||
7 | import java.io.File; |
||
8 | import java.io.FileFilter; |
||
9 | import java.io.IOException; |
||
10 | import java.io.FileInputStream; |
||
11 | import java.io.FileOutputStream; |
||
12 | import java.io.OutputStream; |
||
1257 | dev | 13 | import java.io.BufferedReader; |
14 | import java.io.InputStreamReader; |
||
936 | dev | 15 | import java.awt.Image; |
16 | import java.awt.Toolkit; |
||
17 | import java.awt.Graphics; |
||
18 | import java.awt.image.BufferedImage; |
||
19 | import java.awt.image.ImageObserver; |
||
20 | import java.awt.image.PixelGrabber; |
||
21 | import javax.imageio.ImageIO; |
||
22 | import org.apache.log4j.Logger; |
||
23 | import marcoschmidt.image.ImageInfo; |
||
24 | import ak.photoalbum.util.FileUtils; |
||
25 | import ak.photoalbum.util.FileNameComparator; |
||
26 | |||
27 | public class Thumbnailer |
||
28 | { |
||
29 | protected static final String DEFAULT_FORMAT = "jpg"; |
||
30 | protected static final String SMALL_SUFFIX = ".small"; |
||
31 | protected static final String MEDIUM_SUFFIX = ".medium"; |
||
32 | protected static final String DIR_SUFFIX = ".dir"; |
||
1074 | dev | 33 | protected static final String DIR_PART_SUFFIX = ".dir_part"; |
936 | dev | 34 | protected static final int DEFAULT_SMALL_WIDTH = 120; |
35 | protected static final int DEFAULT_SMALL_HEIGHT = 120; |
||
36 | protected static final int DEFAULT_MEDIUM_WIDTH = 800; |
||
37 | protected static final int DEFAULT_MEDIUM_HEIGHT = 800; |
||
38 | |||
39 | protected Logger logger; |
||
40 | protected ImageResizer resizer; |
||
41 | protected int smallWidth = DEFAULT_SMALL_WIDTH; |
||
42 | protected int smallHeight = DEFAULT_SMALL_HEIGHT; |
||
43 | protected int mediumWidth = DEFAULT_MEDIUM_WIDTH; |
||
44 | protected int mediumHeight = DEFAULT_MEDIUM_HEIGHT; |
||
45 | protected File cacheDir; |
||
46 | protected String format = DEFAULT_FORMAT; |
||
47 | protected Map smallCache = new HashMap(); |
||
48 | protected Map mediumCache = new HashMap(); |
||
49 | protected Map dirCache = new HashMap(); |
||
50 | protected File imagesRoot; |
||
51 | protected FileFilter imagesFilter = null; |
||
52 | protected Comparator fileNameComparator = new FileNameComparator(true); |
||
53 | protected Comparator fileNameComparatorRev = new FileNameComparator(false); |
||
1074 | dev | 54 | protected boolean nativeMode = true; // FIXME config |
936 | dev | 55 | |
56 | protected File dirTemplate; |
||
57 | protected ThumbnailPosition[] dirThumbnailPositions; |
||
58 | protected int[] dirTemplateSize; |
||
59 | protected long dirTemplateTimestamp; |
||
60 | |||
61 | public Thumbnailer() |
||
62 | { |
||
63 | this.logger = Logger.getLogger(this.getClass()); |
||
64 | } |
||
65 | |||
66 | public String getMime() |
||
67 | { |
||
68 | return FileUtils.getMime(format); |
||
69 | } |
||
70 | |||
71 | public int[] getDirSize(File origin) |
||
72 | throws IOException |
||
73 | { |
||
74 | if(dirTemplateSize == null |
||
75 | || dirTemplateTimestamp != dirTemplate.lastModified()) |
||
76 | { |
||
77 | dirTemplateSize = getOriginSize(dirTemplate); |
||
78 | dirTemplateTimestamp = dirTemplate.lastModified(); |
||
79 | } |
||
80 | |||
81 | return dirTemplateSize; |
||
82 | } |
||
83 | |||
84 | public int[] getSmallSize(File origin) |
||
85 | throws IOException |
||
86 | { |
||
87 | CachedFile cached = getCached(smallCache, origin); |
||
88 | |||
89 | if(cached == null) { |
||
90 | int[] originSize = getOriginSize(origin); |
||
91 | |||
92 | return calcSizes(originSize[0], originSize[1], smallWidth, smallHeight); |
||
93 | } |
||
94 | else { |
||
95 | int[] originSize = new int[2]; |
||
96 | |||
97 | originSize[0] = cached.getWidth(); |
||
98 | originSize[1] = cached.getHeight(); |
||
99 | |||
100 | return originSize; |
||
101 | } |
||
102 | } |
||
103 | |||
104 | public int[] getMediumSize(File origin) |
||
105 | throws IOException |
||
106 | { |
||
107 | CachedFile cached = getCached(mediumCache, origin); |
||
108 | |||
109 | if(cached == null) { |
||
110 | int[] originSize = getOriginSize(origin); |
||
111 | |||
112 | return calcSizes(originSize[0], originSize[1], mediumWidth, mediumHeight); |
||
113 | } |
||
114 | else { |
||
115 | int[] originSize = new int[2]; |
||
116 | |||
117 | originSize[0] = cached.getWidth(); |
||
118 | originSize[1] = cached.getHeight(); |
||
119 | |||
120 | return originSize; |
||
121 | } |
||
122 | } |
||
123 | |||
124 | protected int[] getOriginSize(File origin) |
||
125 | throws IOException |
||
126 | { |
||
127 | if(logger.isDebugEnabled()) |
||
128 | logger.debug("get size of " + origin.getCanonicalPath()); |
||
129 | |||
130 | ImageInfo ii = new ImageInfo(); |
||
131 | FileInputStream in = null; |
||
132 | int[] res = new int[2]; |
||
133 | |||
134 | try { |
||
135 | in = new FileInputStream(origin); |
||
136 | ii.setInput(in); |
||
137 | |||
138 | if(!ii.check()) { |
||
139 | logger.warn("not supported format of " + origin.getCanonicalPath()); |
||
140 | res[0] = 0; |
||
141 | res[1] = 0; |
||
142 | } |
||
143 | else{ |
||
144 | res[0] = ii.getWidth(); |
||
145 | res[1] = ii.getHeight(); |
||
146 | } |
||
147 | } |
||
148 | finally { |
||
149 | if(in != null) in.close(); |
||
150 | } |
||
151 | |||
152 | return res; |
||
153 | } |
||
154 | |||
155 | public void rebuildCache() |
||
156 | throws IOException |
||
157 | { |
||
158 | logger.info("rebuild cache"); |
||
159 | |||
160 | deleteCache(); |
||
161 | buildCache(); |
||
162 | } |
||
163 | |||
164 | public void deleteCache() |
||
165 | throws IOException |
||
166 | { |
||
167 | logger.info("delete cache"); |
||
168 | |||
169 | deleteCache(cacheDir); |
||
170 | } |
||
171 | |||
172 | public void buildCache() |
||
173 | throws IOException |
||
174 | { |
||
175 | logger.info("build cache"); |
||
176 | |||
177 | buildCache(imagesRoot); |
||
178 | } |
||
179 | |||
1257 | dev | 180 | public void reloadCache() |
181 | throws IOException |
||
182 | { |
||
183 | logger.info("reload cache"); |
||
184 | |||
185 | smallCache.clear(); |
||
186 | mediumCache.clear(); |
||
187 | dirCache.clear(); |
||
188 | loadCaches(cacheDir); |
||
189 | } |
||
190 | |||
936 | dev | 191 | protected void deleteCache(File dir) |
192 | throws IOException |
||
193 | { |
||
194 | File[] children = dir.listFiles(); |
||
195 | |||
196 | if(children == null) return; // the dir does not exists |
||
197 | |||
198 | Arrays.sort(children, fileNameComparator); |
||
199 | |||
200 | for(int i = 0; i < children.length; i++) { |
||
201 | if(children[i].isDirectory()) |
||
202 | deleteCache(children[i]); |
||
203 | else |
||
204 | children[i].delete(); |
||
205 | } |
||
206 | dir.delete(); |
||
207 | } |
||
208 | |||
209 | protected void buildCache(File dir) |
||
210 | throws IOException |
||
211 | { |
||
212 | File[] children; |
||
213 | |||
214 | if(imagesFilter == null) |
||
215 | children = dir.listFiles(); |
||
216 | else |
||
217 | children = dir.listFiles(imagesFilter); |
||
218 | |||
219 | if(children == null) return; // the dir does not exists |
||
220 | |||
221 | Arrays.sort(children, fileNameComparator); |
||
222 | |||
223 | for(int i = 0; i < children.length; i++) { |
||
224 | if(children[i].isDirectory()) { |
||
225 | writeDir(children[i], null); |
||
226 | buildCache(children[i]); |
||
227 | } |
||
228 | else { |
||
229 | writeSmall(children[i], null); |
||
230 | writeMedium(children[i], null); |
||
231 | } |
||
232 | } |
||
233 | } |
||
234 | |||
235 | protected CachedFile getCached(Map cache, File imageFile) |
||
236 | throws IOException |
||
237 | { |
||
238 | CachedFile cached = (CachedFile)cache.get(imageFile.getCanonicalPath()); |
||
239 | |||
240 | if(cached == null || !cached.getFile().exists()) { |
||
241 | logger.debug("not found in cache"); |
||
242 | return null; |
||
243 | } |
||
244 | |||
245 | if(cached.getOriginTimestamp() != imageFile.lastModified()) { |
||
246 | cached.getFile().delete(); |
||
247 | cache.remove(imageFile.getCanonicalPath()); |
||
248 | logger.debug("timestamps dont match"); |
||
249 | return null; |
||
250 | } |
||
251 | |||
252 | return cached; |
||
253 | } |
||
254 | |||
255 | protected boolean writeCached(Map cache, File imageFile, OutputStream out) |
||
256 | throws IOException |
||
257 | { |
||
258 | CachedFile cached = getCached(cache, imageFile); |
||
259 | |||
260 | if(cached == null) return false; |
||
261 | |||
262 | if(logger.isDebugEnabled()) |
||
263 | logger.debug("write cached " + imageFile.getCanonicalPath()); |
||
264 | |||
265 | if(out != null) { |
||
266 | FileInputStream in = null; |
||
267 | |||
268 | try { |
||
269 | in = new FileInputStream(cached.getFile()); |
||
270 | FileUtils.copyStreams(in, out); |
||
271 | } |
||
272 | finally { |
||
273 | if(in != null) in.close(); |
||
274 | } |
||
275 | } |
||
276 | |||
277 | return true; |
||
278 | } |
||
279 | |||
1074 | dev | 280 | protected File getCacheFile(File dir, File imageFile, String suffix) |
281 | { |
||
282 | return new File(dir, imageFile.getName() + suffix + "." + format); |
||
283 | } |
||
284 | |||
936 | dev | 285 | protected void cacheThumbnail(Map cache, File imageFile, |
286 | BufferedImage thumbnail, String suffix, int width, int height) |
||
287 | throws IOException |
||
288 | { |
||
289 | logger.debug("cache thumbnail " + suffix + " " |
||
290 | + imageFile.getCanonicalPath()); |
||
291 | |||
292 | File dir = getCacheFileDir(imageFile); |
||
1074 | dev | 293 | File cacheFile = getCacheFile(dir, imageFile, suffix); |
936 | dev | 294 | |
295 | dir.mkdirs(); |
||
296 | ImageIO.write(thumbnail, format, cacheFile); |
||
297 | |||
298 | cache.put(imageFile.getCanonicalPath(), |
||
299 | new CachedFile(cacheFile, imageFile, |
||
300 | cacheFile.lastModified(), imageFile.lastModified(), width, height)); |
||
301 | } |
||
302 | |||
303 | public void startup() |
||
304 | throws IOException |
||
305 | { |
||
306 | logger.info("startup"); |
||
307 | loadCaches(cacheDir); |
||
308 | logger.info("started"); |
||
309 | } |
||
310 | |||
1074 | dev | 311 | protected void loadFileCache(File file) |
936 | dev | 312 | throws IOException |
1074 | dev | 313 | { |
314 | /* FIXME use this optimization? |
||
936 | dev | 315 | String dirEnd = DIR_SUFFIX + "." + format; |
316 | String smallEnd = SMALL_SUFFIX + "." + format; |
||
317 | String mediumEnd = MEDIUM_SUFFIX + "." + format; |
||
1074 | dev | 318 | */ |
936 | dev | 319 | |
1251 | dev | 320 | File origin = null; |
321 | Map cache = null; |
||
936 | dev | 322 | |
1074 | dev | 323 | if(file.getName().endsWith(SMALL_SUFFIX + "." + format)) { |
324 | origin = getOriginFile(file, SMALL_SUFFIX); |
||
325 | cache = smallCache; |
||
936 | dev | 326 | |
1074 | dev | 327 | if(logger.isDebugEnabled()) |
328 | logger.debug("load cached small " + file.getCanonicalPath() |
||
329 | + " for " + origin.getCanonicalPath()); |
||
330 | } |
||
331 | else if(file.getName().endsWith(MEDIUM_SUFFIX + "." + format)) { |
||
332 | origin = getOriginFile(file, MEDIUM_SUFFIX); |
||
333 | cache = mediumCache; |
||
936 | dev | 334 | |
1074 | dev | 335 | if(logger.isDebugEnabled()) |
336 | logger.debug("load cached medium " + file.getCanonicalPath() |
||
337 | + " for " + origin.getCanonicalPath()); |
||
338 | } |
||
339 | else if(file.getName().endsWith(DIR_SUFFIX + "." + format)) { |
||
340 | origin = getOriginFile(file, DIR_SUFFIX); |
||
341 | cache = dirCache; |
||
936 | dev | 342 | |
1074 | dev | 343 | if(logger.isDebugEnabled()) |
344 | logger.debug("load cached dir " + file.getCanonicalPath() |
||
345 | + " for " + origin.getCanonicalPath()); |
||
346 | } |
||
1251 | dev | 347 | else if(file.getName().endsWith(DIR_PART_SUFFIX + "." + format)) { |
348 | if(logger.isDebugEnabled()) |
||
349 | logger.debug("skip cached dir part " + file.getCanonicalPath()); |
||
350 | } |
||
1074 | dev | 351 | else { |
352 | if(logger.isInfoEnabled()) |
||
353 | logger.warn( |
||
354 | "unknown type of cached " + file.getCanonicalPath()); |
||
355 | } |
||
936 | dev | 356 | |
1257 | dev | 357 | if(origin != null && cache != null) { |
1251 | dev | 358 | loadCacheInfo(file, origin, cache); |
1257 | dev | 359 | } |
360 | else { |
||
361 | file.delete(); |
||
362 | |||
363 | if(logger.isDebugEnabled()) |
||
364 | logger.debug("deleted (unknown origin)"); |
||
365 | } |
||
1074 | dev | 366 | } |
936 | dev | 367 | |
1074 | dev | 368 | protected void loadCacheInfo(File file, File origin, Map cache) |
369 | throws IOException |
||
370 | { |
||
371 | long originTimestamp = origin.lastModified(); |
||
372 | long cachedTimestamp = file.lastModified(); |
||
373 | int[] sizes = getOriginSize(file); |
||
936 | dev | 374 | |
1074 | dev | 375 | if(origin.exists() && cachedTimestamp >= originTimestamp) { |
376 | cache.put(origin.getCanonicalPath(), |
||
377 | new CachedFile(file, origin, cachedTimestamp, |
||
378 | originTimestamp, sizes[0], sizes[1])); |
||
936 | dev | 379 | |
1074 | dev | 380 | logger.debug("added"); |
381 | } |
||
382 | else { |
||
383 | file.delete(); |
||
936 | dev | 384 | |
1074 | dev | 385 | if(logger.isDebugEnabled()) |
386 | logger.debug("deleted: " + origin.exists() |
||
387 | + " " + cachedTimestamp + " " + originTimestamp); |
||
388 | } |
||
389 | } |
||
936 | dev | 390 | |
1074 | dev | 391 | protected void loadCaches(File dir) |
392 | throws IOException |
||
393 | { |
||
394 | if(logger.isDebugEnabled()) |
||
395 | logger.debug("load caches in " + dir.getCanonicalPath()); |
||
396 | |||
397 | File[] children = dir.listFiles(); |
||
398 | |||
399 | if(children == null) return; // the dir does not exists |
||
400 | |||
401 | for(int i = 0; i < children.length; i++) { |
||
402 | if(children[i].isDirectory()) |
||
403 | loadCaches(children[i]); |
||
404 | else |
||
405 | loadFileCache(children[i]); |
||
936 | dev | 406 | } |
407 | } |
||
408 | |||
409 | protected File getOriginFile(File cached, String suffix) |
||
410 | throws IOException |
||
411 | { |
||
412 | String fileEnd = suffix + "." + format; |
||
413 | String fileName = cached.getName(); |
||
414 | String cachedPath = cached.getParentFile().getCanonicalPath(); |
||
415 | String cacheRoot = cacheDir.getCanonicalPath(); |
||
416 | |||
417 | fileName = fileName.substring(0, fileName.length() - fileEnd.length()); |
||
418 | |||
419 | if(!cacheRoot.equals(cachedPath)) |
||
420 | if(!cacheRoot.endsWith(File.separator)) cacheRoot += File.separator; |
||
421 | |||
422 | return new File(imagesRoot, cachedPath.substring(cacheRoot.length()) |
||
423 | + File.separator + fileName); |
||
424 | } |
||
425 | |||
426 | protected File getCacheFileDir(File imageFile) |
||
427 | throws IOException |
||
428 | { |
||
429 | String imagePath = imageFile.getParentFile().getCanonicalPath(); |
||
430 | String rootPath = imagesRoot.getCanonicalPath(); |
||
431 | |||
432 | if(imagePath.equals(rootPath)) return cacheDir; |
||
433 | if(!rootPath.endsWith(File.separator)) rootPath += File.separator; |
||
434 | |||
435 | if(!imagePath.startsWith(rootPath)) |
||
436 | throw new RuntimeException("Image " + imageFile.getCanonicalPath() |
||
437 | + " is not under images root " + imagesRoot.getCanonicalPath()); |
||
438 | |||
439 | return new File(cacheDir, imagePath.substring(rootPath.length())); |
||
440 | } |
||
441 | |||
442 | public void writeSmall(File imageFile, OutputStream out) |
||
443 | throws IOException |
||
444 | { |
||
445 | if(logger.isInfoEnabled()) |
||
446 | logger.info("write small " + imageFile.getCanonicalPath()); |
||
447 | |||
448 | if(writeCached(smallCache, imageFile, out)) return; |
||
449 | |||
1074 | dev | 450 | if(nativeMode) { |
451 | File dir = getCacheFileDir(imageFile); |
||
452 | File cacheFile = getCacheFile(dir, imageFile, SMALL_SUFFIX); |
||
936 | dev | 453 | |
1074 | dev | 454 | createThumbnailNative(dir, cacheFile, imageFile, smallWidth, smallHeight); |
455 | loadCacheInfo(cacheFile, imageFile, smallCache); |
||
456 | writeCached(smallCache, imageFile, out); |
||
457 | } |
||
458 | else { |
||
459 | BufferedImage small = createThumbnail(imageFile, smallWidth, smallHeight); |
||
936 | dev | 460 | |
1074 | dev | 461 | if(small != null) { |
462 | // a thumbnail returned - save it into the cache dir |
||
463 | int sizes[] = calcSizes( |
||
464 | small.getWidth(null), small.getHeight(null), smallWidth, smallHeight); |
||
465 | cacheThumbnail( |
||
466 | smallCache, imageFile, small, SMALL_SUFFIX, sizes[0], sizes[1]); |
||
467 | |||
468 | if(out != null) ImageIO.write(small, format, out); |
||
469 | } |
||
470 | } |
||
936 | dev | 471 | } |
472 | |||
473 | public void writeMedium(File imageFile, OutputStream out) |
||
474 | throws IOException |
||
475 | { |
||
476 | if(logger.isInfoEnabled()) |
||
477 | logger.info("write medium " + imageFile.getCanonicalPath()); |
||
478 | |||
479 | if(writeCached(mediumCache, imageFile, out)) return; |
||
480 | |||
1074 | dev | 481 | if(nativeMode) { |
482 | File dir = getCacheFileDir(imageFile); |
||
483 | File cacheFile = getCacheFile(dir, imageFile, MEDIUM_SUFFIX); |
||
936 | dev | 484 | |
1074 | dev | 485 | createThumbnailNative(dir, cacheFile, imageFile, mediumWidth, mediumHeight); |
486 | loadCacheInfo(cacheFile, imageFile, mediumCache); |
||
487 | writeCached(mediumCache, imageFile, out); |
||
488 | } |
||
489 | else { |
||
490 | BufferedImage medium = createThumbnail(imageFile, mediumWidth, mediumHeight); |
||
936 | dev | 491 | |
1074 | dev | 492 | if(medium != null) { |
493 | // a image returned - save it into the cache dir |
||
494 | int sizes[] = calcSizes(medium.getWidth(null), medium.getHeight(null), |
||
495 | mediumWidth, mediumHeight); |
||
496 | cacheThumbnail( |
||
497 | mediumCache, imageFile, medium, MEDIUM_SUFFIX, sizes[0], sizes[1]); |
||
498 | |||
499 | if(out != null) ImageIO.write(medium, format, out); |
||
500 | } |
||
501 | } |
||
936 | dev | 502 | } |
503 | |||
504 | public void writeDir(File dir, OutputStream out) |
||
505 | throws IOException |
||
506 | { |
||
507 | if(logger.isInfoEnabled()) |
||
508 | logger.info("write dir " + dir.getCanonicalPath()); |
||
509 | |||
510 | if(writeCached(dirCache, dir, out)) return; |
||
511 | |||
512 | BufferedImage thumbnail = createDirThumbnail(dir); |
||
513 | |||
514 | if(thumbnail != null) { |
||
515 | if(dirTemplateSize == null |
||
516 | || dirTemplateTimestamp != dirTemplate.lastModified()) |
||
517 | { |
||
518 | dirTemplateSize = getOriginSize(dirTemplate); |
||
519 | dirTemplateTimestamp = dirTemplate.lastModified(); |
||
520 | } |
||
521 | |||
522 | cacheThumbnail(dirCache, dir, thumbnail, DIR_SUFFIX, |
||
523 | dirTemplateSize[0], dirTemplateSize[1]); |
||
524 | |||
525 | if(out != null) ImageIO.write(thumbnail, format, out); |
||
526 | } |
||
527 | } |
||
528 | |||
529 | synchronized protected BufferedImage createThumbnail(File imageFile, |
||
530 | int width, int height) |
||
531 | throws IOException |
||
532 | { |
||
533 | if(logger.isDebugEnabled()) |
||
534 | logger.debug("create thumbnail " + imageFile.getCanonicalPath()); |
||
535 | |||
536 | Image image = loadImage(imageFile.getCanonicalPath()); |
||
537 | // = ImageIO.read(imageFile); |
||
538 | int[] sizes; |
||
539 | |||
540 | if(image == null) { // not supported format |
||
541 | logger.warn("unsupported format for origin or operation interrupted"); |
||
542 | |||
543 | return null; |
||
544 | } |
||
545 | else { |
||
546 | sizes = calcSizes(image.getWidth(null), image.getHeight(null), |
||
547 | width, height); |
||
548 | logger.debug("resize to " + sizes[0] + "x" + sizes[1]); |
||
549 | |||
550 | return resizer.resize(image, sizes[0], sizes[1]); |
||
551 | } |
||
552 | } |
||
553 | |||
1074 | dev | 554 | synchronized protected BufferedImage createThumbnailNative(File dir, File cacheFile, |
555 | File imageFile, int width, int height) |
||
556 | throws IOException |
||
557 | { |
||
558 | if(logger.isDebugEnabled()) |
||
559 | logger.debug("create thumbnail2 " + imageFile.getCanonicalPath() + " to " |
||
560 | + cacheFile.getCanonicalPath()); |
||
561 | |||
562 | dir.mkdirs(); |
||
563 | |||
564 | // FIMXE: 1) make util path (and params?) configurable |
||
565 | Process process = Runtime.getRuntime().exec(new String[] { |
||
566 | "/usr/local/bin/convert", |
||
567 | "-size", width + "x" + height, |
||
568 | "-thumbnail", width + "x" + height, |
||
569 | imageFile.getCanonicalPath(), |
||
570 | cacheFile.getCanonicalPath() |
||
571 | }); |
||
572 | |||
573 | // FIXME make it finner |
||
574 | BufferedReader in = new BufferedReader(new InputStreamReader(process.getErrorStream())); |
||
575 | String line; |
||
576 | while((line = in.readLine()) != null) { |
||
577 | System.out.println("EXEC: " + line); |
||
578 | } |
||
579 | |||
580 | try { |
||
581 | int res = process.waitFor(); |
||
582 | |||
583 | if(logger.isDebugEnabled()) |
||
584 | logger.debug("process exited with result " + res); |
||
585 | } |
||
586 | catch(InterruptedException ex) { |
||
587 | logger.debug("process interrupted"); |
||
588 | } |
||
589 | |||
590 | return null; |
||
591 | } |
||
592 | |||
936 | dev | 593 | synchronized protected BufferedImage createDirThumbnail(File dir) |
594 | throws IOException |
||
595 | { |
||
1074 | dev | 596 | long timeStart = System.currentTimeMillis(); |
597 | |||
936 | dev | 598 | if(logger.isDebugEnabled()) |
599 | logger.debug("create dir thumbnail " + dir.getCanonicalPath()); |
||
600 | |||
601 | Image template = loadImage(dirTemplate.getCanonicalPath()); |
||
602 | BufferedImage dirThumbnail; |
||
603 | Graphics graphics; |
||
604 | int count; |
||
605 | File[] firstFiles; |
||
606 | |||
607 | if(template == null) { // not supported format |
||
608 | logger.warn("unsupported format for template or operation interrupted"); |
||
609 | |||
610 | return null; |
||
611 | } |
||
612 | |||
613 | dirThumbnail = createBufferedImage(template); |
||
614 | |||
615 | graphics = dirThumbnail.getGraphics(); |
||
616 | count = dirThumbnailPositions.length; |
||
617 | firstFiles = new File[count]; |
||
618 | count = getFirstFiles(dir, count, firstFiles, 0); |
||
619 | |||
620 | for(int i = 0; i < count; i++) { |
||
1074 | dev | 621 | Image image; |
622 | BufferedImage thumbnail = null; |
||
623 | int[] sizes; |
||
936 | dev | 624 | |
1074 | dev | 625 | if(nativeMode) { |
626 | File cacheFileDir = getCacheFileDir(firstFiles[i]); |
||
627 | File cacheFile = getCacheFile(cacheFileDir, firstFiles[i], DIR_PART_SUFFIX); |
||
628 | createThumbnailNative(cacheFileDir, cacheFile, firstFiles[i], |
||
629 | dirThumbnailPositions[i].getWidth(), dirThumbnailPositions[i].getHeight()); |
||
630 | image = loadImage(cacheFile.getCanonicalPath()); |
||
631 | thumbnail = createBufferedImage(image); |
||
632 | } |
||
633 | else { |
||
634 | image = loadImage(firstFiles[i].getCanonicalPath()); |
||
635 | } |
||
636 | |||
936 | dev | 637 | if(image == null) { // not supported format |
638 | logger.warn("unsupported format for origin or operation interrupted"); |
||
639 | |||
640 | return null; |
||
641 | } |
||
642 | else { |
||
643 | sizes = calcSizes(image.getWidth(null), image.getHeight(null), |
||
644 | dirThumbnailPositions[i].getWidth(), |
||
645 | dirThumbnailPositions[i].getHeight()); |
||
1074 | dev | 646 | } |
936 | dev | 647 | |
1074 | dev | 648 | if(!nativeMode) { |
936 | dev | 649 | thumbnail = resizer.resize(image, sizes[0], sizes[1]); |
650 | } |
||
1074 | dev | 651 | |
652 | graphics.drawImage(thumbnail, |
||
653 | getXPosition(dirThumbnailPositions[i].getX(), |
||
654 | dirThumbnailPositions[i].getWidth(), sizes[0], |
||
655 | dirThumbnailPositions[i].getHorAlign()), |
||
656 | getYPosition(dirThumbnailPositions[i].getY(), |
||
657 | dirThumbnailPositions[i].getHeight(), sizes[1], |
||
658 | dirThumbnailPositions[i].getVertAlign()), |
||
659 | null); |
||
936 | dev | 660 | } |
661 | |||
1074 | dev | 662 | if(logger.isDebugEnabled()) { |
663 | logger.debug("dir thumbnail created in " |
||
664 | + (System.currentTimeMillis() - timeStart) + " ms"); |
||
665 | } |
||
666 | |||
936 | dev | 667 | return dirThumbnail; |
668 | } |
||
669 | |||
670 | protected Image loadImage(String fileName) |
||
671 | { |
||
1074 | dev | 672 | long timeStart = System.currentTimeMillis(); |
673 | |||
936 | dev | 674 | // FIXME: probably toolbox reads an image not by every request but |
675 | // caches it |
||
676 | Toolkit toolkit = Toolkit.getDefaultToolkit(); |
||
677 | Image image = toolkit.getImage(fileName); |
||
678 | |||
679 | toolkit.prepareImage(image, -1, -1, null); |
||
680 | |||
681 | while(true) { |
||
682 | int status = toolkit.checkImage(image, -1, -1, null); |
||
683 | |||
684 | if((status & ImageObserver.ALLBITS) != 0) break; |
||
685 | if((status & ImageObserver.ERROR) != 0) return null; |
||
686 | |||
687 | try { |
||
688 | Thread.sleep(100); |
||
689 | } |
||
690 | catch(Exception ex) { |
||
691 | return null; |
||
692 | } |
||
693 | } |
||
694 | |||
1074 | dev | 695 | if(logger.isDebugEnabled()) { |
696 | logger.debug("image " + fileName + " loaded in " |
||
697 | + (System.currentTimeMillis() - timeStart) + " ms"); |
||
698 | } |
||
699 | |||
936 | dev | 700 | return image; |
701 | } |
||
702 | |||
703 | protected int[] calcSizes(int width, int height, int maxWidth, int maxHeight) |
||
704 | { |
||
705 | int[] result = new int[2]; |
||
706 | double xRate; |
||
707 | double yRate; |
||
708 | |||
709 | if(width == 0 || height == 0) { |
||
710 | result[0] = 0; |
||
711 | result[1] = 0; |
||
712 | return result; |
||
713 | } |
||
714 | |||
715 | xRate = (double)maxWidth / (double)width; |
||
716 | yRate = (double)maxHeight / (double)height; |
||
717 | if(xRate >= 1.0 || yRate >= 1.0) { |
||
718 | result[0] = width; |
||
719 | result[1] = height; |
||
720 | } |
||
721 | else if(xRate > yRate) { |
||
722 | result[0] = maxHeight * width / height; |
||
723 | result[1] = maxHeight; |
||
724 | } |
||
725 | else { |
||
726 | result[0] = maxWidth; |
||
727 | result[1] = maxWidth * height / width; |
||
728 | } |
||
729 | |||
730 | return result; |
||
731 | } |
||
732 | |||
733 | protected int getXPosition(int left, int maxWidth, int width, int align) |
||
734 | { |
||
735 | if(align == ThumbnailPosition.ALIGN_HOR_LEFT) |
||
736 | return left; |
||
737 | else if(align == ThumbnailPosition.ALIGN_HOR_RIGHT) |
||
738 | return left + (maxWidth - width); |
||
739 | else if(align == ThumbnailPosition.ALIGN_HOR_CENTER) |
||
740 | return left + (maxWidth - width) / 2; |
||
741 | else |
||
742 | throw new RuntimeException("Unknown align type: " + align); |
||
743 | } |
||
744 | |||
745 | protected int getYPosition(int top, int maxHeight, int height, int align) |
||
746 | { |
||
747 | if(align == ThumbnailPosition.ALIGN_VERT_TOP) |
||
748 | return top; |
||
749 | else if(align == ThumbnailPosition.ALIGN_VERT_BOTTOM) |
||
750 | return top + (maxHeight - height); |
||
751 | else if(align == ThumbnailPosition.ALIGN_VERT_CENTER) |
||
752 | return top + (maxHeight - height) / 2; |
||
753 | else |
||
754 | throw new RuntimeException("Unknown align type: " + align); |
||
755 | } |
||
756 | |||
757 | protected int getFirstFiles(File dir, int count, File[] files, int pos) |
||
758 | { |
||
759 | File[] children; |
||
760 | |||
761 | if(imagesFilter == null) |
||
762 | children = dir.listFiles(); |
||
763 | else |
||
764 | children = dir.listFiles(imagesFilter); |
||
765 | |||
766 | if(children == null) return 0; // the dir does not exists |
||
767 | |||
768 | Arrays.sort(children, fileNameComparatorRev); |
||
769 | |||
1254 | dev | 770 | for(int i = 0; i < children.length && pos < count; i++) { |
936 | dev | 771 | if(children[i].isDirectory()) |
772 | ; //pos = getFirstFiles(children[i], count, files, pos); |
||
773 | else { |
||
774 | files[pos++] = children[i]; |
||
775 | } |
||
776 | } |
||
777 | |||
778 | return pos; |
||
779 | } |
||
780 | |||
781 | protected BufferedImage createBufferedImage(Image image) |
||
782 | { |
||
783 | if(image == null) return null; |
||
784 | |||
785 | int width = image.getWidth(null); |
||
786 | int height = image.getHeight(null); |
||
787 | |||
788 | if(width < 1 || height < 1) return null; |
||
789 | |||
790 | // get pixels |
||
791 | int[] pixels = new int[width * height]; |
||
792 | PixelGrabber pg = new PixelGrabber(image, |
||
793 | 0, 0, width, height, pixels, 0, width); |
||
794 | |||
795 | try { |
||
796 | pg.grabPixels(); |
||
797 | } |
||
798 | catch(InterruptedException e) { |
||
799 | return null; |
||
800 | } |
||
801 | |||
802 | if((pg.getStatus() & ImageObserver.ABORT) != 0) return null; |
||
803 | |||
804 | // create buffered image |
||
805 | BufferedImage buffered = new BufferedImage(width, height, |
||
806 | BufferedImage.TYPE_INT_RGB); |
||
807 | |||
808 | for(int y = 0; y < height; y++) { |
||
809 | for(int x = 0; x < width; x++) |
||
810 | buffered.setRGB(x, y, pixels[y * width + x]); |
||
811 | } |
||
812 | |||
813 | return buffered; |
||
814 | } |
||
815 | |||
816 | public ImageResizer getResizer() |
||
817 | { |
||
818 | return resizer; |
||
819 | } |
||
820 | |||
821 | public void setResizer(ImageResizer resizer) |
||
822 | { |
||
823 | this.resizer = resizer; |
||
824 | } |
||
825 | |||
826 | public String getFormat() |
||
827 | { |
||
828 | return format; |
||
829 | } |
||
830 | |||
831 | public void setFormat(String format) |
||
832 | { |
||
833 | this.format = format; |
||
834 | } |
||
835 | |||
836 | public File getCacheDir() |
||
837 | { |
||
838 | return cacheDir; |
||
839 | } |
||
840 | |||
841 | public void setCacheDir(File dir) |
||
842 | { |
||
843 | this.cacheDir = dir; |
||
844 | } |
||
845 | |||
846 | public File getImagesRoot() |
||
847 | { |
||
848 | return imagesRoot; |
||
849 | } |
||
850 | |||
851 | public void setImagesRoot(File dir) |
||
852 | { |
||
853 | this.imagesRoot = dir; |
||
854 | } |
||
855 | |||
856 | public int getSmallWidth() |
||
857 | { |
||
858 | return smallWidth; |
||
859 | } |
||
860 | |||
861 | public void setSmallWidth(int width) |
||
862 | { |
||
863 | this.smallWidth = width; |
||
864 | } |
||
865 | |||
866 | public int getSmallHeight() |
||
867 | { |
||
868 | return smallHeight; |
||
869 | } |
||
870 | |||
871 | public void setSmallHeight(int height) |
||
872 | { |
||
873 | this.smallHeight = height; |
||
874 | } |
||
875 | |||
876 | public int getMediumWidth() |
||
877 | { |
||
878 | return mediumWidth; |
||
879 | } |
||
880 | |||
881 | public void setMediumWidth(int width) |
||
882 | { |
||
883 | this.mediumWidth = width; |
||
884 | } |
||
885 | |||
886 | public int getMediumHeight() |
||
887 | { |
||
888 | return mediumHeight; |
||
889 | } |
||
890 | |||
891 | public void setMediumHeight(int height) |
||
892 | { |
||
893 | this.mediumHeight = height; |
||
894 | } |
||
895 | |||
896 | public FileFilter getImagesFilter() |
||
897 | { |
||
898 | return imagesFilter; |
||
899 | } |
||
900 | |||
901 | public void setImagesFilter(FileFilter filter) |
||
902 | { |
||
903 | this.imagesFilter = filter; |
||
904 | } |
||
905 | |||
906 | public File getDirTemplate() |
||
907 | { |
||
908 | return dirTemplate; |
||
909 | } |
||
910 | |||
911 | public void setDirTemplate(File dirTemplate) |
||
912 | { |
||
913 | this.dirTemplate = dirTemplate; |
||
914 | } |
||
915 | |||
916 | public ThumbnailPosition[] getDirThumbnailPositions() |
||
917 | { |
||
918 | return dirThumbnailPositions; |
||
919 | } |
||
920 | |||
921 | public void setDirThumbnailPositions( |
||
922 | ThumbnailPosition[] dirThumbnailPositions) |
||
923 | { |
||
924 | this.dirThumbnailPositions = dirThumbnailPositions; |
||
925 | } |
||
926 | } |