Subversion Repositories general

Rev

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