/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.base.explorer;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.net.URI;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.SubsampleAverageDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.base.explorer.list.IThumbnailList;
import org.weasis.base.explorer.list.IThumbnailModel;
import org.weasis.core.api.gui.util.GuiExecutor;
import org.weasis.core.api.image.util.ImageFiler;
import org.weasis.core.api.media.data.ImageElement;
import org.weasis.core.api.media.data.Thumbnail;
import org.weasis.core.api.util.ThreadUtil;
public final class JIThumbnailCache {
private static final Logger LOGGER = LoggerFactory.getLogger(JIThumbnailCache.class);
private static final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Set only one concurrent thread. The time consuming part is in loading image thread (see ImageElement)
private static final ExecutorService qExecutor =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, queue, ThreadUtil.getThreadFactory("Thumbnail Cache")); //$NON-NLS-1$
private static final JIThumbnailCache instance = new JIThumbnailCache();
private final Map<URI, ThumbnailIcon> cachedThumbnails;
private JIThumbnailCache() {
this.cachedThumbnails = Collections.synchronizedMap(new LinkedHashMap<URI, ThumbnailIcon>(80) {
private static final long serialVersionUID = 5981678679620794224L;
private static final int MAX_ENTRIES = 100;
@Override
@SuppressWarnings("rawtypes")
protected boolean removeEldestEntry(final Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
});
}
public static JIThumbnailCache getInstance() {
return instance;
}
public synchronized void invalidate() {
this.cachedThumbnails.clear();
}
public static void removeInQueue(ImageElement imgElement) {
Runnable r = null;
for (Runnable runnable : queue) {
if (Objects.equals(imgElement, ((ThumbnailRunnable) runnable).getDiskObject())) {
r = runnable;
}
}
if (r != null) {
queue.remove(r);
}
}
public ThumbnailIcon getThumbnailFor(final ImageElement diskObject, final IThumbnailList aThumbnailList, final int index) {
try {
final ThumbnailIcon jiIcon = this.cachedThumbnails.get(diskObject.getMediaURI());
if (jiIcon != null) {
return jiIcon;
}
} catch (final Exception e) {
LOGGER.error("", e); //$NON-NLS-1$
}
if (!diskObject.isLoading()) {
loadThumbnail(diskObject, aThumbnailList, index);
}
return null;
}
private static void loadThumbnail(final ImageElement diskObject, final IThumbnailList thumbnailList,
final int index) {
if ((index > thumbnailList.getLastVisibleIndex()) || (index < thumbnailList.getFirstVisibleIndex())) {
return;
}
for (Runnable runnable : queue) {
if (diskObject.equals(((ThumbnailRunnable) runnable).getDiskObject())) {
return;
}
}
cleanPending();
ThumbnailRunnable runnable = new ThumbnailRunnable(diskObject, thumbnailList, index);
JIThumbnailCache.qExecutor.execute(runnable);
}
private static void cleanPending() {
for (Runnable runnable : queue) {
ThumbnailRunnable r = (ThumbnailRunnable) runnable;
int index = r.getIndex();
if ((index > r.getThumbnailList().getLastVisibleIndex())
|| (index < r.getThumbnailList().getFirstVisibleIndex())) {
JIThumbnailCache.removeInQueue(r.getDiskObject());
}
}
}
static class ThumbnailRunnable implements Runnable {
final ImageElement diskObject;
final IThumbnailList thumbnailList;
final int index;
final IThumbnailModel modelList;
public ThumbnailRunnable(ImageElement diskObject, IThumbnailList thumbnailList, int index) {
this.diskObject = diskObject;
this.thumbnailList = thumbnailList;
this.index = index;
this.modelList = thumbnailList.getThumbnailListModel();
}
public ImageElement getDiskObject() {
return diskObject;
}
public IThumbnailList getThumbnailList() {
return thumbnailList;
}
public int getIndex() {
return index;
}
public IThumbnailModel getModelList() {
return modelList;
}
@Override
public void run() {
RenderedImage img = null;
// Get the final that contain the thumbnail when the uncompress mode is activated
File file = diskObject.getFile();
if (file != null) {
img = ImageFiler.getThumbnailInTiff(file);
}
if (img == null) {
img = diskObject.getRenderedImage(diskObject.getImage(null));
}
if (img == null) {
return;
}
final double scale = Math.min(ThumbnailRenderer.ICON_DIM.height / (double) img.getHeight(),
ThumbnailRenderer.ICON_DIM.width / (double) img.getWidth());
final BufferedImage tIcon =
scale <= 1.0
? scale > 0.005 ? SubsampleAverageDescriptor
.create(img, scale, scale, Thumbnail.DownScaleQualityHints).getAsBufferedImage() : null
: PlanarImage.wrapRenderedImage(img).getAsBufferedImage();
// Prevent to many files open on Linux (Ubuntu => 1024) and close image stream
diskObject.removeImageFromCache();
GuiExecutor.instance().execute(() -> {
if (tIcon != null) {
getInstance().cachedThumbnails.put(diskObject.getMediaURI(), new ThumbnailIcon(tIcon));
}
thumbnailList.getThumbnailListModel().notifyAsUpdated(index);
});
}
}
}