2012-02-15 52 views
14

Từ câu hỏi ban đầu (dưới đây), tôi hiện đang cung cấp một tiền thưởng sau:Java - bảng điều khiển góc tròn với compositing trong paintComponent

Một AlphaComposite giải pháp dựa cho các góc tròn.

  • Vui lòng chứng minh bằng JPanel.
  • Góc phải hoàn toàn trong suốt.
  • Phải có khả năng hỗ trợ sơn JPG, nhưng góc vẫn làm tròn
  • Không được sử dụng setClip (hoặc bất kỳ clipping)
  • Phải có hiệu suất khá

Hy vọng rằng một ai đó nhặt này lên nhanh chóng, có vẻ như dễ dàng.

Tôi cũng sẽ trao tiền thưởng nếu có lý do chính đáng giải thích tại sao điều này không bao giờ có thể được thực hiện, mà những người khác đồng ý.

Đây là một hình ảnh mẫu của những gì tôi có trong tâm trí (nhưng sử dụng AlphaComposite) enter image description here


gốc câu hỏi

Tôi đã cố gắng tìm ra một cách để làm góc tròn sử dụng tổng hợp, rất giống với How to make a rounded corner image in Java hoặc http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker.html.

Tuy nhiên, nỗ lực của tôi không có BufferedImage trung gian không hoạt động - kết hợp điểm đến được làm tròn dường như không ảnh hưởng đến nguồn. Tôi đã thử những thứ khác nhau nhưng không có gì hiệu quả. Nên nhận được một hình chữ nhật màu đỏ tròn, thay vào đó tôi nhận được một hình vuông.

Vì vậy, tôi có hai câu hỏi, thực sự:

1) Có cách nào để thực hiện công việc này không?

2) Ảnh trung gian có thực sự tạo hiệu suất tốt hơn không?

SSCCE:

bảng

kiểm tra TPanel

import java.awt.AlphaComposite; 
import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 

import javax.swing.JLabel; 

public class TPanel extends JLabel { 
int w = 300; 
int h = 200; 

public TPanel() { 
    setOpaque(false); 
    setPreferredSize(new Dimension(w, h)); 
     setMaximumSize(new Dimension(w, h)); 
     setMinimumSize(new Dimension(w, h)); 
} 

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

    // Yellow is the clipped area. 
    g2d.setColor(Color.yellow); 
    g2d.fillRoundRect(0, 0, w, h, 20, 20); 
    g2d.setComposite(AlphaComposite.Src); 

    // Red simulates the image. 
    g2d.setColor(Color.red); 
    g2d.setComposite(AlphaComposite.SrcAtop); 

    g2d.fillRect(0, 0, w, h); 
    } 
} 

Sandbox của nó

import java.awt.Dimension; 
import java.awt.FlowLayout; 

import javax.swing.JFrame; 

public class Sandbox { 
public static void main(String[] args) { 
    JFrame f = new JFrame(); 
     f.setMinimumSize(new Dimension(800, 600)); 
     f.setLocationRelativeTo(null); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.setLayout(new FlowLayout()); 

     TPanel pnl = new TPanel(); 
     f.getContentPane().add(pnl); 

     f.setVisible(true); 
    } 
} 
+0

