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