`
jorneyR
  • 浏览: 16232 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

别以为Swing有双缓冲就够了,变态时三缓冲是很有必要的

阅读更多


AWT时代必须自己实现双缓冲机制,否则绘画时界面总是闪烁。
Swing的JComponent以及其子类的绘制默认是使用了双缓冲的,例如JPanel,方便了不少。

但是,当在Swing中绘制几千个图元时,如果绘图仍然是直接对Swing的Back-buffer进行操作,速度会非常的慢,甚至慢到没法忍受。例如下面的例子里有16385个点,共画16384条线,改变窗口的大小,就可以发现直接操作Swing的Back-buffer是多么的令人难以忍受。
这个时候,使用三缓冲(triple-buffer)是很有必要的:先把这些图元绘制到自己创建的缓冲图像里,然后再一次性的把此缓冲图像交给Swing后台绘制,速度的提升是非常非常的大的。

只有两个Java文件,就不打包了。
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

import util.GeometryUtil;

@SuppressWarnings("serial")
public class Growing extends JPanel {
    private List<Point2D> ps = new ArrayList<Point2D>();
    private Timer timer;
    private boolean stopped = false;

    public Growing() {
        ps.add(new Point2D.Double(0, 0));
        ps.add(new Point2D.Double(800, 0));

        timer = new Timer(500, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                grow();
                repaint();
            }
        });

        timer.start();
    }

    public void grow() {
        if (stopped) { return; }

        List<Point2D> temp = new ArrayList<Point2D>();
        temp.add(ps.get(0));

        for (int i = 0; i < ps.size() - 1; ++i) {
            Point2D p0 = ps.get(i);
            Point2D p4 = ps.get(i + 1);
            double len = GeometryUtil.distanceOfPoints(p0, p4);

            if (len < 0.5) {
                // 当线条长度小于1时,就停止再增长
                System.out.println(ps.size());
                timer.stop();
                return;
            }

            Point2D p1 = GeometryUtil.extentPoint(p0, p4, len / 3);
            Point2D p3 = GeometryUtil.extentPoint(p0, p4, len * 2 / 3);
            Point2D p2 = GeometryUtil.rotate(p3.getX(), p3.getY(), p1.getX(), p1.getY(), 60);

            temp.add(p1);
            temp.add(p2);
            temp.add(p3);
            temp.add(p4);
        }

        ps = null;
        ps = temp;
        temp = null;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        // 修改type的值使用不同的绘制方式,1为compatible image, 2为swing的back-buffer
        int type = 1;

        // 改变窗口的大小,可以看到直接对intermediate image操作比直接对swing back-buffer操作快很多.
        // 所以有很多绘制操作时,使用triple buffer是很有必要的(因为Swing已经默认使用了双缓冲).

        if (type == 1) {
            // [[[1]]]: 操作 compatible image 速度非常快
            renderWithBuf(g2d, getWidth(), getHeight());
        } else {
            // [[[2]]]: 操作Swing的 back-buffer 速度非常慢
            render(g2d, getWidth(), getHeight());
        }
    }

    private BufferedImage bufImg;

    protected void renderWithBuf(Graphics2D g2d, int w, int h) {
        if (bufImg == null || bufImg.getWidth() != w || bufImg.getHeight() != h) {
            bufImg = createCompatibleImage(w, h, Transparency.OPAQUE);
            // bufImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        }

        Graphics2D gg = bufImg.createGraphics();
        render(gg, w, h);
        gg.dispose();

        g2d.drawImage(bufImg, 0, 0, null);
    }

    protected void render(Graphics2D g2d, int w, int h) {
        g2d.setBackground(Color.BLACK);
        g2d.clearRect(0, 0, w, h);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.translate(0, h - 20);

        g2d.setColor(Color.WHITE);
        for (int i = 0; i < ps.size() - 1; ++i) {
            Point2D sp = ps.get(i);
            Point2D ep = ps.get(i + 1);
            g2d.drawLine((int) sp.getX(), -(int) sp.getY(), (int) ep.getX(), -(int) ep.getY());
        }
    }

    // 创建硬件适配的缓冲图像,为了能显示得更快速
    public static BufferedImage createCompatibleImage(int w, int h, int type) {
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice device = env.getDefaultScreenDevice();
        GraphicsConfiguration gc = device.getDefaultConfiguration();
        return gc.createCompatibleImage(w, h, type);
    }

    private static void createGuiAndShow() {
        JFrame frame = new JFrame("Growing");
        frame.getContentPane().add(new Growing());

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 400);
        frame.setAlwaysOnTop(true);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGuiAndShow();
            }
        });
    }
}

package util;

import java.awt.geom.Point2D;

public class GeometryUtil {
    // 两点之间的距离
    public static double distanceOfPoints(Point2D p1, Point2D p2) {
        double disX = p2.getX() - p1.getX();
        double disY = p2.getY() - p1.getY();
        double dis = Math.sqrt(disX * disX + disY * disY);

        return dis;
    }

    // 两点的中点
    public static Point2D middlePoint(Point2D p1, Point2D p2) {
        double x = (p1.getX() + p2.getX()) / 2;
        double y = (p1.getY() + p2.getY()) / 2;

        return new Point2D.Double(x, y);
    }

    // 在两点所在直线上,以从startPoint到endPoint为方向,离startPoint的距离disToStartPoint的点
    public static Point2D extentPoint(Point2D startPoint, Point2D endPoint, double disToStartPoint) {
        double disX = endPoint.getX() - startPoint.getX();
        double disY = endPoint.getY() - startPoint.getY();
        double dis = Math.sqrt(disX * disX + disY * disY);
        double sin = (endPoint.getY() - startPoint.getY()) / dis;
        double cos = (endPoint.getX() - startPoint.getX()) / dis;
        double deltaX = disToStartPoint * cos;
        double deltaY = disToStartPoint * sin;

        return new Point2D.Double(startPoint.getX() + deltaX, startPoint.getY() + deltaY);
    }

    // 绕原点的旋转矩阵,绕任意点旋转,可以先移动到原点,旋转,然后再移回去
    // cosθ -sinθ 0
    // sinθ +conθ 0
    // 0000 +0000 1
    // x = r*cosα, y = r*sinα
    // x' = r*cos(α+θ) = r*cosα*cosθ - r*sinα*sinθ = x*cosθ - y*sinθ
    // y' = r*sin(α+θ) = r*sinα*cosθ + r*cosα*sinθ = x*sinθ + y*cosθ
    // (x, y)绕圆心旋转degree度
    public static Point2D rotate(double x, double y, double degree) {
        return rotate(x, y, 0, 0, degree);
    }

    // (x, y)绕(ox, oy)旋转degree度
    public static Point2D rotate(double x, double y, double ox, double oy, double degree) {
        x -= ox;
        y -= oy;

        double cos = Math.cos(Math.toRadians(degree));
        double sin = Math.sin(Math.toRadians(degree));

        double temp = x * cos - y * sin;
        y = x * sin + y * cos;
        x = temp;

        return new Point2D.Double(x + ox, y + oy);
    }

    public static void main(String[] args) {
        Point2D p = rotate(50, 10, 10);
        System.out.println(p);
        p = rotate(100, 60, 50, 50, 10);
        System.out.println(p);
    }
}
  • 大小: 35.9 KB
分享到:
评论
5 楼 maodiesky 2011-01-13  
同行之间的仇恨确实很大啊
4 楼 qianhd 2011-01-12  
jorneyR 写道
qianhd 写道
有数据证明吗?
从原理上看 这完全是无稽之谈
从我测试的数据来看 更加是无稽之谈

为了让差距更加明显 我将初始尺寸改成1400*600 线条长度小于0.05
总共1048577个点

我加了一个线程 会定时修改frame的size
然后在paintComponent开始时记录时间,结束时候打印执行时间

所谓的3缓冲完全绘图一次 耗时1.5-1.8秒(这也时间太长了,我这里是毫秒级的)
默认双缓冲完全绘图一次 耗时1.5-1.8秒

没看出来所谓的3缓冲有任何的性能提升

呵呵,就算我在扯蛋吧,没什么好争论的.
有时间可以去看看Swing方面的书,如Filthy Rich Client等之类的


装啥B啊 不就本30多块的书嘛
只可惜你看了也白看
3 楼 jorneyR 2011-01-12  
qianhd 写道
有数据证明吗?
从原理上看 这完全是无稽之谈
从我测试的数据来看 更加是无稽之谈

为了让差距更加明显 我将初始尺寸改成1400*600 线条长度小于0.05
总共1048577个点

我加了一个线程 会定时修改frame的size
然后在paintComponent开始时记录时间,结束时候打印执行时间

所谓的3缓冲完全绘图一次 耗时1.5-1.8秒(这也时间太长了,我这里是毫秒级的)
默认双缓冲完全绘图一次 耗时1.5-1.8秒

没看出来所谓的3缓冲有任何的性能提升

呵呵,就算我在扯蛋吧,没什么好争论的.
有时间可以去看看Swing方面的书,如Filthy Rich Client等之类的
2 楼 qianhd 2011-01-12  
有数据证明吗?
从原理上看 这完全是无稽之谈
从我测试的数据来看 更加是无稽之谈

为了让差距更加明显 我将初始尺寸改成1400*600 线条长度小于0.05
总共1048577个点

我加了一个线程 会定时修改frame的size
然后在paintComponent开始时记录时间,结束时候打印执行时间

所谓的3缓冲完全绘图一次 耗时1.5-1.8秒
默认双缓冲完全绘图一次 耗时1.5-1.8秒

没看出来所谓的3缓冲有任何的性能提升
1 楼 mycybyb 2011-01-12  
回复-1?

相关推荐

    Java双缓冲技术原理详细讲解例子

    Java双缓冲技术是一种提高图形界面性能的策略,尤其在绘制复杂的UI组件时,能有效避免闪烁现象,提供平滑的视觉体验。双缓冲的核心思想是将屏幕上的绘图操作先在一个临时缓冲区进行,待全部操作完成后再一次性将缓冲...

    双缓冲例子

    - 在Java中,可以使用Swing或JavaFX库实现双缓冲。 - 在C++中,可以利用OpenGL或DirectX的双缓冲特性。 - Python的Pygame库也支持双缓冲。 - 在Unity3D等游戏引擎中,双缓冲是默认的渲染方式。 这个“双缓冲例子”...

    俄罗斯方块_带双缓冲绘图

    在这个例子中,`TetrisDoubleBuffered.java`很可能是实现俄罗斯方块游戏的主类,它继承自`JPanel`,并覆盖了`paintComponent()`方法,实现了双缓冲绘图。`State.java`可能包含了游戏状态的管理,如方块的状态、游戏...

    java画图(双缓冲)

    双缓冲是一种图形渲染技术,用于提高在屏幕上绘制复杂图形时的性能和质量,避免闪烁和不连续的视觉效果。在本案例中,开发者独立实现了这样一个功能,创建了一个画图板应用,它使用了双缓冲技术来提供平滑、无闪烁的...

    利用双缓冲做的时钟程序

    标题中的“利用双缓冲做的时钟程序”是指在编程中使用了双缓冲技术来实现一个显示实时时间的程序。双缓冲是一种图形渲染技术,主要用于减少屏幕闪烁和提高图像质量。在Java AWT(Abstract Window Toolkit)和Swing库...

    利用双缓冲的方法解决界面屏闪

    例如,Java的AWT/Swing库、C#的Windows Forms或WPF、Python的Pygame等都有相应的双缓冲机制。在提供的压缩文件"用双缓冲解决闪烁问题"中,可能包含了使用特定编程语言或库实现双缓冲的示例代码,供开发者参考学习。 ...

    双缓冲区绘制线条,避免绘图闪烁

    在计算机图形学中,双缓冲区技术是一种常用于优化图形绘制和更新,特别是对于动态图形显示,以消除或减少屏幕闪烁现象的策略。这个技术在游戏开发、GUI(图形用户界面)设计以及动画制作等领域中广泛应用。下面我们...

    利用双缓冲做的时钟程序1 .rar_双缓冲

    【标题】"利用双缓冲做的时钟程序1 .rar_双缓冲" 提供了一个关键的编程概念,即双缓冲技术,通常应用于图形用户界面(GUI)的开发中,特别是涉及频繁更新显示内容的场景,例如游戏或动画。双缓冲是一种优化技术,...

    双缓冲范例之水扁跳舞

    在计算机图形学和游戏开发领域,"双缓冲范例之水扁跳舞"是一个生动的示例,用于演示如何利用双缓冲技术来优化图形渲染并消除屏幕闪烁现象,从而提高用户体验。双缓冲是一种重要的图形绘制策略,尤其在动态图像如动画...

    Java双缓冲技术原理详细讲解例子Java实用源码整理learns

    Java双缓冲技术是一种图形渲染优化策略,主要用于提高GUI(图形用户界面)的性能和减少屏幕闪烁,提升用户体验。在Java中,它主要应用于Swing和JavaFX等图形库。本教程将深入探讨Java双缓冲技术的原理,并通过实例...

    商业编程-源码-实例解说双缓冲.zip

    在传统的单缓冲系统中,每当图形更新时,新的图像会立即显示在屏幕上,如果更新过程中有中断,用户可能会看到不完整的图像,造成视觉上的不连续感。双缓冲则解决了这个问题,它引入了两个缓冲区:一个用于绘制新图像...

    双缓冲绘图实例.zip

    在计算机图形学中,双缓冲绘图是一种优化技术,用于减少屏幕闪烁和图像撕裂问题,尤其是在进行动态或频繁更新的图形操作时。这个“双缓冲绘图实例”是一个使用Java实现的示例,旨在展示如何在GUI应用程序中有效地...

    使用双缓冲绘画.rar .

    在计算机图形学中,双缓冲技术是一种优化图形绘制性能并减少屏幕闪烁的策略,尤其在进行复杂的图形更新时。在Windows编程或者Java图形用户界面(GUI)开发中,双缓冲是一种常见的实践。本文将深入探讨双缓冲绘画的...

    scrollview 冻结列 双缓冲

    例如,在Java Swing或Android中,我们可以找到支持冻结列和双缓冲的第三方库,或者通过扩展基础组件并重写其绘制方法来实现。在JavaScript和Web开发中,可能需要利用CSS定位技术来实现冻结列,并使用WebGL或Canvas的...

    Java双缓冲技术.pdf

    在深入探讨Java双缓冲技术之前,首先需要对双缓冲技术有一个基本的理解。双缓冲技术是一种常见的图形渲染技术,用于减少或消除在屏幕上绘制图形时出现的闪烁现象,特别适用于需要频繁更新画面的应用程序,例如动画和...

    基于Java Swing的循环缓冲与缓冲池的操作系统模拟程序(可视化界面)

    基于Java Swing的循环缓冲与缓冲池的操作系统模拟程序(可视化界面);基于Java Swing的循环缓冲与缓冲池的操作系统模拟程序(可视化界面);基于Java Swing的循环缓冲与缓冲池的操作系统模拟程序(可视化界面);...

    双缓冲解决屏幕闪烁问题

    在计算机图形学和GUI编程中,"双缓冲"是一种有效解决屏幕闪烁问题的技术。当程序在屏幕上绘制图形或更新界面时,如果没有采取适当的措施,可能会出现闪烁现象,这主要是因为屏幕刷新与数据更新不同步导致的。双缓冲...

    JAVA双缓冲绘图源码

    此外,Java Swing提供了更好的绘图组件JComponent,它的 paintComponent 方法已经内置了双缓冲机制,所以如果你使用Swing,无需手动实现双缓冲,只需重写`paintComponent(Graphics g)`方法进行绘图即可。 通过以上...

    双缓冲效果

    在编程语言中,如Java的Swing、JavaFX,C++的OpenGL,或者Unity、Unreal Engine等游戏引擎,都有内置支持双缓冲的功能,开发者可以通过相应的API轻松实现双缓冲效果。例如,使用OpenGL时,可以设置GL_DOUBLEBUFFER...

    双缓冲方法解决屏幕刷新闪烁的问题(rar)

    在Java、C++、Python等编程语言中都有相应的库支持实现双缓冲,例如Java的AWT和Swing库,C++的OpenGL库,以及Python的Pygame库等。 总结起来,双缓冲是一种优化图形显示质量的技术,通过分离绘制和显示过程,有效...

Global site tag (gtag.js) - Google Analytics