Графика Java2D внутри JScrollPanel не очень хороша
Я студент компьютерных наук в Нидерландах и работаю над заданием, в котором мне нужно нарисовать пару фигур. Чтобы эти фигуры выглядели лучше, я включил сглаживание подсказок рендеринга. Но теперь моя проблема в том, что, когда я прокручиваю свою панель JScrollPanel туда, где фигуры не отображаются на экране, а затем прокручиваю назад, кажется, что сглаживание и прозрачность не работают правильно.
На изображении, связанном ниже, я прокрутил до половины форму, а затем прокрутил назад.
Я действительно понятия не имею о том, где находится проблема, поэтому я предоставил полный код ниже класса.
Вы можете скачать полную папку проекта eclipse здесь.
Я хотел бы заранее поблагодарить вас,
Милан против Дейка
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
@SuppressWarnings("serial")
public class AgendaPanel extends JPanel{
private final int WIDTH = 24; // width in hours
private final Color BUTTON_TOP_GRADIENT = new Color(250, 250, 250);
private final Color BUTTON_BOTTOM_GRADIENT = new Color(210, 210, 210);
private ArrayList<Line> lines = new ArrayList<Line>();
private ArrayList<Shape> shadowBoxes = new ArrayList<Shape>();
private ArrayList<Shape> itemBoxes = new ArrayList<Shape>();
private ArrayList<Image2D> images = new ArrayList<Image2D>();
private ArrayList<String2D> names = new ArrayList<String2D>();
public AgendaPanel(){
setPreferredSize(new Dimension(WIDTH*100, 300));
drawGrid();
}
public void drawGrid(){
for(int i=100; i < WIDTH*100; i+=100){
lines.add(new Line(i,0,i,300,Color.GRAY));
}
for(int i=50; i < WIDTH*100; i+=100){
lines.add(new Line(i,0,i,300, Color.LIGHT_GRAY));
}
// change to the amount of podia
for(int i=75; i < 300; i+=75){
lines.add(new Line(0, i, WIDTH*100, i, Color.black));
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
RenderingHints qualityHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
qualityHints.put(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
g2.setRenderingHints( qualityHints );
// Add agenda items to the draw queue
addAgendaItem("logo.png", "Minus Militia", 2, 2, 170);
//draw time grid
for(Line line : lines){
g2.setColor(line.color);
g2.drawLine(line.x1, line.y1, line.x2, line.y2);
}
//Shape roundRect = new RoundRectangle2D.Double(2, 2, 170, 71, 20, 20);
//Paint all shadow shapes as the first layer over the grid with a transparacy of 30%
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.3f));
for (Shape shadow : shadowBoxes){
paintBorderShadow(g2,4,shadow);
}
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,1f));
//Paint all boxes as second layer over the shadows
g2.setStroke(new BasicStroke());
g2.setPaint(new GradientPaint( new Point(0, 0),
BUTTON_TOP_GRADIENT,
new Point(0, 71),
BUTTON_BOTTOM_GRADIENT));
for (Shape box : itemBoxes){
g2.fill(box);
}
// Paint as third layer all the images over the boxes
for (Image2D image : images){
g2.drawImage(image.getImage() , image.getX(), image.getY(), null);
}
g2.setColor(Color.black);
//Paint all names as fourth and last layer
for (String2D name : names){
g2.drawString(name.getText(), name.getX(), name.getY());
}
}
private void addAgendaItem(String imageFile,String name, int x, int y, int length){
shadowBoxes.add(new RoundRectangle2D.Double(x+3, y+3, length-3, 68, 20, 20));
itemBoxes.add(new RoundRectangle2D.Double(x, y, length, 71, 20, 20));
BufferedImage in = null;
try {
in = ImageIO.read(new File("logo.png"));
} catch (IOException e) {
e.printStackTrace();
}
images.add(new Image2D(x+8, y+8 ,drawPicture(scaleImage(in, 55, 55,Color.black),20)));
names.add(new String2D(x+73,y+38, name));
}
private BufferedImage drawPicture(BufferedImage image, int cornerRadius){
int w = image.getWidth();
int h = image.getHeight();
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = output.createGraphics();
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
g2.setComposite(AlphaComposite.SrcAtop);
g2.drawImage(image, 0, 0, null);
g2.dispose();
return output;
}
public BufferedImage scaleImage(BufferedImage img, int width, int height, Color background) {
int imgWidth = img.getWidth();
int imgHeight = img.getHeight();
if (imgWidth*height < imgHeight*width) {
width = imgWidth*height/imgHeight;
} else {
height = imgHeight*width/imgWidth;
}
BufferedImage output = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = output.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setBackground(background);
g.clearRect(0, 0, width, height);
g.drawImage(img, 0, 0, width, height, null);
} finally {
g.dispose();
}
return output;
}
private void paintBorderShadow(Graphics2D g2, int shadowWidth, Shape shape) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int sw = shadowWidth*2;
for (int i=sw; i >= 2; i-=2) {
float pct = (float)(sw - i) / (sw - 1);
g2.setColor(getMixedColor(Color.DARK_GRAY, pct,
Color.DARK_GRAY, 1.0f-pct));
g2.setStroke(new BasicStroke(i));
g2.draw(shape);
}
}
private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) {
float[] clr1 = c1.getComponents(null);
float[] clr2 = c2.getComponents(null);
for (int i = 0; i < clr1.length; i++) {
clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2);
}
return new Color(clr1[0], clr1[1], clr1[2], clr1[3]);
}
}
Классы Image2D, String2D и Line:
public class String2D {
private int x = 0;
private int y = 0;
private String text = "";
public String2D(int x, int y, String text){
this.x = x;
this.y = y;
this.text = text;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
import java.awt.image.BufferedImage;
public class Image2D {
private int x = 0;
private int y = 0;
private BufferedImage image = null;
public Image2D(int x, int y, BufferedImage image){
this.x = x;
this.y = y;
this.image = image;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
}
import java.awt.*;
public class Line{
public int x1;
public int x2;
public int y1;
public int y2;
public Color color;
public Line(int x1, int y1, int x2, int y2, Color color){
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
this.color = color;
}
}
2 ответа
После личной эврики я узнал, в чём была моя проблема. Я изменяю размеры и делаю изображения закругленными углами при каждом перекрашивании, это изменило объект g2, так или иначе отключив мои renderhints. Я поместил этот код в конструктор, и теперь все работает гладко.
Код ниже был проблемой:
private BufferedImage drawPicture(BufferedImage image, int cornerRadius){
int w = image.getWidth();
int h = image.getHeight();
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = output.createGraphics();
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
g2.setComposite(AlphaComposite.SrcAtop);
g2.drawImage(image, 0, 0, null);
g2.dispose();
return output;
}
public BufferedImage scaleImage(BufferedImage img, int width, int height, Color background) {
int imgWidth = img.getWidth();
int imgHeight = img.getHeight();
if (imgWidth*height < imgHeight*width) {
width = imgWidth*height/imgHeight;
} else {
height = imgHeight*width/imgWidth;
}
BufferedImage output = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = output.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setBackground(background);
g.clearRect(0, 0, width, height);
g.drawImage(img, 0, 0, width, height, null);
} finally {
g.dispose();
}
return output;
}
Я назвал это в рутине компонента краски:
addAgendaItem("logo.png", "Minus Militia", 2, 2, 170);
Какой это метод:
private void addAgendaItem(String imageFile,String name, int x, int y, int length){
shadowBoxes.add(new RoundRectangle2D.Double(x+3, y+3, length-3, 68, 20, 20));
itemBoxes.add(new RoundRectangle2D.Double(x, y, length, 71, 20, 20));
BufferedImage in = null;
try {
in = ImageIO.read(new File("logo.png"));
} catch (IOException e) {
e.printStackTrace();
}
images.add(new Image2D(x+8, y+8 ,drawPicture(scaleImage(in, 55, 55,Color.black),20)));
names.add(new String2D(x+73,y+38, name));
}
И это вызывает два вышеупомянутых метода (каждый перекрас), что привело к моим проблемам с производительностью и рисованием.
Это один из способов, и иногда он может быть более эффективным процессором (а также потреблять больше памяти), однако, это обходит проблему.
Проблема существует, потому что вы меняете состояние объекта Graphics, предоставленного в качестве параметра, на метод paintComponent. Это распространенная ошибка, которая вызывает различные странные графические ошибки.
Причина, по которой вы не должны изменять состояние объекта, заключается в том, что этот объект передается вашему компоненту его родительским компонентом, который получил его от своего родительского компонента, и так далее. Каждый компонент в цепочке, вплоть до собственного окна, использует один и тот же объект Graphics для рисования самого себя. Этот же графический объект также будет автоматически передан любым дочерним элементам вашего компонента. Это основа того, как работает Swing (обсуждение того, как работает Swing по сравнению с AWT или даже SWT, является совершенно другой темой).
Вместо этого вам следует сохранить государство. Это можно сделать одним из двух способов:
- Перед возвратом метода (т. Е. Последние строки кода в методе) установите все, что вы изменили, обратно в прежнее состояние (в вашем случае на подсказки рендеринга и т. Д.). Это хороший способ, если у вас есть только несколько изменений в состоянии графического объекта.
- Создайте копию объекта Graphics в начале метода, используя его метод create, используйте его вместо исходного объекта для рисования и избавьтесь от него в конце метода, используя его метод dispose.