2010-12-13 40 views
17

EDIT HAIJava: làm thế nào để làm đệm đôi trong Swing?

Để ngăn chặn ý kiến ​​snarky và một dòng câu trả lời thiếu điểm: IFF nó cũng đơn giản như gọi setDoubleBuffered (true), sau đó làm thế nào để tôi nhận được quyền truy cập vào bộ đệm ẩn hiện để tôi có thể bắt đầu rối tung với bộ đệm dữ liệu pixel cơ bản của BufferedImage?

Tôi đã dành thời gian để viết một đoạn mã đang hoạt động (có vẻ thú vị), vì vậy tôi thực sự đánh giá cao câu trả lời thực sự trả lời (câu hỏi của tôi) và giải thích cái này hoạt động như thế nào -liners and snarky comments;)

Đây là một đoạn mã hoạt động đã trả về một hình vuông trên JFrame. Tôi muốn biết về các cách khác nhau có thể được sử dụng để chuyển đổi đoạn mã này để nó sử dụng bộ đệm đôi. Lưu ý rằng cách tôi xóa màn hình và vẽ lại hình vuông không phải là hiệu quả nhất nhưng điều này thực sự không phải là câu hỏi này là gì (theo một cách nào đó, tốt hơn là vì ví dụ này là hơi chậm một chút).). Về cơ bản, tôi cần sửa đổi liên tục nhiều pixel trong BufferedImage (như có một số loại hoạt ảnh) và tôi không muốn xem các tạo phẩm trực quan do đệm một lần trên màn hình.

Tôi có một JLabel có Biểu tượng là một ImageIcon gói một BufferedImage. Tôi muốn sửa đổi BufferedImage đó.

Điều gì phải được thực hiện để điều này trở thành bộ đệm đôi?

Tôi hiểu rằng bằng cách nào đó "hình ảnh 1" sẽ được hiển thị trong khi tôi sẽ vẽ trên "hình ảnh 2". Nhưng sau đó khi tôi vẽ xong trên "hình ảnh 2", làm cách nào để "nhanh" thay thế "hình ảnh 1" bởi "hình ảnh 2"?

Đây có phải là điều tôi nên làm theo cách thủ công, chẳng hạn như, bằng cách hoán đổi ImageIcon của JLabel?

Tôi có nên luôn luôn vẽ trong cùng một BufferedImage sau đó làm một 'blit' nhanh chóng của các điểm ảnh BufferedImage trong BufferedImage của ImageLcon của JLabel? (Tôi đoán không và tôi không thấy làm thế nào tôi có thể "đồng bộ" điều này với "dòng trống dọc" của màn hình [hoặc tương đương trong màn hình phẳng: ý tôi là, để 'đồng bộ' mà không can thiệp vào thời điểm màn hình tự làm mới pixel, để ngăn chặn sự cắt xén]).

Còn các lệnh "repaint" thì sao? Tôi có giả sử kích hoạt bản thân mình không? Tôi nên gọi số nào repaint() hoặc cái gì khác?

Yêu cầu quan trọng nhất là tôi phải sửa đổi pixel trực tiếp trong bộ dữ liệu pixel của hình ảnh.

import javax.swing.*; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 
import java.awt.image.BufferedImage; 
import java.awt.image.DataBufferInt; 

public class DemosDoubleBuffering extends JFrame { 

    private static final int WIDTH = 600; 
    private static final int HEIGHT = 400; 

    int xs = 3; 
    int ys = xs; 

    int x = 0; 
    int y = 0; 

    final int r = 80; 

    final BufferedImage bi1; 

    public static void main(final String[] args) { 
     final DemosDoubleBuffering frame = new DemosDoubleBuffering(); 
     frame.addWindowListener(new WindowAdapter() { 
      public void windowClosing(WindowEvent e) { 
       System.exit(0); 
      } 
     }); 
     frame.setSize(WIDTH, HEIGHT); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public DemosDoubleBuffering() { 
     super("Trying to do double buffering"); 
     final JLabel jl = new JLabel(); 
     bi1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); 
     final Thread t = new Thread(new Runnable() { 
      public void run() { 
       while (true) { 
        move(); 
        drawSquare(bi1); 
        jl.repaint(); 
        try {Thread.sleep(10);} catch (InterruptedException e) {} 
       } 
      } 
     }); 
     t.start(); 
     jl.setIcon(new ImageIcon(bi1)); 
     getContentPane().add(jl); 
    } 

