2012-02-15 10 views
14

z oryginalnego pytanie (poniżej), teraz jestem oferując nagrodę za:Java - zaokrąglony panel narożny z tworzenia kompozycji w paintComponent

AlphaComposite rozwiązaniem opartym o zaokrąglonych rogach.

  • Proszę wykazać z JPanel.
  • Narożniki muszą być całkowicie przezroczyste.
  • Musi być w stanie wspierać JPG malowanie, ale nadal mają zaokrąglone rogi
  • Nie wolno używać setClip (lub dowolny wycinek)
  • Musi mieć przyzwoitą wydajność

Mam nadzieję, że ktoś odbiera ten się szybko, jak się wydaje łatwo.

Będę także nagradzać nagrodę, jeżeli istnieje dobrze wyjaśniony powód, dla którego nie można tego zrobić, z którym inni się zgadzają.

Oto obraz próbka tego, co mam na myśli (ale przy użyciu AlphaComposite) enter image description here


Original pytanie

Próbowałem wymyślić jakiś sposób to zrobić zaokrąglone narożniki za pomocą kompozytu, bardzo podobne do How to make a rounded corner image in Java lub .

Jednak moje próby bez pośredniego obrazu BufferedImage nie działają - zaokrąglony docelowy kompozyt najwyraźniej nie wpływa na źródło. Próbowałem różnych rzeczy, ale nic nie działa. Powinienem otrzymać zaokrąglony czerwony prostokąt, zamiast tego otrzymuję kwadratowy.

Tak, mam dwa pytania, naprawdę:

1) Czy istnieje sposób, aby to działało?

2) Czy obraz pośredni rzeczywiście wygeneruje lepszą wydajność?

SSCCE:

panel

testy 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); 
    } 
} 

i jego Sandbox

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