vui lòng đọc câu hỏi về [JButton and Graphics] (http://stackoverflow.com/users/584862/mre?tab=questions) bởi @mre – mKorbel

+0

Bạn đang tham khảo http://stackoverflow.com/questions/8416295/thành phần-tranh-bên ngoài-tùy chỉnh-biên giới? Điều đó đề cập đến màu sắc rắn được lấp đầy vào đồ họa, chứ không phải hình ảnh ... Tôi đang tìm cách sử dụng hình dạng tổng hợp để đặt mặt nạ, sau đó vẽ một hình ảnh lên trên nó. – Ben

Trả lời

7

Liên quan đến hiệu suất của bạn liên quan đến bài viết Java 2D Trickery chứa liên kết đến lời giải thích rất tốt của Chet Haase về cách sử dụng Intermediate Images.

Tôi cho rằng đoạn trích sau đây từ các lớp học nền tảng của O'Reilly Java trong Nutshell có thể hữu ích cho bạn để hiểu rõ hành vi AlphaComposite và tại sao hình ảnh trung gian có thể là kỹ thuật cần thiết để sử dụng.

Các AlphaComposite Rules Compositing

Các SRC_OVER quy tắc hợp lại rút ra một thể mờ màu nguồn so với màu sắc đích. Đây là những gì chúng tôi thường muốn xảy ra khi chúng tôi thực hiện thao tác đồ họa. Nhưng đối tượng AlphaComposite thực sự cho phép kết hợp màu sắc theo bảy quy tắc khác .

Trước khi chúng tôi xem xét các quy tắc tổng hợp chi tiết, có một điểm quan trọng là bạn cần hiểu. Màu sắc hiển thị trên màn hình không bao giờ có kênh alpha. Nếu bạn có thể thấy một màu thì đó là màu mờ đục. Giá trị màu chính xác có thể đã được chọn dựa trên tính toán minh bạch , nhưng khi màu đó được chọn, màu nằm trong bộ nhớ của thẻ video ở đâu đó và không có giá trị alpha liên kết với nó. Nói cách khác, với màn hình trên màn hình, pixel đích luôn có giá trị alpha là 1.0.

Tình huống khác khi bạn vẽ hình ảnh ngoài màn hình . Như bạn sẽ thấy khi chúng ta xem xét lớp Java 2D BufferedImage sau này trong chương này, bạn có thể chỉ định biểu diễn màu mong muốn khi bạn tạo ra một hình ảnh ngoài màn hình. Theo mặc định, đối tượng BufferedImage đại diện cho một hình ảnh dưới dạng một mảng các màu RGB, nhưng bạn cũng có thể tạo một hình ảnh là một mảng màu của ARGB. Hình ảnh như vậy có giá trị alpha được liên kết với nó và khi bạn vẽ thành hình ảnh , giá trị alpha vẫn được liên kết với pixel bạn vẽ .

Sự khác biệt này giữa trên màn hình và tắt màn hình bản vẽ rất quan trọng vì một số các quy tắc thực hiện hợp lại hợp lại dựa trên các giá trị alpha của các điểm ảnh đích, chứ không phải là giá trị alpha của các điểm ảnh nguồn.Với bản vẽ trên màn hình, pixel đích luôn mờ đục (với giá trị alpha là 1.0), nhưng với bản vẽ ngoài màn hình, đây không phải là trường hợp. Do đó, một số quy tắc tổng hợp chỉ hữu ích khi bạn vẽ thành hình ảnh ngoài màn hình có kênh alpha.

Để overgeneralize một chút, chúng ta có thể nói rằng khi bạn đang vẽ trên màn hình, bạn thường gắn bó với SRC_OVER mặc định hợp lại quy định, sử dụng màu sắc mờ đục, và thay đổi giá trị alpha được sử dụng bởi các đối tượng AlphaComposite. Tuy nhiên, khi làm việc với hình ảnh ngoài màn hình có các kênh alpha , bạn có thể sử dụng các quy tắc tổng hợp khác. Trong trường hợp này, bạn thường sử dụng các màu mờ và các hình ảnh mờ và một đối tượng AlphaComposite có giá trị alpha là 1.0.

+0

'Các màu được hiển thị trên màn hình không bao giờ có kênh alpha.' FTW. Không chắc tại sao không ai khác đóng đinh điều này trong hai tuần qua. Được trao, vì bạn đã giải quyết các mối quan tâm về hiệu suất và nguồn gốc của vấn đề (từ "nguồn đáng tin cậy và chính thức" không kém). Chúc mừng. – Ben

+0

BTW đọc bài báo đó trong "Khách hàng giàu có bẩn thỉu", điều này khiến tôi quan tâm đến vấn đề này ngay từ đầu: cười khúc khích: cụ thể là công cụ tổng hợp - anh ấy rất hào hứng về vật liệu tổng hợp. – Ben

4

tôi đã nhìn vào vấn đề này và không thể nhìn thấy làm thế nào để làm điều này trong một một cuộc gọi đến các lớp hệ thống.

Graphics2D là một phiên bản trừu tượng, được triển khai dưới dạng SunGraphics2D. Mã nguồn có sẵn tại ví dụ docjar và vì vậy chúng tôi có khả năng chỉ 'làm tương tự, nhưng khác' bằng cách sao chép một số mã. Tuy nhiên, các phương pháp vẽ hình ảnh phụ thuộc vào một số lớp 'ống' không có sẵn. Mặc dù bạn làm công cụ với việc nạp lớp, có thể bạn sẽ nhấn một số lớp được tối ưu hóa, không thể được thao tác để thực hiện phương pháp tối ưu về mặt lý thuyết; tất cả những gì bạn nhận được là vẽ hình ảnh như hình vuông.Tuy nhiên, chúng tôi có thể thực hiện một cách tiếp cận mà mã riêng của chúng ta, không phải là gốc (đọc: chậm?), Chạy ít nhất có thể, và không phụ thuộc vào kích thước hình ảnh mà đúng hơn là vùng thấp (tương đối) trong vòng rect. Ngoài ra, mà không sao chép các hình ảnh trong bộ nhớ, tiêu thụ rất nhiều bộ nhớ. Nhưng nếu bạn có nhiều bộ nhớ, thì rõ ràng, một hình ảnh được lưu trong bộ nhớ cache sẽ nhanh hơn sau khi cá thể đã được tạo.

Alternative 1:

import java.awt.Composite; 
import java.awt.CompositeContext; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.RenderingHints; 
import java.awt.image.BufferedImage; 
import java.awt.image.ColorModel; 
import java.awt.image.Raster; 
import java.awt.image.WritableRaster; 

import javax.swing.JLabel; 

public class TPanel2 extends JLabel implements Composite, CompositeContext { 
private int w = 300; 
private int h = 200; 

private int cornerRadius = 20; 
private int[] roundRect; // first quadrant 
private BufferedImage image; 
private int[][] first = new int[cornerRadius][]; 
private int[][] second = new int[cornerRadius][]; 
private int[][] third = new int[cornerRadius][]; 
private int[][] forth = new int[cornerRadius][]; 

public TPanel2() { 
    setOpaque(false); 
    setPreferredSize(new Dimension(w, h)); 
    setMaximumSize(new Dimension(w, h)); 
    setMinimumSize(new Dimension(w, h)); 

    // calculate round rect  
    roundRect = new int[cornerRadius]; 
    for(int i = 0; i < roundRect.length; i++) { 
     roundRect[i] = (int)(Math.cos(Math.asin(1 - ((double)i)/20))*20); // x for y 
    } 

    image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); // all black 
} 

@Override 
public void paintComponent(Graphics g) { 
    // discussion: 
    // We have to work with the passed Graphics object. 

    if(g instanceof Graphics2D) { 

     Graphics2D g2d = (Graphics2D) g; 

     // draw the whole image and save the corners 
     g2d.setComposite(this); 
     g2d.drawImage(image, 0, 0, null); 
    } else { 
     super.paintComponent(g); 
    } 
} 

@Override 
public CompositeContext createContext(ColorModel srcColorModel, 
     ColorModel dstColorModel, RenderingHints hints) { 
    return this; 
} 

@Override 
public void dispose() { 

} 

@Override 
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { 
    // lets assume image pixels >> round rect pixels 
    // lets also assume bulk operations are optimized 

    // copy current pixels 
    for(int i = 0; i < cornerRadius; i++) { 
     // quadrants 

     // from top to buttom 
     // first 
     first[i] = (int[]) dstOut.getDataElements(src.getWidth() - (cornerRadius - roundRect[i]), i, cornerRadius - roundRect[i], 1, first[i]); 

     // second 
     second[i] = (int[]) dstOut.getDataElements(0, i, cornerRadius - roundRect[i], 1, second[i]); 

     // from buttom to top 
     // third 
     third[i] = (int[]) dstOut.getDataElements(0, src.getHeight() - i - 1, cornerRadius - roundRect[i], 1, third[i]); 

     // forth 
     forth[i] = (int[]) dstOut.getDataElements(src.getWidth() - cornerRadius + roundRect[i], src.getHeight() - i - 1, cornerRadius - roundRect[i], 1, forth[i]); 
    } 

    // overwrite entire image as a square 
    dstOut.setRect(src); 

    // copy previous pixels back in corners 
    for(int i = 0; i < cornerRadius; i++) { 
     // first 
     dstOut.setDataElements(src.getWidth() - cornerRadius + roundRect[i], i, first[i].length, 1, second[i]); 

     // second 
     dstOut.setDataElements(0, i, second[i].length, 1, second[i]); 

     // third 
     dstOut.setDataElements(0, src.getHeight() - i - 1, third[i].length, 1, third[i]); 

     // forth 
     dstOut.setDataElements(src.getWidth() - cornerRadius + roundRect[i], src.getHeight() - i - 1, forth[i].length, 1, forth[i]); 
    } 
} 

} 

