Java中的渐变问题

13
在我的程序中,我想在JFrame上使用一个半透明的白色到透明的渐变来覆盖黄色背景。这个方案有效,由于程序设置原因,必须是从白色到透明。然而,当我把程序带到大学(从JRE7到我的JRE6),渐变会从白色到黑色再到透明……直到你开始增加白色颜色的不透明度才会变得更糟糕。有没有办法可以解决这个问题?
以下是我JFrame代码的相关部分。
public class DictionaryGUI extends JFrame
{   
    protected JPanel pGradientPane;

    //Interface gradient specification
    private Color pInterfaceColour = new Color(255, 245, 62);
    protected int iDegreeWhite = 180
    protected int iDegreeBlack = 0

    DictionaryGUI(int iWidth, int iHeight)
    {
        /*General definitions*/
        super(String.format("French Verb Conjugator - Version %s", MainLauncher.version));
        setSize(iWidth, iHeight);
        new Menu(this);

        this.iWidth = iWidth;    
        this.iHeight = iHeight;

        getContentPane().setBackground(pInterfaceColour);
        pGradientPane = new JPanel(new GridBagLayout())
        {
            private static final long serialVersionUID = 1L;

            protected void paintComponent(Graphics pGraphics) 
            {
                Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
                pGraphicsGradientRender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0, iDegreeBlack));
                pGraphicsGradientRender.setPaint(pGradient);
                pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
                super.paintComponent(pGraphics);
            }
        };
        pGradientPane.setOpaque(false);
        pGradientPane.setPreferredSize(new Dimension(iWidth - 16, iHeight - 62));
        /*components added to pGradientPane here!*/
        add(pGradientPane);
    }

以及主类也是:

public class MainLauncher
{
    static int iHeight = 400;
    static int iWidth = 730;
    static String version = "0A3B6";

    public static void main(String[] args)
    {
    try 
    {
        for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
        {
            if ("Nimbus".equals(info.getName()))
            {
                UIManager.setLookAndFeel(info.getClassName());
                break;
            }
        }
    } catch (Exception e) {e.printStackTrace();}
    DictionaryGUI window = new DictionaryGUI(iWidth, iHeight);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setLocationByPlatform(true);
    window.setVisible(true);
}

这只是JRE6和JRE7之间的一些差异吗?我也应该把底部的颜色改成白色吗?(原本是黑色,以防有人想要使底部变暗。)

如果有人需要,我可以明天发布一些截图....

渐变效果应该是什么样的

对于某些人来说,渐变效果实际上是什么样的

谢谢 Jamie

编辑: 我将渐变中的第二个(透明的)颜色改为了白色,并解决了问题。但是,我仍然困扰于为什么透明黑色的颜色会在中间显示出来?这一定与JRE7有关,因为它发生在那里...也许他们改变了渐变中透明度工作方式的某些内容。有人知道如何在保持黑色的情况下消除这个问题吗?


我包含了MainLauncher,因为我不知道如何集成它们,但这会成为一个问题吗? - J_mie6
嗯...好吧,你和我一样使用JRE6吗?我认为使用JRE7的机器会导致问题。我将发布一个屏幕截图,展示颜色应该是什么样子的。 - J_mie6
@J_mie6:我在这个问题上放了一个500声望值的悬赏,以了解更多关于这个问题的信息,并看看是否存在更好的解决方案。 - Hovercraft Full Of Eels
j_mie6: 我需要你的帮助 -- 谁应该得到奖金。我倾向于Nick Rippe。 - Hovercraft Full Of Eels
我同意你的观点,抱歉回复晚了,我的账户出了些问题:D - J_mie6
显示剩余4条评论
5个回答

10

这是我的版本,将您的代码作为sscce呈现:

import java.awt.*;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

public class MainLauncher {
   static int iHeight = 400;
   static int iWidth = 730;
   static String version = "0A3B6";

   public static void main(String[] args) {
      try {
         for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
               UIManager.setLookAndFeel(info.getClassName());
               break;
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      DictionaryGUI window = new DictionaryGUI(iWidth, iHeight);
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      window.setLocationByPlatform(true);
      window.setVisible(true);
   }

}

class DictionaryGUI extends JFrame {
   protected JPanel pGradientPane;

   // Interface gradient specification
   private Color pInterfaceColour = new Color(255, 245, 62);
   protected int iDegreeWhite = 180;
   protected int iDegreeBlack = 0;