proszę przeczytać pytania dotyczące [JButton i Graphics] (http://stackoverflow.com/users/584862/mre?tab=questions) przez @mre – mKorbel

+0

to dotyczy http://stackoverflow.com/questions/8416295/element-painting-outside-custom-border? To dotyczy stałych kolorów wypełnianych grafiką, a nie obrazów ... Szukam sposobu użycia złożonego kształtu, aby ustawić maskę, a następnie pomalować na niej obraz. – Ben

Odpowiedz

7

Co do twojego wykonania dotyczy artykułu Java 2D Trickery zawiera link do bardzo dobrego wyjaśnienia Cheta Haase'a na temat użycia Intermediate Images.

myślę następujący fragment klas O'Reilly Foundation Java w pigułce może być pomocne dla Ciebie, aby zrozumieć zachowania AlphaComposite i dlaczego obrazy pośrednie mogą być konieczne technika w użyciu.

AlphaComposite Zasady kompozycjonowania zasada tworzenia kompozycji

SRC_OVER rysuje ewentualnie źródło półprzezroczysty kolor nad kolorem docelowym. Zazwyczaj chcemy, aby stało się, gdy wykonujemy operację graficzną. Ale obiekt AlphaComposite umożliwia także łączenie kolorów zgodnie z siedmioma innymi regułami .

Zanim przejdziemy do szczegółowego omówienia zasad kompozycji, należy koniecznie zrozumieć ważny punkt . Kolory wyświetlane na ekranie nigdy nie mają kanału alfa. Jeśli widzisz kolor, jest to nieprzezroczysty kolor . Dokładna wartość koloru mogła zostać wybrana na podstawie obliczenia przezroczystości , ale po wybraniu tego koloru, kolor znajduje się w pamięci karty wideo i nie ma powiązanej z nią wartości alfa . Innymi słowy, przy wyświetlaniu na ekranie rysunku , piksele docelowe mają zawsze wartości alfa równe 1,0.

Jednak sytuacja wygląda inaczej, gdy rysujesz obraz poza ekranem . Jak zobaczysz, gdy weźmiemy pod uwagę klasę Java 2D BufferedImage w dalszej części tego rozdziału, możesz określić żądaną reprezentację koloru podczas tworzenia obrazu poza ekranem. Domyślnie obiekt BufferedImage reprezentuje obraz jako tablicę kolorów RGB, , ale można również utworzyć obraz będący tablicą kolorów ARGB. Takie zdjęcie ma przypisane wartości alfa, a po narysowaniu na obrazów, wartości alfa pozostają powiązane z pikselami, które rysujesz.

To rozróżnienie na ekranie i poza ekranem rysunek jest ważny ponieważ niektóre z zasad compositing wykonać komponowanie podstawie wartości alfa przeznaczenia pikseli, niż alfa wartości pikseli źródłowych.W przypadku rysowania na ekranie docelowe piksele są zawsze nieprzezroczyste (z wartościami alfa 1,0), ale z poza ekranem rysunek nie musi tak być. W związku z tym niektóre reguły dotyczące kompozycji są użyteczne tylko wtedy, gdy rysujesz obrazy poza ekranem, które mają kanał alfa o wartości .

Aby overgeneralize nieco, można powiedzieć, że po pobraniu na ekranie, zazwyczaj trzymać z domyślnym SRC_OVER Compositing regułę, użyj nieprzezroczyste kolory i zmieniać wartość alfa używany przez obiekt AlphaComposite. Podczas pracy z obrazami spoza ekranu, które mają kanały alfa , można jednak użyć innych reguł kompozycji. W tym przypadku zwykle używasz przezroczystych kolorów i przezroczystych obrazów oraz obiektu AlphaComposite z wartością alfa równą 1,0.

+0

'Kolory wyświetlane na ekranie nigdy nie mają kanału alfa. FTW. Nie wiem, dlaczego nikt inny nie przybił tego w ciągu ostatnich dwóch tygodni. Przyznany, ponieważ zajmujesz się problemami z wydajnością i źródłem problemu (z "wiarygodnych i oficjalnych źródeł" nie mniej). Twoje zdrowie. – Ben

+0

BTW przeczytać ten artykuł w "Filthy Rich Clients", który zainteresował mnie przede wszystkim ten problem: chichot: w szczególności materiał kompozytowy - jest bardzo podekscytowany kompozytami. – Ben

4

ja się z tym problemem i nie może zobaczyć, jak to zrobić w sposób pojedyncze wywołanie do klas systemowych.

Graphics2D to abstrakcyjna instancja, zaimplementowana jako SunGraphics2D. Kod źródłowy jest dostępny na przykład pod numerem docjar, więc możemy potencjalnie po prostu "zrobić to samo, ale inaczej", poprzez skopiowanie kodu. Jednak metody malowania obrazu zależą od niektórych klas "rur", które nie są dostępne. Chociaż robisz rzeczy przy użyciu klas, prawdopodobnie trafisz na natywną, zoptymalizowaną klasę, której nie można zmanipulować, aby zrobić teoretycznie optymalne podejście; wszystko, co dostajesz, to malowanie obrazów jako kwadraty.

Możemy jednak stworzyć podejście, w którym nasz własny, nie-rodzimy (czytaj: wolny?) Kod działa tak mało jak to możliwe, a nie w zależności od rozmiaru obrazu, ale raczej (względnie) niskiego obszaru w rundzie rect. Ponadto, bez kopiowania zdjęć w pamięci, zużywa dużo pamięci. Ale jeśli masz dużo pamięci, to oczywiście buforowany obraz jest szybszy po utworzeniu instancji.

Alternatywa 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]); 
    } 
} 

} 

Alternatywa 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]); 
    } 
} 

} 

Nadzieja to pomaga, to jest w pewnym sensie najlepsze rozwiązanie, ale takie jest życie (to jest, gdy niektóre grafiki guru przychodzi i udowodni, że się mylę (??) ..) ;-)

+0

+1, ale [ciemna strona księżyca] (http://stackoverflow.com/a/8420566/714968) – mKorbel

+0

Dzięki Thomas, +1 za kłopot, ale muszę iść z moim tułowi tutaj, co mówi @ Mark McLaren jest "najbardziej poprawny". – Ben

+0

Proszę mi wybaczyć, że nie używam bezpośrednio AlphaComposite.SrcATop, sądziłem, że najbardziej zainteresuje Cię RÓWNOWAŻNE ROZWIĄZANIE ROBOCZE. Powyższe wydaje się spełniać 5 z 5 kryteriów dla okrągłego recta, a będziesz mógł dodawać antyaliasing/wyblakłe krawędzie przy niewielkim wysiłku. Tyle, że jest pragmatyczny. – ThomasRS

Powiązane problemy