Subversion Repositories general

Rev

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
}