主要功能就是可以浏览筛选的图片(可以拖拽放大),批量重命名,改尺寸,常见格式等,监听键盘事件可以通过方向键切换图片,空格键自动播放,delete键等方便超控一些功能。下载地址:源码和打包的.exe(直接运行)程序。
预览
依赖
<!-- swing窗体美化包 -->
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.1</version>
</dependency>
<!-- 图片处理工具类 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.19</version>
</dependency>
新建一个Swing GUI窗体,设计需要的布局内容。from布局和源码可以去前面下,下面只贴出部分代码内容。
筛选图片按钮功能,调用自带的 JFileChooser窗口(文件选择器)进行文件选择,处理分为单选和多选,对选中的文件夹进行递归,将所有jpg,png,jpeg 格式图片统计到arrImageList集合。
/**
* 筛选图片
*/
private void scanPath() {
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("筛选图片");
chooser.setCurrentDirectory(new File(originalPath));
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.setMultiSelectionEnabled(true);
设置文件过滤器(可选择的文件类型):默认所有文件
chooser.setFileFilter();//设置默认使用的文件过滤器
添加 文件过滤器
chooser.addChoosableFileFilter(
new FileNameExtensionFilter("zip(*.txt, *.rar, *.zip)", "txt", "rar", "zip"));//文件过滤器
chooser.addChoosableFileFilter(
new FileNameExtensionFilter("image(*.jpg,*.png,.gif)", "jpeg", "jpg", "png", "gif"));
chooser.setApproveButtonText("选择");
int state = chooser.showOpenDialog(null);
if (state == JFileChooser.APPROVE_OPTION) {
// 获取选择的文件流
pathFiles = chooser.getSelectedFiles();
scanPath(pathFiles);
}
imageHome.requestFocus();
}
/**
* 筛选图片
*/
private void scanPath(File[] files) {
arrImageList = new ArrayList<>();
arrPathList = new ArrayList<>();
if (files.length == 1) {
// 单选
textPrefix.setText(files[0].getName());
}
for (File f : files) {
if (f.isDirectory()) {
// 文件夹时,递归查询子文件里的图片
lookOverFile(f);
arrPathList.add(f.getAbsolutePath());
} else {
final String[] split = f.getName().split("\\.");
final String strType = split[split.length - 1].toLowerCase();
if ("jpg".equals(strType)
|| "png".equals(strType)
|| "jpeg".equals(strType)) {
arrImageList.add(f.getAbsolutePath());
}
}
}
StringBuilder sb = new StringBuilder();
for (String s : arrImageList) {
s = s.replace(originalPath, "");
sb.append(s).append("\n");
}
textArea1.setText(sb.toString());
nCountImgsNum = arrImageList.size();
ladelMessage.setText("-- 合计总数: \t " + nCountImgsNum);
nIndex = 0;
spinIndex.setValue(nIndex);
initSetSpin();
sliderIndex.setValue(nIndex);
sliderIndex.setMaximum(nCountImgsNum);
isSaveing = false;
btnSaveing.setText("执行转存");
if (isShowImgShow) {
threaPreviewImage();
}
drawImage();
}
/**
* 递归扫描路径文件
*/
public void lookOverFile(File dir) {
File[] subfiles = dir.listFiles();
if (subfiles != null) {
for (File f : subfiles) {
if (f.isDirectory()) {
lookOverFile(f);
arrPathList.add(f.getAbsolutePath());
} else {
final String[] split = f.getName().split("\\.");
final String strType = split[split.length - 1].toLowerCase();
if ("jpg".equals(strType)
|| "png".equals(strType)
|| "jpeg".equals(strType)) {
arrImageList.add(f.getAbsolutePath());
}
}
}
}
}
绘制图片,大致就是用线程池渲染显示图片,若预览图功能开启了,那会有一个另一个线程池预处理前后几张图片,并定期清除过远的预览图。这里就不贴了。
/**
* 上一张
*/
void upShow() {
if (arrImageList == null || nCountImgsNum == 0) {
drawStr(Color.red, "当前图片数量为空!");
imageHome.requestFocus();
return;
}
nIndex = (int) spinIndex.getValue();
spinIndex.setValue(getIndex(nIndex - 1));
drawImage();
imageHome.requestFocus();
}
/**
* 下一张
*/
private void downShow() {
if (arrImageList == null || nCountImgsNum == 0) {
drawStr(Color.red, "当前图片数量为空!");
imageHome.requestFocus();
return;
}
nIndex = (int) spinIndex.getValue();
spinIndex.setValue(getIndex(nIndex + 1));
drawImage();
imageHome.requestFocus();
}
/**
* 渲染显示图片
*/
private void drawImage() {
if (arrImageList == null || nCountImgsNum == 0) {
drawStr(Color.red, "当前图片数量为空!");
return;
}
int indexValue = (int) spinIndex.getValue();
nIndex = indexValue;
sliderIndex.setValue(indexValue);
threaDrawImage(indexValue);
}
/**
* 线程 绘制图片
*/
private void threaDrawImage(int index) {
//创建线程池
ThreadPoolExecutor threadDrawImage = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new SynchronousQueue<>());
//向线程池提交任务 无返回值
int finalIndex = getIndex(index);
threadDrawImage.execute(() -> {
String path = arrImageList.get(finalIndex);
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path))
) {
orgImage = ImageIO.read(bis);
} catch (IOException e) {
e.printStackTrace();
}
drawPictures();
// 绘制预览图
if (isShowImgShow) {
int w = imgShow.getWidth() / 5;
int h = w * 2 / 3;
drawPreviewImage(img1.getGraphics(), finalIndex - 2, w, h, 10);
drawPreviewImage(img2.getGraphics(), finalIndex - 1, w, h, 10);
drawPreviewImage(img3.getGraphics(), finalIndex, w + 20, h + 20, 0);
drawPreviewImage(img4.getGraphics(), finalIndex + 1, w, h, 10);
drawPreviewImage(img5.getGraphics(), finalIndex + 2, w, h, 10);
}
});
//关闭线程池
threadDrawImage.shutdown();
}
/**
* 绘制图片
*/
private void drawPictures() {
final Rectangle rect = drawPictures(orgImage);
scale = rect.getWidth() / orgImage.getWidth();
offsetX = (int) (rect.x / scale);
offsetY = (int) (rect.y / scale);
}
/**
* 绘制图片
*/
private Rectangle drawPictures(BufferedImage image) {
int width = imageShow.getWidth();
int heigth = imageShow.getHeight();
Graphics g = imageShow.getGraphics();
drawImage = new BufferedImage(width, heigth, BufferedImage.TYPE_INT_RGB);
final Graphics2D graphics = drawImage.createGraphics();
graphics.setColor(imageHome.getBackground());
graphics.fillRect(0, 0, drawImage.getWidth(), drawImage.getHeight());
int x = 0;
int y = 0;
if (image != null) {
try {
image = Thumbnails.of(image).size(width, heigth).asBufferedImage();
x = (width - image.getWidth()) / 2;
y = (heigth - image.getHeight()) / 2;
graphics.drawImage(image, x, y, null);
g.drawImage(drawImage, 0, 0, null);
return new Rectangle(x, y, image.getWidth(), image.getHeight());
} catch (IOException e) {
e.printStackTrace();
return new Rectangle();
}
} else {
g.drawImage(drawImage, 0, 0, null);
}
return new Rectangle();
}
/**
* 绘制 预览图片
*/
private void drawPreviewImage(Graphics g, int imgIndex, int width, int heigth, int y) {
BufferedImage images = new BufferedImage(width, heigth, BufferedImage.TYPE_INT_RGB);
final Graphics2D graphics = images.createGraphics();
graphics.setColor(imageHome.getBackground());
graphics.fillRect(0, 0, images.getWidth(), images.getHeight());
int x = 0;
BufferedImage img = hasPreproImage.get(getIndex(imgIndex));
if (img != null) {
try {
img = Thumbnails.of(img).size(width, heigth).asBufferedImage();
x = (width - img.getWidth()) / 2;
graphics.drawImage(img, x, y, null);
g.drawImage(images, 0, 0, null);
} catch (IOException e) {
e.printStackTrace();
}
} else {
g.drawImage(images, 0, 0, null);
}
}
缩放拖拽功能实现,这个监听事件(按下,松开,滚轮等)的逻辑要弄好,具体的缩放拖拽功能有两种实现,第一种就是 利用缩放画布绘制 ,第二种是 计算参数 用 Thumbnails.精确裁剪缩放,第二种画质好些,但是计算参数算法还有点偏差,有改进的建议或者更好的方法可以写评论。
/**
* 图片缩放功能
*/
private void zoomDrawImage(MouseWheelEvent e) {
double ratio = scale;
if (e.getWheelRotation() < 0) {
// 滚轮向上,放大画布
scale *= 1.1;
} else {
// 滚轮向下,缩小画布
scale /= 1.1;
}
/** 调整偏移量 */
offsetX += (int) (e.getX() * (1 / scale - 1 / ratio));
offsetY += (int) (e.getY() * (1 / scale - 1 / ratio));
// 重新绘图
drawImageXY();
}
/**
* 拖放图片
*/
private void moveImage(MouseEvent e) {
if (!isMoveing) {
return;
}
// 统计本次鼠标移动的相对值
int dx = e.getX() - startX;
int dy = e.getY() - startY;
// 偏移量累加
offsetX += (int) (dx / scale);
offsetY += (int) (dy / scale);
// 记录当前拖动后的位置
startX += dx;
startY += dy;
// 重新绘图
drawImageXY();
}
/**
* 绘制图片
* 根据指定的缩放拖拽方式,以线程方式执行
*/
private void drawImageXY() {
//创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//向线程池提交任务 有返回值
executorService.execute(() -> {
if (RadZooming.isSelected()) {
drawImageXY2();
} else {
drawImageXY1();
}
});
//关闭线程池
executorService.shutdown();
}
/**
* 绘制图片 1
* 利用 缩放画布绘制
*/
private void drawImageXY1() {
if (orgImage == null) {
return;
}
Graphics2D g = (Graphics2D) imageShow.getGraphics();
// 缩放画布
g.scale(scale, scale);
// 拖动画布
// g.translate(offsetX, offsetY);
// g.drawImage(image, offsetX, offsetY, null);
// 缓冲图上绘制图片然后绘制至面板
BufferedImage imgs = new BufferedImage((int) (imageShow.getWidth() / scale), (int) (imageShow.getHeight() / scale), BufferedImage.TYPE_INT_RGB);
final Graphics2D g2D = imgs.createGraphics();
g2D.setColor(imageHome.getBackground());
g2D.fillRect(0, 0, imgs.getWidth(), imgs.getHeight());
g2D.drawImage(orgImage, offsetX, offsetY, null);
drawImage = imgs;
g.drawImage(imgs, 0, 0, null);
}
/**
* 绘制图片 2
* 计算参数 用 Thumbnails.精确裁剪缩放
*/
private void drawImageXY2() {
if (orgImage == null) {
return;
}
int drawX = 0;
int drawY = 0;
int clipX = 0;
int clipY = 0;
int clipW = (int) (imageShow.getWidth() / scale);
int clipH = (int) (imageShow.getHeight() / scale);
if (offsetX < 0) {
clipX = -offsetX;
} else {
drawX = (int) (offsetX * scale);
}
if (offsetY < 0) {
clipY = -offsetY;
} else {
drawY = (int) (offsetY * scale);
}
Graphics2D g = (Graphics2D) imageShow.getGraphics();
// 缓冲图上绘制图片然后绘制至面板
BufferedImage images = new BufferedImage(imageShow.getWidth(), imageShow.getHeight(), BufferedImage.TYPE_INT_RGB);
final Graphics2D g2D = images.createGraphics();
g2D.setColor(imageHome.getBackground());
g2D.fillRect(0, 0, images.getWidth(), images.getHeight());
if (clipX < orgImage.getWidth() && clipY < orgImage.getHeight()) {
if (clipW - offsetX > orgImage.getWidth()) {
clipW = orgImage.getWidth() - clipX;
}
if (clipH - offsetY > orgImage.getHeight()) {
clipH = orgImage.getHeight() - clipY;
}
BufferedImage imgs = null;
try {
imgs = Thumbnails.of(orgImage).sourceRegion(clipX, clipY, clipW, clipH).size((int) (clipW * scale), (int) (clipH * scale)).asBufferedImage();
} catch (IOException e) {
e.printStackTrace();
}
g2D.drawImage(imgs, drawX, drawY, null);
}
g.drawImage(images, 0, 0, null);
drawImage = images;
}
键盘监听事件
主要就是让一个布局内容获得焦点,在所有事件里加一句 imageHome.requestFocus();请求焦点。
这里触发的事件就不一一贴代码了
/**
* 键值响应
*/
private void imageShowKeyReleased(int code) {
// System.out.println("键值 : " + code);
/*
45 - ;61 + ;
< 37; ^ 38; > 39 ;v 40;
A 65; W 87;D 68;S 83;
空格 32; 删除 8 ;delete 127;
10 回车
*/
if (code == 27) {
isShowBtnSetup = !isShowBtnSetup;
btnSetup.setVisible(isShowBtnSetup);
if (isShowBtnSetup) {
btnSetup.updateUI();
}
} else if (code == 20) {
isShowImgShow = !isShowImgShow;
chkPreview.setSelected(isShowImgShow);
imgShow.setVisible(isShowImgShow);
if (isShowImgShow) {
imgShow.updateUI();
threaPreviewImage();
}
drawImage();
} else if (code == 37 || code == 38 || code == 65 || code == 87) {
upShow();
} else if (code == 39 || code == 40 || code == 68 || code == 83) {
downShow();
} else if (code == 32) {
threaPlayDrawImage();
} else if (code == 10) {
saveOneImg();
} else if (code == 127) {
dleteOneImg();
} else if (code == 8) {
removeOneImg();
} else if (code == 61) {
sleepTime = sliderSleep.getValue() + 200;
sliderSleep.setValue(Math.toIntExact(Math.min(sleepTime, sliderSleep.getMaximum())));
} else if (code == 45) {
sleepTime = sliderSleep.getValue() - 200;
sliderSleep.setValue(Math.toIntExact(Math.max(sleepTime, sliderSleep.getMinimum())));
}
}