   DictionaryGUI(int iWidth, int iHeight) {
      /* General definitions */
      super(String.format("French Verb Conjugator - Version %s",
            MainLauncher.version));
      setSize(iWidth, iHeight);

      getContentPane().setBackground(pInterfaceColour);
      pGradientPane = new JPanel() {
         private static final long serialVersionUID = 1L;

         protected void paintComponent(Graphics pGraphics) {
            Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
            pGraphicsGradientRender.setRenderingHint(
                  RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON);
            GradientPaint pGradient = new GradientPaint(0, 0, new Color(255,
                  255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0,
                  iDegreeBlack));
            pGraphicsGradientRender.setPaint(pGradient);
            pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
            super.paintComponent(pGraphics);
         }
      };
      pGradientPane.setOpaque(false);
      pGradientPane.setPreferredSize(new Dimension(iWidth - 16, iHeight - 62));
      /* components added to pGradientPane here! */
      add(pGradientPane);
   }
}

但是这并没有展示你的问题。我猜测你的问题是在使用透明背景与Swing GUI时绘画残留没有完全被修正。如果是这样,请阅读Rob Camick在他的博客中所说的内容:透明背景


我很难解释这个问题。在我的电脑上它完美无缺,但是当我在我的笔记本电脑或者同事的电脑上使用时(两者都安装了JRE7),它看起来不同。因此,我无法展示这个问题,因为它并非对所有人都会出现,但是一旦出现,它就一直存在... - J_mie6
1
@J_mie6:但是这个最小的代码会出现这个问题吗?还是必须要有在这个背景上面变化的组件。换句话说,你已经证明了这个SSCCE能够重现这个问题吗?还需要更多的东西吗? - Hovercraft Full Of Eels
是的,那段代码导致了问题...请看我上面的截图。 - J_mie6
1
@J_mie6:嗯...我看到了你上面的帖子中的问题(为你所有的努力加1分!),但由于我无法在我的系统上重现你的问题,所以我很难弄清楚为什么它不起作用。你有没有看过Rob Camick关于透明背景的博客?那可能会对你有所帮助。 - Hovercraft Full Of Eels
1
@J_mie6:我希望我知道答案,但是需要比我更有知识的人来回答这个问题。谢谢您接受我的答案,但事实上我并没有回答这个问题,所以您可能希望“取消接受”我的答案,看看是否有其他人可以提供更好的解决方案。如果没有好的答案出现,我考虑在一两天内提供悬赏。天知道我有足够的声望,也可以“花费”一下。 - Hovercraft Full Of Eels
显示剩余6条评论

7

代码中的问题在于这一行:

GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0, iDegreeBlack));

should be this:

GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(255, 245, 62, iDegreeWhite));

回顾您的问题,我发现您基本上已经找到了解决方案,但它有些不同。原因如下:
在渐变中混合颜色时,您正在混合颜色的所有方面:RBGA
您看,在达到完整的第二种颜色之前,您正在将黑色混合到颜色渐变中,并且该混合物不会具有完全透明度。因此,在页面下降20%时,您将拥有此颜色:204,204,204,144(即80%白色,20%黑色和56%不透明)。
最简单的解决方案是完全避免使用半透明性,如果您不使用它-只需从顶部的浅黄色混合到底部的深黄色。这样做也需要更少的资源。
但是,由于您正在使用透明度,所以我提供的解决方案也使用透明度。您将使用一致的透明度从白色混合到黄色。
如果您从白色混合到白色(透明),则与之前相同,只是变成了白色(由于它是您使用的颜色之一,因此不太明显):直到第二种颜色达到完全透明度之前,渐变将具有白色“条纹”。
至于为什么在不同的JVM上的行为不同,我猜测Oracle可能已经改变了alpha的混合方式。更好的alpha支持似乎是他们一直在努力的事情,这是朝着这个方向的一个逻辑步骤。尽管如此,我没有关于这个声明的任何证据-它只是基于我看到的其他alpha更改(比如透明窗口)。
编辑:这个SSCCE演示了问题和解决方案。
import java.awt.*;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;


public class TransparencyDemo extends Box{

    protected JPanel pGradientPane;

    //Interface gradient specification
    private Color pInterfaceColour = new Color(255, 245, 62);
    protected int iDegreeWhite = 180;
    protected int iDegreeBlack = 0;

    public TransparencyDemo() {
        super(BoxLayout.X_AXIS);
        setOpaque(true);

        //Incorrect Solution
        pGradientPane = new JPanel(new GridBagLayout())
        {
            private static final long serialVersionUID = 1L;

            protected void paintComponent(Graphics pGraphics) 
            {
                Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
                pGraphicsGradientRender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0, iDegreeBlack));
                pGraphicsGradientRender.setPaint(pGradient);
                pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
                super.paintComponent(pGraphics);
            }
        };
        pGradientPane.setOpaque(false);
        add(pGradientPane);

        //Correct Solution
        JPanel pGradientPane2 = new JPanel(new GridBagLayout())
        {
            private static final long serialVersionUID = 1L;

            protected void paintComponent(Graphics pGraphics) 
            {
                Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
                pGraphicsGradientRender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(),  new Color(255, 245, 62, iDegreeWhite));
                pGraphicsGradientRender.setPaint(pGradient);
                pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
                super.paintComponent(pGraphics);
            }
        };
        pGradientPane2.setOpaque(false);
        add(pGradientPane2);


        setBackground(pInterfaceColour);

    }

    public static void main(String[] args){
        try {
             for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                   UIManager.setLookAndFeel(info.getClassName());
                   break;
                }
             }
          } catch (Exception e) {
             e.printStackTrace();
          }

        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new TransparencyDemo());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Nimbus有自己的Painter,在(@Hovercraft Full Of Eels)的回答中检查我的链接,你有遇到什么问题吗? - mKorbel