Phương án 2:

import java.awt.AlphaComposite; 
import java.awt.Composite; 
import java.awt.CompositeContext; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.RenderingHints; 
import java.awt.image.BufferedImage; 
import java.awt.image.ColorModel; 
import java.awt.image.Raster; 
import java.awt.image.WritableRaster; 
import javax.swing.JLabel; 

public class TPanel extends JLabel implements Composite, CompositeContext { 
private int w = 300; 
private int h = 200; 

private int cornerRadius = 20; 
private int[] roundRect; // first quadrant 
private BufferedImage image; 

private boolean initialized = false; 
private int[][] first = new int[cornerRadius][]; 
private int[][] second = new int[cornerRadius][]; 
private int[][] third = new int[cornerRadius][]; 
private int[][] forth = new int[cornerRadius][]; 

public TPanel() { 
    setOpaque(false); 
    setPreferredSize(new Dimension(w, h)); 
    setMaximumSize(new Dimension(w, h)); 
    setMinimumSize(new Dimension(w, h)); 

    // calculate round rect  
    roundRect = new int[cornerRadius]; 
    for(int i = 0; i < roundRect.length; i++) { 
     roundRect[i] = (int)(Math.cos(Math.asin(1 - ((double)i)/20))*20); // x for y 
    } 

    image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); // all black 
} 