    private void drawSquare(final BufferedImage bi) { 
     final int[] buf = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); 
     for (int i = 0; i < buf.length; i++) { 
      buf[i] = 0xFFFFFFFF; // clearing all white 
     } 
     for (int xx = 0; xx < r; xx++) { 
      for (int yy = 0; yy < r; yy++) { 
       buf[WIDTH*(yy+y)+xx+x] = 0xFF000000; 
      } 
     } 
    } 

    private void move() { 
     if (!(x + xs >= 0 && x + xs + r < bi1.getWidth())) { 
      xs = -xs; 
     } 
     if (!(y + ys >= 0 && y + ys + r < bi1.getHeight())) { 
      ys = -ys; 
     } 
     x += xs; 
     y += ys; 
    } 

} 

EDIT

Đây là không cho một ứng dụng Java toàn màn hình, nhưng một ứng dụng Java thông thường, chạy trong cửa sổ (hơi nhỏ) của riêng mình.

+2

Swing được đệm đôi theo mặc định. Đây không phải là AWT. – camickr

+0

Vẫn đang chờ ai đó đăng mã thực tế mà chúng tôi có thể kiểm tra xem liệu bộ đệm đôi có xứng đáng với nỗ lực không. – camickr

+1

Tôi đoán bạn đã hiểu nhầm swing. Bạn không thể truy cập bộ đệm ngoại tuyến (chưa được sửa đổi). Swing sẽ vượt qua bộ đệm để cha mẹ của bạn, và cha mẹ của bạn sẽ sửa đổi nó. ... Để làm những gì bạn muốn, bạn phải * disable * swing doublebuffering và làm điều đó với, BufferedImage. Nếu bạn muốn thực sự "bitblt" thực, bạn phải sử dụng thành phần "nặng" trong AWT. –

Trả lời

3

Nói chung, chúng tôi sử dụng lớp Canvas phù hợp với hoạt ảnh trong Java. Anyhoo, sau đây là cách bạn đạt được bộ đệm đôi:

class CustomCanvas extends Canvas { 
    private Image dbImage; 
    private Graphics dbg; 
    int x_pos, y_pos; 

    public CustomCanvas() { 

    } 

    public void update (Graphics g) { 
    // initialize buffer 
    if (dbImage == null) { 

     dbImage = createImage (this.getSize().width, this.getSize().height); 
     dbg = dbImage.getGraphics(); 

    } 

    // clear screen in background 
    dbg.setColor (getBackground()); 
    dbg.fillRect (0, 0, this.getSize().width, this.getSize().height); 

    // draw elements in background 
    dbg.setColor (getForeground()); 
    paint (dbg); 

    // draw image on the screen 
    g.drawImage (dbImage, 0, 0, this); 
    } 

     public void paint (Graphics g) 
{ 

     g.setColor (Color.red); 



     g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius); 

    } 
} 

Bây giờ bạn có thể cập nhật x_pos và y_pos từ một chuỗi, sau đó gọi lại 'repaint' trên đối tượng canvas. Kỹ thuật tương tự cũng hoạt động trên JPanel.

+0

@Usman Saleem: Chào Saleem, thật tuyệt khi thấy một người mới đến giúp đỡ ở đây :) Khi làm việc với Canvas, tôi có thể truy cập vào bộ dữ liệu của pixel theo cách tương tự với BufferedImage không? – SyntaxT3rr0r

+0

Không nên gọi lại cuộc gọi lại trong EDT? – Riduidel

+1

Nhưng không cần thiết cho điều này. Swing cũng cho phép hoạt ảnh. Đó không phải là đệm đôi. Hoặc nếu nó là cùng một mã có thể được sử dụng trên một JPanel Swing vì tất cả những gì bạn đang làm là vẽ một hình ảnh. – camickr

11

---- Edited để giải quyết mỗi thiết lập điểm ảnh ----

Mục đòn quyết đệm đôi, nhưng cũng có một vấn đề về cách để có được điểm ảnh thành một BufferedImage.

Nếu bạn gọi

WriteableRaster raster = bi.getRaster() 

trên BufferedImage nó sẽ trả về một WriteableRaster. Từ đó, bạn có thể sử dụng

int[] pixels = new int[WIDTH*HEIGHT]; 
// code to set array elements here 
raster.setPixel(0, 0, pixels); 

Lưu ý rằng bạn có thể muốn tối ưu hóa mã để không thực sự tạo mảng mới cho mỗi lần hiển thị. Ngoài ra, bạn có thể muốn tối ưu hóa mã thanh toán bù trừ mảng để không sử dụng vòng lặp for.

Arrays.fill(pixels, 0xFFFFFFFF); 

có lẽ sẽ hoạt động tốt hơn cài đặt vòng lặp của bạn nền trắng.

---- Chỉnh sửa sau khi phản ứng ----

Điều quan trọng là trong thiết lập ban đầu của bạn của JFrame và bên trong chạy render vòng lặp.

Trước tiên, bạn cần phải thông báo SWING để dừng Rasterizing bất cứ khi nào nó muốn; bởi vì, bạn sẽ nói với nó khi bạn vẽ xong hình ảnh đệm mà bạn muốn hoán đổi đầy đủ. Thực hiện việc này với

