Java Swing实现图像像素化动画时JLabel首图后显示白屏问题
问题原因
- Swing的UI渲染完全依赖事件派发线程(EDT),你当前所有像素计算、sleep休眠、UI更新操作都在主线程执行,直接阻塞了EDT线程,repaint请求无法被调度执行,界面就会卡死白屏。
- display方法里反复移除再新增JLabel的操作完全冗余,每次修改组件树都会触发布局重算,进一步加剧线程阻塞。
- 每修改1个像素就触发1次全量更新+休眠1秒,执行效率极低,也完全没有必要。
修复后的完整代码
import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.swing.JLabel; import javax.swing.JFrame; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import java.awt.Toolkit; import java.awt.Dimension; import javax.imageio.ImageIO; import java.util.concurrent.TimeUnit; class ImageUtil { static JFrame f; static Dimension screenSize; static JLabel lbl; public static BufferedImage pixelate(BufferedImage imageToPixelate, int pixelSize) { BufferedImage pixelateImage = new BufferedImage( imageToPixelate.getWidth(), imageToPixelate.getHeight(), imageToPixelate.getType()); for (int y = 0; y < imageToPixelate.getHeight(); y += pixelSize) { for (int x = 0; x < imageToPixelate.getWidth(); x += pixelSize) { BufferedImage croppedImage = getCroppedImage(imageToPixelate, x, y, pixelSize, pixelSize); Color dominantColor = getDominantColor(croppedImage); // 整块填充完再更新 for (int yd = y; (yd < y + pixelSize) && (yd < pixelateImage.getHeight()); yd++) { for (int xd = x; (xd < x + pixelSize) && (xd < pixelateImage.getWidth()); xd++) { pixelateImage.setRGB(xd, yd, dominantColor.getRGB()); } } // 填充完一个区块后更新UI ImageIcon img = new ImageIcon(pixelateImage); display(img); } } return pixelateImage; } public static BufferedImage getCroppedImage(BufferedImage image, int startx, int starty, int width, int height) { if (startx < 0) startx = 0; if (starty < 0) starty = 0; if (startx > image.getWidth()) startx = image.getWidth(); if (starty > image.getHeight()) starty = image.getHeight(); if (startx + width > image.getWidth()) width = image.getWidth() - startx; if (starty + height > image.getHeight()) height = image.getHeight() - starty; return image.getSubimage(startx, starty, width, height); } public static Color getDominantColor(BufferedImage image) { int sumR = 0, sumB = 0, sumG = 0, sum2 = 0; int color = 0; for (int x = 0; x < image.getWidth(); x++) { for (int y = 0; y < image.getHeight(); y++) { color = image.getRGB(x, y); Color c = new Color(color); sumR += c.getRed(); sumB += c.getBlue(); sumG += c.getGreen(); sum2++; } } return new Color(sumR / sum2, sumG / sum2, sumB / sum2); } public static void display(ImageIcon image) { // UI更新放到EDT线程执行 SwingUtilities.invokeLater(() -> { lbl.setIcon(image); }); try { // 可自行调整休眠时间,单位毫秒 TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { String filename = args[0]; int PIX_SIZE = Integer.parseInt(args[1]); f = new JFrame(); screenSize = Toolkit.getDefaultToolkit().getScreenSize(); f.setUndecorated(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); try { // 修复写死文件名的问题 BufferedImage img = ImageIO.read(new File(filename)); ImageIcon image = new ImageIcon(img); lbl = new JLabel(image); f.setSize(image.getIconWidth(), image.getIconHeight()); int x = (screenSize.width - f.getWidth()) / 2; int y = (screenSize.height - f.getHeight()) / 2; f.setLocation(x, y); f.getContentPane().add(lbl); f.setVisible(true); // 像素化逻辑放到独立工作线程执行,不阻塞EDT new Thread(() -> { BufferedImage imagePixelated = ImageUtil.pixelate(img, PIX_SIZE); // 可自行取消注释保存结果 // try { // ImageIO.write(imagePixelated, "jpg", new File("pixelated.jpg")); // } catch (IOException e) { // e.printStackTrace(); // } }).start(); } catch (IOException e) { e.printStackTrace(); } } }
核心修改说明
- 线程模型适配:将耗时的像素化计算逻辑放到独立工作线程运行,所有UI更新操作通过
SwingUtilities.invokeLater调度到EDT线程执行,避免阻塞UI渲染 - 简化UI更新逻辑:不再反复移除新增JLabel,直接调用
setIcon方法更新标签内容,大幅降低UI开销 - 优化更新频率:每填充完一个完整的像素区块后再触发UI更新,既保留了逐步像素化的演示效果,又避免了冗余操作
- 修复细节问题:修正了原代码中读取文件写死的问题,补充了窗口关闭逻辑,调整了休眠时间提升演示流畅度
内容的提问来源于stack exchange,提问作者Turqay Umudzade
相关产品推荐
相关产品推荐