@mKorbel 他似乎通过直接绘制渐变来避免Painter问题。另一个解决方案是处理Nimbus Painters... - Nick Rippe
@Tom 我已经添加了 SSCCE 代码,可以并排显示两个版本(带有黑线和不带黑线的版本)。 - Nick Rippe
我注意到我在这里发布的Demo有一些奇怪的问题:如果你要么1)全屏窗口或2)在初始绘制之前设置窗口大小不同 -> 两侧都会正确绘制,直到你再次将窗口缩小到最小尺寸。这可能与Tom的回答和Direct3D管道有关,但它似乎不像文章所示的那样行为。 - Nick Rippe
感谢您的好答案!我避免使用两种黄色来形成渐变,因为程序中的用户可以更改颜色。按照我的方式,他们必须选择一种颜色并使用滑块来改变顶部的白度(因此是iDegreeWhite)。无论如何,还是非常感谢! - J_mie6

6
我的猜测是与不同计算机上使用的“图形管道”有关。Java有几个不同的管道,这里有一些关于它们的信息。在我的电脑上,我可以使用X11管道或OpenGL管道。使用X11管道时会出现黑暗,但在OpenGL上不会。在Windows上,您可以从3个不同的管道中选择,即使如此(查看上面的链接),也可能存在差异。我无法立即想象您的学校有什么配置,并且为什么会有所不同,但您可以尝试进行调查。您可能需要将此差异作为错误报告。

2

我看到了一种叫做“蜃景”的视觉错觉,感觉渐变颜色越来越深,深得让人发狂,真是恐怖至极。

//https://dev59.com/5mYr5IYBdhLWcg3wW45t#PHgGoYgBc1ULPQZFcqPh

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

public class MainLauncher {

    private JFrame window = new JFrame();

    public MainLauncher() {
        GradientPane pane = new GradientPane();
        pane.setLayout(new GridLayout(6, 4, 15, 15));
        for (int i = 1; i <= 24; i++) {
            pane.add(createButton(i));
        }
        pane.setOpaque(false);
        window.add(pane);
        RepaintManager.setCurrentManager(new RepaintManager() {

            @Override
            public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
                Container con = c.getParent();
                while (con instanceof JComponent) {
                    if (!con.isVisible()) {
                        return;
                    }
                    if (con instanceof GradientPane) {
                        c = (JComponent) con;
                        x = 0;
                        y = 0;
                        w = con.getWidth();
                        h = con.getHeight();
                    }
                    con = con.getParent();
                }
                super.addDirtyRegion(c, x, y, w, h);
            }
        });
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setLocationByPlatform(true);
        window.setSize(400, 300);
        //window.pack();
        window.setVisible(true);
    }

    private JButton createButton(final int text) {
        JButton button = new JButton(Integer.toString(text));
        return button;
    }

    class GradientPane extends JPanel {

        private static final long serialVersionUID = 1L;
        private final int h = 150;
        private BufferedImage img = null;
        private BufferedImage shadow = new BufferedImage(1, h, BufferedImage.TYPE_INT_ARGB);

        public GradientPane() {
            paintBackGround(new Color(150, 250, 150));
        }

        public void paintBackGround(Color g) {
            Graphics2D g2 = shadow.createGraphics();
            g2.setPaint(g);
            g2.fillRect(0, 0, 1, h);
            g2.setComposite(AlphaComposite.DstIn);
            g2.setPaint(new GradientPaint(0, 0, new Color(0, 0, 0, 0f), 0, h, new Color(0.1f, 0.8f, 0.8f, 0.5f)));
            g2.fillRect(0, 0, 1, h);
            g2.dispose();
        }

        @Override
        public void paintComponent(Graphics g) {
            if (img == null || img.getWidth() != getWidth() || img.getHeight() != getHeight()) {
                img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
            }
            Graphics2D g2 = img.createGraphics();
            super.paintComponent(g2);
            Rectangle bounds = this.getVisibleRect();
            g2.scale(bounds.getWidth(), -1);
            g2.drawImage(shadow, bounds.x, -bounds.y - h, null);
            g2.scale(1, -1);
            g2.drawImage(shadow, bounds.x, bounds.y + bounds.height - h, null);
            g2.dispose();
            g.drawImage(img, 0, 0, null);
        }
    }

    public static void main(String[] args) {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MainLauncher ml = new MainLauncher();
            }
        });
    }
}

2
正如Nick所指出的,问题在于您使用了透明黑色而不是透明白色。因此,半透明颜色是介于白色和黑色之间的一种色调。
请尝试在您的代码中使用以下这行替换:
GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(255, 255, 255, iDegreeBlack));

这实际上非常有趣 - 当第一和第二颜色匹配时,您看不到条纹的唯一时间。因此,这个答案是正确的,但如果第一种颜色是黑色,那么你会得到一个白色条纹。我们能不能在这里得到一些一致性???(是的,我在看着你Oracle) - Nick Rippe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接