setIgnoreRepaint(true); 

của JFrame Sau đó, bạn sẽ muốn tạo chiến lược đệm. Về cơ bản nó chỉ rõ có bao nhiêu bộ đệm bạn muốn sử dụng

createBufferStrategy(2); 

Bây giờ bạn đã cố gắng để tạo ra các chiến lược đệm, bạn cần phải lấy các đối tượng BufferStrategy vì bạn sẽ cần nó sau này chuyển sang bộ đệm.

final BufferStrategy bufferStrategy = getBufferStrategy(); 

Bên trong bạn Thread thay đổi run() vòng lặp để chứa:

... 
    move(); 
    drawSqure(bi1); 
    Graphics g = bufferStrategy.getDrawGraphics(); 
    g.drawImage(bi1, 0, 0, null); 
    g.dispose(); 
    bufferStrategy.show(); 
... 

Các đồ họa túm lấy từ BufferStrategy sẽ là đối tượng tắt màn Graphics, khi tạo đệm ba, nó sẽ là "bên cạnh "ngoài màn hình Graphics đối tượng theo kiểu vòng tròn.

Hình ảnh và ngữ cảnh Đồ họa không liên quan đến tình huống ngăn chặn và bạn đã nói với Swing bạn sẽ tự vẽ bản vẽ, vì vậy bạn phải vẽ hình ảnh theo cách thủ công. Điều này không phải luôn luôn là một điều xấu, vì bạn có thể chỉ định bộ đệm lật khi hình ảnh được vẽ hoàn toàn (và không phải trước đó).

Việc vứt bỏ đối tượng đồ họa chỉ là ý tưởng tốt vì nó giúp thu gom rác thải.Hiển thị bufferStrategy sẽ lật bộ đệm.

Mặc dù có thể có lỗi ở đâu đó trong mã ở trên, điều này sẽ giúp bạn có được 90% con đường ở đó. Chúc may mắn!

---- gốc bài sau ----

Nó có vẻ ngớ ngẩn để chỉ một câu hỏi như vậy để hướng dẫn javase, nhưng bạn đã nhìn vào BufferStrategy and BufferCapatbilites?

Vấn đề chính tôi nghĩ bạn đang gặp phải là bạn bị lừa bởi tên của Hình ảnh. Một BufferedImage không có gì để làm với đệm đôi, nó phải làm với "đệm dữ liệu (thường là từ đĩa) trong bộ nhớ." Như vậy, bạn sẽ cần hai BufferedImages nếu bạn muốn có một "hình ảnh đệm đôi"; vì nó là không khôn ngoan để thay đổi điểm ảnh trong hình ảnh đang được hiển thị (nó có thể gây ra vấn đề sơn lại).

Trong mã kết xuất của bạn, bạn lấy đối tượng đồ họa. Nếu bạn thiết lập bộ đệm đôi theo hướng dẫn ở trên, điều này có nghĩa là bạn sẽ lấy (theo mặc định) đối tượng ngoài màn hình Graphics và tất cả bản vẽ sẽ không có màn hình. Sau đó, bạn vẽ hình ảnh của bạn (một trong những quyền của khóa học) cho đối tượng ngoài màn hình. Cuối cùng, bạn nói chiến lược với show() bộ đệm và nó sẽ thay thế ngữ cảnh Đồ họa cho bạn.

+0

@Edwin Buck: +1 để được trợ giúp nhưng tôi không sử dụng BufferedImage vì tên sẽ lừa tôi: Tôi đang sử dụng chúng vì khả năng thao tác pixel trực tiếp trong hình ảnh là yêu cầu và cách duy nhất tôi biết truy cập vào bộ đệm dữ liệu pixel bên dưới của hình ảnh là sử dụng BufferedImage. – SyntaxT3rr0r

+0

@SpoonBender tốt, tôi đã suy nghĩ một chút khi bạn cần hai BufferedImages, thực sự bạn có thể nhận được chỉ với một vì bạn cần phải vẽ nó vào từng bộ đệm riêng lẻ. Tôi cập nhật bài viết của tôi cho biết chi tiết về làm thế nào để có thể tích hợp hỗ trợ đệm đôi của Swing vào ví dụ của bạn. –

+0

Đây có lẽ là chiến lược tốt hơn sau đó là chiến lược tôi đã đăng. Kỹ thuật tương tự này cũng được lớp Canvas sử dụng cho nhiều bộ đệm (và tôi nghĩ đây là kỹ thuật mà tôi đã sử dụng từ lâu trong một dự án hoạt hình cho sinh viên của tôi trong Java). –

3

Đây là biến thể trong đó tất cả bản vẽ diễn ra trên event dispatch thread.

Phụ Lục:

Về cơ bản, tôi cần phải liên tục thay đổi rất nhiều điểm ảnh trong một BufferedImage ...

