Java 图像处理工具:瞬间加载 + 马赛克 + 旋转 + 亮度调节
【Java GUI 进阶升级版】图像处理工具开发笔记:瞬间加载 / 灰度 / 马赛克 / 旋转 / 翻转 / 亮度调节 / 滚轮缩放(完整源码)
前言
在上一版像素级图像处理工具中,我们实现了图片加载、灰度转换、鼠标马赛克绘制三大基础功能,但受绘制方式限制,图片会逐行逐点慢慢显示,加载速度较慢、体验不够流畅。
在这一版升级版中,我对项目进行了全面架构重构与性能优化,在保留原有核心功能的基础上大幅扩展交互与处理能力:
- 核心优化:图片从逐行绘制升级为瞬间加载显示
- 新增功能:图片90°旋转、左右翻转、上下翻转
- 交互升级:滑块实时调节亮度、滑块调节马赛克大小、鼠标滚轮缩放图片
- 代码优化:新增独立滤镜类,实现界面、逻辑、算法三层解耦
- 体验优化:缩放后马赛克坐标精准不偏移,所有操作实时生效且效果不丢失
整篇笔记依旧保持新手友好、注释详细、可直接运行的风格,非常适合 Java GUI 进阶学习与课程设计使用。
一、功能说明与技术选型
1.1 工具功能
- 界面设计:900×900 居中窗口,BorderLayout 分区布局,功能区与绘图区清晰分离
- 图片加载:支持本地图片读取,瞬间加载显示,不再逐行绘制
- 基础处理:灰度转换、鼠标拖动绘制马赛克
- 几何变换:图片90°旋转、左右翻转、上下翻转
- 参数调节:滑块可视化调节图片亮度、自由调节马赛克块大小
- 视图控制:鼠标滚轮等比例缩放图片,不变形、不拉伸、不卡顿
- 程序健壮:空指针校验、坐标边界保护、缩放兼容处理
1.2 技术栈
- Swing + AWT:界面搭建与绘图基础
- BufferedImage:缓冲图像,实现图片瞬间加载
- 像素 RGB 解析与位运算
- 事件监听:ActionListener、MouseListener、MouseMotionListener、MouseWheelListener
- JSlider:滑块组件,实现参数可视化调节
- 三层架构:界面层 + 逻辑层 + 算法层,代码结构更清晰
1.3 项目结构
image-tool-v2/
├─ PixelUI.java // 界面、布局、组件初始化
├─ PixelListener.java // 事件处理、交互逻辑、绘制控制
└─ FilterImage.java // 旋转、翻转、亮度、像素绘制(纯算法)
二、核心优化:从逐行绘制到瞬间加载
上一版工具使用 drawLine(j,i,j,i) 逐个绘制像素点,图片会肉眼可见地一行一行显示,加载速度慢。
新版优化思路:
- 先在内存中创建
BufferedImage缓冲图像 - 在内存中完成所有像素的绘制与处理
- 最后通过
drawImage()一次性将整张图片绘制到面板
优势:
- 加载速度显著提升,图片瞬间出现
- 缩放、旋转、翻转时无闪烁、更流畅
- 支持鼠标滚轮顺滑缩放
- 整体更接近真实图像处理工具的使用体验
这是本次升级最关键、体验提升最明显的优化点。
三、完整代码实现
3.1 PixelUI.java
package zsq20260122;
import javax.swing.*;
import java.awt.*;
public class PixelUI {
private JPanel drawPanel;
private PixelListener listener;
public void initUI(){
JFrame jf = new JFrame();
jf.setSize(900,900);
jf.setTitle("图像处理1.0");
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(3);
//边框布局
jf.setLayout(new BorderLayout());
//功能面板,放北边
JPanel northPanel=new JPanel();
northPanel.setBackground(Color.lightGray);
northPanel.setPreferredSize(new Dimension(0,40));
jf.add(northPanel,BorderLayout.NORTH);
//画图面板
drawPanel=new JPanel();
drawPanel.setBackground(Color.WHITE);
jf.add(drawPanel,BorderLayout.CENTER);
listener = new PixelListener(null);
//定义字符串数组
String[]name={"打开","灰度","马赛克","调节马赛克大小","调节亮度","旋转","左右翻转","上下翻转"};
for (int i=0;i<name.length;i++){
//功能按钮
JButton jbu=new JButton(name[i]);
northPanel.add(jbu);
//给按钮加监听器
jbu.addActionListener(listener);
}
jf.setVisible(true);
Graphics g = drawPanel.getGraphics();
listener.setGraphics(g);
// 鼠标监听器绑定到绘图面板
drawPanel.addMouseListener(listener);
drawPanel.addMouseMotionListener(listener);
drawPanel.addMouseWheelListener(listener);
}
public static void main(String[]args){
PixelUI ui =new PixelUI();
ui.initUI();
}
}
3.2 PixelListener.java
package zsq20260122;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class PixelListener implements MouseListener,ActionListener, MouseMotionListener,MouseWheelListener {
private Graphics g;
private Filterlmage filter;
private String currentFunc;
public int lastX, lastY;
public int flag = 1;
private int baseBrightness = 0; // 基础亮度值(全局变量,所有方法都能访问)
// 马赛克调节核心变量
private int baseMosaicSize = 20; // 初始马赛克大小
private final int MOSAIC_MIN = 5; // 最小
private final int MOSAIC_MAX = 50; // 最大
String path = "C:\\Users\\Liar\\Desktop\\1.png";
int[][] pixelArr;
BufferedImage bufferedImage;
//获取缓冲区画笔对象
Graphics buffG;
// 图片缩放变量
private int pixelSize = 100; // 缩放比例,初始1.0(原大小)
private int imgX = 0; // 图片显示的X偏移(防止缩放后出界)
private int imgY = 0; // 图片显示的Y偏移
//构造方法初始化属性
public PixelListener(Graphics g) {
this.g = g;
this.lastX = -1;
this.lastY = -1;
}
public void setGraphics(Graphics g) {
this.g = g;
filter = new Filterlmage(g);
}
//重写事件处理方法
public void actionPerformed(ActionEvent e) {
System.out.println("点击按钮!");
currentFunc = e.getActionCommand(); // 获取点击的按钮名称
flag = 1;
lastX = -1;
lastY = -1;
//加载图片
if ("打开".equals(currentFunc)) {
pixelArr = getPixel(path);
bufferedImage=filter.drawPixel(pixelArr);
// 打开图片重置为初始值100
pixelSize = 100;
imgX = 0;
imgY = 0;
drawScaledImageByPixelSize(); // 用pixelSize绘制原图
} else if ("灰度".equals(currentFunc)) {
if (pixelArr != null && bufferedImage != null) { // 确保已加载图片
drawGrayImage();
drawScaledImageByPixelSize(); // 旋转后按pixelSize重绘
}
}
if ("马赛克".equals(currentFunc)) {
lastX = -1;
lastY = -1;
}
if ("旋转".equals(currentFunc)) {
if (pixelArr != null) {
int[][] NewArr = filter.rotate(pixelArr);
bufferedImage = filter.drawPixel(NewArr);
pixelArr = NewArr;
drawScaledImageByPixelSize(); // 旋转后按pixelSize重绘
}
}
if("左右翻转".equals(currentFunc)){
if(pixelArr!=null){
int[][]NewArr=filter.flipHorzontal(pixelArr);
bufferedImage=filter.drawPixel(NewArr);
pixelArr=NewArr;
drawScaledImageByPixelSize();
}
}
if("上下翻转".equals(currentFunc)){
if(pixelArr!=null){
int[][]NewArr=filter.flipVertical(pixelArr);
bufferedImage=filter.drawPixel(NewArr);
pixelArr=NewArr;
drawScaledImageByPixelSize();
}
}
if("调节亮度".equals(currentFunc)){
showBrightnessSlider();
}
if ("调节马赛克大小".equals(currentFunc)) {
showMosaicSlider();
}
}
//读取指定路径上的图片数据
//I/O流:读写磁盘上的数据
public int[][] getPixel(String path) {
//创建文件对象
File file = new File(path);
//读取图片数据,并返回缓冲图片
// BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
//根据缓冲图片定义二维数组,保持图片所有的像素点
int h = bufferedImage.getHeight();
int w = bufferedImage.getWidth();
int[][] pixelArr = new int[h][w];
//遍历图片的所有像素点
for (int i = 0; i < h; i++) {//行
for (int j = 0; j < w; j++) {//列
int pixel = bufferedImage.getRGB(j, i);
pixelArr[i][j] = pixel;
}
}
return pixelArr;
}
// 根据亮度值更新图片
private void updateBrightness(int brightness) {
if (pixelArr == null) return;
int[][] newArr = filter.brighten(pixelArr, brightness);
bufferedImage = filter.drawPixel(newArr);
drawScaledImageByPixelSize(); // 重新绘制缩放后的图片
}
// 马赛克绘制方法(放大像素块思路)
private void drawMosaic(int x, int y) {
if (bufferedImage == null || g == null) {
return;
}
// 算缩放比例(原图宽度 / 显示宽度)
double scaleRatio = (double) bufferedImage.getWidth() / pixelSize;
// 面板坐标转原图坐标(抵消缩放和偏移)
int originalX = (int) ((x - imgX) * scaleRatio);
int originalY = (int) ((y - imgY) * scaleRatio);
// 校验原图坐标是否在范围内(替代原来的面板坐标校验)
int imgW = bufferedImage.getWidth();
int imgH = bufferedImage.getHeight();
if (originalX < 0 || originalX >= imgW || originalY < 0 || originalY >= imgH) {
return;
}
// 1. 计算当前鼠标所在的马赛克块的左上角坐标
int blockX = (originalX / baseMosaicSize) * baseMosaicSize;
int blockY = (originalY / baseMosaicSize) * baseMosaicSize;
// 2. 计算块的右下角坐标(防止超出图片边界)
int endX = Math.min(blockX + baseMosaicSize, imgW);
int endY = Math.min(blockY + baseMosaicSize, imgH);
// 3. 取块内左上角的像素颜色(马赛克本质:用一个颜色填充整个块)
Color blockColor = new Color(bufferedImage.getRGB(blockX, blockY));
// 把原图马赛克坐标 → 面板显示坐标(反向转换)
int scaledBlockX = (int) (blockX / scaleRatio) + imgX;
int scaledBlockY = (int) (blockY / scaleRatio) + imgY;
int scaledEndX = (int) (endX / scaleRatio) + imgX;
int scaledEndY = (int) (endY / scaleRatio) + imgY;
// 4. 用这个颜色填充整个块,实现像素放大效果
g.setColor(blockColor);
g.fillRect(scaledBlockX, scaledBlockY, scaledEndX -scaledBlockX, scaledEndY - scaledBlockY);
for (int i = blockY; i < endY; i++) {
for (int j = blockX; j < endX; j++) {
bufferedImage.setRGB(j, i, blockColor.getRGB());
}
}
}
//灰度图绘制方法
private void drawGrayImage() {
//初始化缓冲图片,匹配图片实际宽高
if (pixelArr != null && bufferedImage != null) {
BufferedImage grayImage = new BufferedImage(pixelArr[0].length, pixelArr.length, BufferedImage.TYPE_INT_RGB);
buffG = grayImage.getGraphics(); // 获取缓冲画笔
int h = pixelArr.length;
int w = pixelArr[0].length;
//遍历所有像素,逐个转换灰度
for (int i = 0; i < h; i++) {//行(y轴)
for (int j = 0; j < w; j++) {//列(x轴)
//1.获取原始彩色像素的RGB分量
int rgb = bufferedImage.getRGB(j, i);
int r = (rgb >> 16) & 0xFF;//红色分量
int g = (rgb >> 8) & 0xFF;//绿色分量
int b = rgb & 0xFF;//蓝色分量
//2.加权平均法计算
int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);
//3.用灰度值生成新像素(R=G=B=gray,即黑白效果)
Color grayColor = new Color(gray, gray, gray);
this.buffG.setColor(grayColor);
//4.绘制灰度像素点
this.buffG.drawLine(j, i, j, i);
}
}
bufferedImage = grayImage;
// 灰度后的缓冲图片显示到面板
if (this.g != null) {
this.g.drawImage(bufferedImage, 0, 0, null);
}
buffG.dispose(); // 释放资源
//更新pixelArr数组
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
pixelArr[i][j] = bufferedImage.getRGB(j, i);
}
}
}
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
System.out.println("按下面板,坐标:(" + e.getX() + "," + e.getY() + ")");
if ("马赛克".equals(currentFunc)) {
lastX = e.getX();
lastY = e.getY();
drawMosaic(lastX, lastY);
}
}
public void mouseReleased(MouseEvent e) {
System.out.println("松开面板,坐标:(" + e.getX() + "," + e.getY() + ")");
if ("马赛克".equals(currentFunc)) {
lastX = -1;
lastY = -1;
}
//马赛克画完,更新pixelArr为带马赛克的数组
if (bufferedImage != null && pixelArr != null) {
int h = pixelArr.length;
int w = pixelArr[0].length;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
pixelArr[i][j] = bufferedImage.getRGB(j, i);
}
}
}
}
//弹出调节亮度窗口
private void showBrightnessSlider(){
//创建新窗口
JFrame sliderFrame = new JFrame("调节亮度");
sliderFrame.setSize(300,100);
sliderFrame.setLocationRelativeTo(null);
sliderFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//创建滑块(0~255,初始值0)
JSlider brightnessSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, baseBrightness);
brightnessSlider.setMajorTickSpacing(50);//刻度50
brightnessSlider.setPaintLabels(true);//显示刻度值
brightnessSlider.addChangeListener(new ChangeListener() {
// 当滑块值变化时,自动调用这个方法
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
baseBrightness=source.getValue();
updateBrightness(baseBrightness);
System.out.println("滑块亮度值:"+baseBrightness);
}
});
//添加滑块到窗口
sliderFrame.add(brightnessSlider);
sliderFrame.setVisible(true);
}
//马赛克调节窗口
private void showMosaicSlider(){
//创建新窗口
JFrame sliderFrame = new JFrame("调节马赛克大小");
sliderFrame.setSize(300,100);
sliderFrame.setLocationRelativeTo(null);
sliderFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
sliderFrame.setLayout(new FlowLayout());
//创建滑块
JSlider mosaicSlider = new JSlider(JSlider.HORIZONTAL,MOSAIC_MIN,MOSAIC_MAX,baseMosaicSize);
mosaicSlider.setMajorTickSpacing(10);
mosaicSlider.setPaintTicks(true);
mosaicSlider.setPaintLabels(true);
//滑块事件
mosaicSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
JSlider source = (JSlider) e.getSource();
if(!source.getValueIsAdjusting()){
baseMosaicSize=source.getValue();
System.out.println("马赛克大小:"+baseMosaicSize);
}
}
});
//添加滑块到窗口
sliderFrame.add(mosaicSlider);
sliderFrame.setVisible(true);
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
// 1. 获取当前鼠标拖动的坐标(面板本地坐标)
int currentX = e.getX();
int currentY = e.getY();
if ("马赛克".equals(currentFunc)) {
drawMosaic(currentX, currentY);
}
lastX = currentX;
lastY = currentY;
}
public void mouseMoved(MouseEvent e) {
}
public void mouseWheelMoved(MouseWheelEvent e) {
if (bufferedImage == null) return; // 没加载图片不调节
int rotate = e.getWheelRotation();
if (rotate > 0) {//向下
pixelSize -= 20;
} else {//向上
pixelSize += 20;
}
System.out.println("pixelSize=" + pixelSize);
// 根据pixelSize重新绘制图片
drawScaledImageByPixelSize();
}
// 按pixelSize绘制缩放后的图片
private void drawScaledImageByPixelSize() {
if (bufferedImage == null || g == null) return;
// 清空面板
g.clearRect(0, 0, 900, 900);
// 核心:根据pixelSize计算图片显示尺寸(保持宽高比)
double imgRatio = (double) bufferedImage.getWidth() / bufferedImage.getHeight(); // 原图宽高比
int showWidth = pixelSize; // 显示宽度 = pixelSize
int showHeight = (int) (pixelSize / imgRatio); // 显示高度按比例计算,避免变形
// 绘制图片(从偏移位置开始,默认左上角)
g.drawImage(bufferedImage, imgX, imgY, showWidth, showHeight, null);
}
}
3.3 FilterImage.java
package zsq20260122;
import java.awt.*;
import java.awt.image.BufferedImage;
public class FilterImage {
private Graphics g;
private BufferedImage bufferedImage;
public FilterImage(Graphics g){
this.g = g;
}
//绘制原图
//像素点显示图片:代码>jvm>os>总线>屏幕
public BufferedImage drawPixel(int[][]pixelArr){
//创建缓冲图片
bufferedImage = new BufferedImage(pixelArr[0].length,pixelArr.length,BufferedImage.TYPE_3BYTE_BGR);
//获取 缓冲区画笔对象
Graphics buffG =bufferedImage.getGraphics();
for(int i = 0;i<pixelArr.length;i++){
for (int j = 0;j<pixelArr[0].length;j++){
//取出R G B三原色的像素值
int rgb = pixelArr[i][j];
int red = rgb>>16&0xFF;
int green = rgb >>8&0xFF;
int blue = rgb &0xFF;
//根据三原色创建Color对象,设置给画笔
Color color =new Color(red,green,blue);
buffG.setColor(color);
//绘制像素点
buffG.drawLine(j,i,j,i);
}
}
//把缓冲图片显示在面板上
g.drawImage(bufferedImage,0,0,null);
buffG.dispose();
return bufferedImage;
}
//旋转:对图像数组进行旋转
public int[][] rotate(int[][]pixelArr) {
//对pixelArr数组,右转90°
//行列的长度互换
int[][] NewArr = new int[pixelArr[0].length][pixelArr.length];
for (int i = 0; i < pixelArr.length; i++) {
for(int j=0;j<pixelArr[0].length;j++){
NewArr[j][pixelArr.length-1-i]=pixelArr[i][j];
}
}
return NewArr;
}
//左右翻转像素组
public int[][] flipHorzontal(int [][]pixelArr){
if(pixelArr==null||pixelArr.length==0)return pixelArr;
int h=pixelArr.length;//图片高度(行数)
int w=pixelArr[0].length;//图片宽度(列数)
int [][]newArr=new int[h][w];//新像素数组
//每行像素左右交换
for(int i=0;i<h;i++){
for(int j= 0;j<w;j++){
newArr[i][j]=pixelArr[i][w-1-j];
}
}
return newArr;
}
//上下翻转像素组
public int[][]flipVertical(int[][]pixelArr){
if(pixelArr==null||pixelArr.length==0)return pixelArr;
int h= pixelArr.length;
int w=pixelArr[0].length;
int [][]newArr=new int[h][w];
//每列像素上下交换
for(int i=0;i<h;i++){
for(int j=0;j<w;j++){
newArr[i][j]=pixelArr[h-1-i][j];
}
}
return newArr;
}
// 原有rotate、flipHorizontal、flipVertical、drawPixel方法保留
// 图片加亮
public int[][] brighten(int[][] pixelArr, int brightness) {
if (pixelArr == null || pixelArr.length == 0) return pixelArr;
int h = pixelArr.length;
int w = pixelArr[0].length;
int[][] newArr = new int[h][w];
// 遍历每个像素,修改RGB值
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
// 1. 解析当前像素的RGB值(像素值是int类型,包含ARGB四个通道)
int pixel = pixelArr[i][j];
int alpha = (pixel >> 24) & 0xFF; // 透明度(保留不变)
int red = (pixel >> 16) & 0xFF; // 红色通道(0~255)
int green = (pixel >> 8) & 0xFF; // 绿色通道(0~255)
int blue = pixel & 0xFF; // 蓝色通道(0~255)
// 2. 加亮处理:提高RGB值,限制在0~255(超过255会变成白色)
red = Math.min(red + brightness, 255); // 不能超过255
green = Math.min(green + brightness, 255);
blue = Math.min(blue + brightness, 255);
// 3. 重新组合成新的像素值
int newPixel = (alpha << 24) | (red << 16) | (green << 8) | blue;
newArr[i][j] = newPixel;
}
}
return newArr;
}
}
四、核心功能与优化点讲解
4.1 图片瞬间加载
基于 BufferedImage 内存缓冲,先在后台完成所有像素绘制,再一次性显示,彻底告别逐行绘制。
4.2 旋转 / 左右翻转 / 上下翻转
纯像素数组坐标变换实现,不依赖任何第三方库,逻辑清晰、运行高效。
4.3 滑块调节亮度与马赛克大小
使用 JSlider 实现可视化调节,拖动滑块实时生效,操作直观、体验流畅。
4.4 鼠标滚轮缩放
自动计算图片宽高比,实现等比例缩放,不变形、不拉伸,缩放后仍可正常进行所有图像处理。
4.5 缩放后马赛克坐标精准映射
将面板坐标映射为原图坐标,确保图片放大/缩小时马赛克依然绘制在正确位置,不会偏移或越界。
4.6 三层架构解耦
- PixelUI:只负责界面搭建
- PixelListener:只负责事件与交互
- FilterImage:只负责图像处理算法
代码结构清晰,易于阅读、维护与后续扩展。
五、运行步骤
- 修改 PixelListener.java 中的图片路径为本地有效路径
- 运行 PixelUI.java 启动程序
- 点击顶部按钮使用对应功能
- 马赛克模式:按下鼠标并拖动绘制
- 鼠标滚轮:缩放图片大小
- 调节亮度/马赛克:弹出滑块窗口实时调整
六、新手避坑指南
- 必须使用 BufferedImage 缓冲才能实现瞬间加载
- 图片旋转时宽高需要互换,否则会变形
- 亮度调整后必须用 Math.min 限制在 0~255,避免颜色异常
- 图片缩放后马赛克需做坐标映射,否则会绘制偏移
- 每次图像处理后要同步更新像素数组,避免效果丢失
总结
本次升级版图像处理工具,在原有基础上完成了性能与功能的双重升级,从一个基础像素演示工具,变为一款流畅、实用、交互完整的轻量级图片编辑器。
通过本次开发,可以深入理解 Java GUI 绘图机制、缓冲图像原理、像素级处理、鼠标交互与坐标映射等核心知识点,代码结构清晰、注释完整,非常适合 GUI 学习与课程设计使用。
以上就是本次 Java GUI 图像处理工具的全部内容,代码注释详细、可直接运行。
项目还在持续完善中,后续我会继续更新更多功能与优化。
如果你有运行问题,或是有好的建议,欢迎在评论区留言交流。
我们一起学习、一起完善~
更多推荐




所有评论(0)