最近在一个java swing cs项目中有个需求,用户打开文件选择器时,需要预览图片,方便用户操作。用户的图片大部分都是100M一个的tif文件和一些几M一个的jpg文件,java实时压缩较费时,故需要缓存预览图。
本类实现了预览图片生成的图片缓存到本地
硬盘文件中,在项目的ImageCache目录下,用日期yyyy-MM-dd格式生成文件夹,当天的图片缓存文件存放在该文件中,程序每次启动加载缓存前,会将定义好的10天前的图片缓存删除,如果有些图片在被删除前3天有被再次访问,则该缓存文件会移动到当天缓存文件目录中。
首先,JFileChooser默认弹窗太小,SizedFileChooser类实现
自定义大小和位置弹窗
class="java" name="code">import java.awt.Component;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
public class SizedFileChooser extends JFileChooser{
private Point p;
private Dimension d;
public SizedFileChooser(File currentDirectory){
super(currentDirectory);
}
public SizedFileChooser(String currentDirectoryPath){
super(currentDirectoryPath);
}
public SizedFileChooser(){
super();
}
public int showOpenDialog(Component parent) throws HeadlessException{
return showOpenDialog(parent, null, 1000, 600);
}
public int showOpenDialog(Component parent, Point location, int width, int height) throws HeadlessException{
if(location == null){
Dimension dimesion = Toolkit.getDefaultToolkit().getScreenSize();
location = new Point((dimesion.width - width) / 2, (dimesion.height - height) / 2);
}
p = location;
d = new Dimension(width, height);
return super.showOpenDialog(parent);
}
public int showOpenDialog(Component parent, Point location, Dimension size) throws HeadlessException{
p = location;
d = size;
return super.showOpenDialog(parent);
}
public int showSaveDialog(Component parent, Point location, Dimension size) throws HeadlessException{
p = location;
d = size;
return super.showSaveDialog(parent);
}
public int showDialog(Component parent, String approveButtonText, Point location, Dimension size){
p = location;
d = size;
return super.showDialog(parent, approveButtonText);
}
public void getLastLocationAndSize(Point p, Dimension d){
if(p != null){
p.x = this.p.x;
p.y = this.p.y;
}
if(d != null){
d.width = this.d.width;
d.height = this.d.height;
}
}
public void onWindowClose(){
}
protected JDialog createDialog(Component parent) throws HeadlessException{
final JDialog dlg = super.createDialog(parent);
dlg.addComponentListener(new ComponentAdapter(){
public void componentMoved(ComponentEvent e){
p = dlg.getLocation();
}
public void componentResized(ComponentEvent e){
d = dlg.getSize();
}
});
if(p != null){
dlg.setLocation(p);
}
if(d != null && d.width > 0 && d.height > 0){
dlg.setSize(d);
}
dlg.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent wevent){
onWindowClose();
}
});
return dlg;
}
}
实现图片预览类
import java.awt.Component;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateUtils;
import com.anyfinding.bailushi.utils.CommUtils;
import com.anyfinding.bailushi.utils.Config;
import com.anyfinding.bailushi.utils.Const;
import com.anyfinding.bailushi.utils.IM4JUtils;
import com.anyfinding.bailushi.utils.ImageUtils;
public class PhotoSizedFileChooser extends SizedFileChooser{
private static final ImageIcon defaultIcon = new ImageIcon(PhotoSizedFileChooser.class.getClass().getResource("/images/photo_img.png"));
private static final Map<String, ImageCache> imageCacheMap = new ConcurrentHashMap<String, ImageCache>(4096);
private ExecutorService executor = Executors.newFixedThreadPool(Const.PHOTO_THREAD);
private ImagePreviewPanel preview = null;
private Map<File, Boolean> statusMap = new HashMap<File, Boolean>();
/**
* 窗口关闭时一些操作
*/
private void release(){
if(executor != null){
executor.shutdownNow();
executor = null;
}
preview = null;
//窗口关闭前,图片没有被预览完成,需要删除
for(Entry<File, Boolean> entry : statusMap.entrySet()){
if(!entry.getValue()){
imageCacheMap.remove(entry.getKey());
}
}
statusMap = null;
}
public void onWindowClose(){
release();
}
public void approveSelection(){
super.approveSelection();
release();
}
public void cancelSelection(){
super.cancelSelection();
release();
}
/**
* 加载缓存
*/
public static void loadImageCache(){
try{
File file = new File("./ImageCache");
if(file != null && file.exists()){
new File(file.getAbsolutePath() + File.separator + CommUtils.dateToShortString(new Date())).mkdir();
//删除过期缓存
for(File f : file.listFiles()){
if(!f.getName().contains("svn") && DateUtils.addDays(CommUtils.stringToDate(f.getName()), Config.imageCacheDay).before(new Date())){
FileUtils.deleteDirectory(f);
}
}
//加载缓存
for(File f : file.listFiles()){
if(!f.getName().contains("svn")){
for(File cacheFile : f.listFiles()){
if(!cacheFile.getName().contains("svn")){
ImageIcon icon = new ImageIcon(ImageIO.read(cacheFile));
imageCacheMap.put(cacheFile.getName(), new ImageCache(icon, cacheFile.getAbsolutePath()));
}
}
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
private static String getFileString(File file){
String name = file.getAbsolutePath();
name = name.replaceAll("/", "_");
name = name.replaceAll("//", "_");
name = name.replaceAll("\\\\", "_");
name = name.replaceAll(":", "");
return name.toLowerCase() + "_" + file.length() + "_" + file.lastModified();
}
public PhotoSizedFileChooser(String currentDirectoryPath){
super(currentDirectoryPath);
this.setFileSelectionMode(JFileChooser.FILES_ONLY);
this.setMultiSelectionEnabled(true);
this.setDialogTitle("请选择图片文件");
this.setFileFilter(new PhotoFileFilter());
preview = new ImagePreviewPanel();
this.setAccessory(preview);
this.addPropertyChangeListener(preview);
this.addPropertyChangeListener(new PropertyChangeListener(){
public void propertyChange(PropertyChangeEvent evt){
//每次切换目录,结束图片预览线程
if(JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(evt.getPropertyName())){
if(preview != null){
preview.clear();
}
if(executor != null){
executor.shutdownNow();
executor = null;
}
executor = Executors.newFixedThreadPool(Const.PHOTO_THREAD);
}
}
});
this.setFileView(new ThumbnailView());
}
public int showOpenDialog(Component parent) throws HeadlessException{
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int width = d.width - 80;
int height = d.height - 70;
Point p = new Point((d.width - width) / 2, 20);
return showOpenDialog(parent, p, width, height);
}
private class ThumbnailView extends FileView{
public Icon getIcon(File file){
if(ImageUtils.isImage(file)){
String fileString = getFileString(file);
ImageCache cache = imageCacheMap.get(fileString);
ImageIcon icon = null;
if(cache == null){
icon = new ImageIcon(defaultIcon.getImage());
imageCacheMap.put(fileString, new ImageCache(icon, file.getAbsolutePath()));
statusMap.put(file, false);
executor.submit(new ThumbnailIconLoader(icon, file));
}else{
icon = cache.getImageIcon();
try{
String cacheDate = cache.getCacheDate();
//3天后就要过期的图片,如果还被再次访问,则把该缓存文件移动到当天缓存目录
if(Config.imageCacheDay > 3 && CommUtils.isDate(cacheDate) && DateUtils.addDays(CommUtils.stringToDate(cacheDate), Config.imageCacheDay - 3).before(new Date())){
File newFile = new File("./ImageCache" + File.separator + CommUtils.dateToShortString(new Date()) + File.separator + fileString);
FileUtils.moveFile(new File(cache.getImagePath()), newFile);
imageCacheMap.put(fileString, new ImageCache(icon, newFile.getAbsolutePath()));
}
}catch(Exception e){
e.printStackTrace();
}
}
return icon;
}else{
FileSystemView sv = FileSystemView.getFileSystemView();
if(sv != null){
return sv.getSystemIcon(file);
}
return super.getIcon(file);
}
}
}
private class ThumbnailIconLoader implements Runnable{
private final ImageIcon icon;
private final File file;
private BufferedImage bgImage = null;
private BufferedImage noPreviewImage = null;
public ThumbnailIconLoader(ImageIcon i, File f){
icon = i;
file = f;
}
public void run(){
try{
if(bgImage == null){
bgImage = ImageIO.read(this.getClass().getResource("/images/subject_bg.jpg"));
}
int size = defaultIcon.getIconWidth() > defaultIcon.getIconHeight() ? defaultIcon.getIconWidth() : defaultIcon.getIconHeight();
BufferedImage image = IM4JUtils.fileToScaledBufferedImage(file, size, size);
if(image != null){
//bgImage是一个60x60的白色背景图,把预览的图片合并在一起,保存每个预览图片都是正方形,比较整齐
image = Thumbnails.of(bgImage).scale(1).watermark(Positions.CENTER, image, 1.0f).outputQuality(0.5f).asBufferedImage();
File outputfile = new File("./ImageCache" + File.separator + CommUtils.dateToShortString(new Date()) + File.separator + getFileString(file));
ImageIO.write(image, "jpg", outputfile);
}else{
//预览失败的图片
if(noPreviewImage == null){
noPreviewImage = ImageIO.read(this.getClass().getResource("/images/no_preview2.png"));
}
image = noPreviewImage;
}
icon.setImage(image);
statusMap.put(file, true);
SwingUtilities.invokeLater(new Runnable(){
public void run(){
repaint();
}
});
}catch(Exception e){
e.printStackTrace();
}
}
}
}
点击预览图,右图出大图预览
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import com.anyfinding.bailushi.utils.IM4JUtils;
import com.anyfinding.bailushi.utils.ImageUtils;
public class ImagePreviewPanel extends JPanel implements PropertyChangeListener{
private ImageIcon icon;
private Image image;
private static final int ACCSIZE = 250;
private Color bg;
public ImagePreviewPanel(){
setPreferredSize(new Dimension(ACCSIZE, -1));
bg = getBackground();
}
public void clear(){
image = null;
repaint();
}
public void propertyChange(PropertyChangeEvent e){
String propertyName = e.getPropertyName();
if(propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)){
File file = (File)e.getNewValue();
if(file == null){
return;
}
if(ImageUtils.isImage(file)){
BufferedImage bufferedImage = IM4JUtils.fileToScaledBufferedImage(file, ACCSIZE, ACCSIZE);
if(bufferedImage != null){
icon = new ImageIcon(bufferedImage);
}else{
icon = new ImageIcon(getClass().getResource("/images/no_preview.png"));
}
image = icon.getImage();
scaleImage();
repaint();
}
}
}
private void scaleImage(){
int width = image.getWidth(this);
int height = image.getHeight(this);
if(height > width){
height = (int)(height * (ACCSIZE * 1.0D / width));
if(height > this.getSize().height){
height = this.getSize().height;
}
width = ACCSIZE;
image = image.getScaledInstance(width, height, Image.SCALE_FAST);
}
}
public void paintComponent(Graphics g){
g.setColor(bg);
g.fillRect(0, 0, ACCSIZE, getHeight());
g.drawImage(image, 2, 2, this);
}
}
程序启动后,马上
在线程中加载图片缓存
Base.cacheService.submit(new Runnable(){
public void run(){
PhotoSizedFileChooser.loadImageCache();
}
});
其它东西
因为图片大部分都是100M的tif和几M的jpg,所以图片压缩使用GraphicsMagick+im4j
有一些特别的tif,GraphicsMagick+im4j压缩出来后,生成BufferedImage会报错,
需要使用JAI包生成BufferedImage,JAI压缩大图片比GraphicsMagick+im4j慢6到12倍
图片合并使用的是Thumbnails的水印功能
- 大小: 301.1 KB