này kinetic model minh họa một vài phương pháp để hình ảnh động pixel.

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics2D; 
import java.awt.GridLayout; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.*; 
import java.awt.image.BufferedImage; 

/** @see http://stackoverflow.com/questions/4430356 */ 
public class DemosDoubleBuffering extends JPanel implements ActionListener { 

    private static final int W = 600; 
    private static final int H = 400; 
    private static final int r = 80; 
    private int xs = 3; 
    private int ys = xs; 
    private int x = 0; 
    private int y = 0; 
    private final BufferedImage bi; 
    private final JLabel jl = new JLabel(); 
    private final Timer t = new Timer(10, this); 

    public static void main(final String[] args) { 
     EventQueue.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       JFrame frame = new JFrame(); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.add(new DemosDoubleBuffering()); 
       frame.pack(); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    public DemosDoubleBuffering() { 
     super(true); 
     this.setLayout(new GridLayout()); 
     this.setPreferredSize(new Dimension(W, H)); 
     bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB); 
     jl.setIcon(new ImageIcon(bi)); 
     this.add(jl); 
     t.start(); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     move(); 
     drawSquare(bi); 
     jl.repaint(); 
    } 

    private void drawSquare(final BufferedImage bi) { 
     Graphics2D g = bi.createGraphics(); 
     g.setColor(Color.white); 
     g.fillRect(0, 0, W, H); 
     g.setColor(Color.blue); 
     g.fillRect(x, y, r, r); 
     g.dispose(); 
    } 

    private void move() { 
     if (!(x + xs >= 0 && x + xs + r < bi.getWidth())) { 
      xs = -xs; 
     } 
     if (!(y + ys >= 0 && y + ys + r < bi.getHeight())) { 
      ys = -ys; 
     } 
     x += xs; 
     y += ys; 
    } 
} 
3

Điều bạn muốn về cơ bản là không thể ở chế độ cửa sổ có Swing. Không có hỗ trợ cho đồng bộ hóa raster cho các cửa sổ repaints, điều này chỉ có sẵn trong chế độ toàn màn hình (và thậm chí sau đó có thể không được hỗ trợ bởi tất cả các nền tảng).

Các thành phần Swing được đệm đôi theo mặc định, nghĩa là chúng sẽ thực hiện tất cả hiển thị cho bộ đệm trung gian và bộ đệm đó cuối cùng được sao chép vào màn hình, tránh nhấp nháy từ xóa nền và sau đó vẽ lên trên nó. Và đó là chiến lược duy nhất được hỗ trợ tốt trên tất cả các nền tảng cơ bản. Nó tránh chỉ lặp lại nhấp nháy, nhưng không bị rách mắt khỏi các yếu tố đồ họa chuyển động. Một cách hợp lý đơn giản để có quyền truy cập vào các điểm ảnh thô của một khu vực hoàn toàn dưới sự kiểm soát của bạn sẽ là mở rộng một thành phần tùy chỉnh từ JComponent và ghi đè lên phương thức paintComponent() của nó để vẽ khu vực từ BufferedImage (từ bộ nhớ). :

public class PixelBufferComponent extends JComponent { 

    private BufferedImage bufferImage; 

    public PixelBufferComponent(int width, int height) { 
     bufferImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 
     setPreferredSize(new Dimension(width, height)); 
    } 

    public void paintComponent(Graphics g) { 
     g.drawImage(bufferImage, 0, 0, null); 
    } 

} 

Sau đó, bạn có thể thao tác bạn đệm hình ảnh theo bất kỳ cách nào bạn muốn. Để các thay đổi của bạn hiển thị trên màn hình, chỉ cần gọi repaint() trên đó. Nếu bạn thực hiện thao tác pixel từ một chủ đề khác với EDT, bạn cần HAI hình ảnh đệm để đối phó với các điều kiện chủng tộc giữa repaint thực tế và chuỗi thao tác của bạn.

Lưu ý rằng bộ xương này sẽ không vẽ toàn bộ khu vực của thành phần khi được sử dụng với trình quản lý bố cục kéo dài thành phần vượt quá kích thước ưa thích của nó.

Cũng lưu ý, phương pháp tiếp cận hình ảnh đệm chủ yếu chỉ có ý nghĩa nếu bạn thực hiện thao tác pixel ở mức độ thấp thông qua setRGB (...) trên hình ảnh hoặc trực tiếp truy cập trực tiếp vào DataBuffer. Nếu bạn có thể thực hiện tất cả các thao tác sử dụng các phương thức của Graphics2D, bạn có thể thực hiện tất cả các công cụ trong phương thức paintComponent bằng cách sử dụng đồ họa được cung cấp (đây thực sự là một Graphics2D và có thể được đúc đơn giản).

Các vấn đề liên quan