@Override 
public void paintComponent(Graphics g) { 
    if(g instanceof Graphics2D) { 

     Graphics2D g2d = (Graphics2D) g; 

     // draw 1 + 2 rectangles and copy pixels from image. could also be 1 rectangle + 4 edges 
     g2d.setComposite(AlphaComposite.Src); 

     g2d.drawImage(image, cornerRadius, 0, image.getWidth() - cornerRadius - cornerRadius, image.getHeight(), null); 
     g2d.drawImage(image, 0, cornerRadius, cornerRadius, image.getHeight() - cornerRadius - cornerRadius, null); 
     g2d.drawImage(image, image.getWidth() - cornerRadius, cornerRadius, image.getWidth(), image.getHeight() - cornerRadius, image.getWidth() - cornerRadius, cornerRadius, image.getWidth(), image.getHeight() - cornerRadius, null); 

     // draw the corners using our own logic 
     g2d.setComposite(this); 

     g2d.drawImage(image, 0, 0, null); 

    } else { 
     super.paintComponent(g); 
    } 
} 

@Override 
public CompositeContext createContext(ColorModel srcColorModel, 
     ColorModel dstColorModel, RenderingHints hints) { 
    return this; 
} 

@Override 
public void dispose() { 

} 

@Override 
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { 
    // assume only corners need painting 

    if(!initialized) { 
     // copy image pixels 
     for(int i = 0; i < cornerRadius; i++) { 
      // quadrants 

      // from top to buttom 
      // first 
      first[i] = (int[]) src.getDataElements(src.getWidth() - cornerRadius, i, roundRect[i], 1, first[i]); 

      // second 
      second[i] = (int[]) src.getDataElements(cornerRadius - roundRect[i], i, roundRect[i], 1, second[i]); 

      // from buttom to top 
      // third 
      third[i] = (int[]) src.getDataElements(cornerRadius - roundRect[i], src.getHeight() - i - 1, roundRect[i], 1, third[i]); 

      // forth 
      forth[i] = (int[]) src.getDataElements(src.getWidth() - cornerRadius, src.getHeight() - i - 1, roundRect[i], 1, forth[i]); 
     } 
     initialized = true; 
    }  

    // copy image pixels into corners 
    for(int i = 0; i < cornerRadius; i++) { 
     // first 
     dstOut.setDataElements(src.getWidth() - cornerRadius, i, first[i].length, 1, second[i]); 

     // second 
     dstOut.setDataElements(cornerRadius - roundRect[i], i, second[i].length, 1, second[i]); 

     // third 
     dstOut.setDataElements(cornerRadius - roundRect[i], src.getHeight() - i - 1, third[i].length, 1, third[i]); 

     // forth 
     dstOut.setDataElements(src.getWidth() - cornerRadius, src.getHeight() - i - 1, forth[i].length, 1, forth[i]); 
    } 
} 

} 

Hope this helps, đây là phần nào của một giải pháp thứ hai tốt nhất nhưng đó là cuộc sống (đây là khi một số đồ họa-guru đến và chứng minh cho tôi sai (??) ..) ;-)

+0

+1, nhưng [mặt tối của mặt trăng] (http://stackoverflow.com/a/8420566/714968) – mKorbel

+0

Cảm ơn Thomas, +1 cho sự cố của bạn nhưng phải đi với ruột của tôi ở đây, nói @Mark McLaren là "chính xác nhất". – Ben

+0

Tha thứ cho tôi vì không sử dụng AlphaComposite.SrcATop trực tiếp, tôi đã nghĩ bạn hầu như sẽ quan tâm đến MỘT GIẢI PHÁP LÀM VIỆC ĐẶC BIỆT. Ở trên dường như để thực hiện 5 trong số 5 tiêu chí cho một rect tròn, và bạn sẽ có thể thêm các cạnh antialiasing/phai mờ với ít nỗ lực. Vì vậy, nhiều cho thực dụng. – ThomasRS