【问题标题】:Java: Filling a BufferedImage with transparent pixelsJava:用透明像素填充 BufferedImage
【发布时间】:2011-08-06 01:46:03
【问题描述】:

我有一个屏幕外的 BufferedImage,其类型为 BufferedImage.TYPE_INT_ARGB。它可以包含任何东西,我正在寻找一种方法来(相当有效地)用透明像素完全覆盖图像,从而产生一个“不可见”的图像。

使用这样的东西:

    (bufimg.getGraphics()).setColor(new Color(10, 10, 100, 0));   
    (bufimg.getGraphics()).fillRect (0, 0, x, y);

没有效果。一种可能的方法可能只是覆盖 BufferedImage 中的每个像素,但我不确定这是最好的解决方案。你会怎么做?

[编辑]
Graphics 文档建议不要将 clearRect 用于屏幕外图像,但我已经尝试过,结果与上述相同。

[编辑2]
在尝试了 MeBigFatGuy 的代码后(谢谢!),它确实清除了图像。但它也停止了对该图像的进一步绘画(或似乎)。例如这段代码:

    BufferedImage img = new BufferedImage (600, 600, BufferedImage.TYPE_INT_ARGB);
    Graphics g = img.createGraphics ()    
    g.drawLine (100, 100, 500, 500);
    AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
    g.setComposite(composite);
    g.setColor(new Color(0, 0, 0, 0));
    g.fillRect(0, 0, 600, 600);
    graphicsAI.setColor(new Color (10, 10, 10, 255));
    graphicsAI.drawLine (100, 100, 500, 500);

导致图像上看不到任何东西(我正在将图像绘制到 JPanel)。这与添加 alpha 值有关吗?

【问题讨论】:

  • 除了我的回答之外,这是我在这里提出的一个与此相关的问题(1.3 KView as I type this comment)。请务必阅读 Stacker 的最佳答案:stackoverflow.com/questions/2825837(请注意,我确实希望将精确的像素——也就是说,没有颜色模型转换——放入底层 int[] 中,我想要这将很快完成)。
  • 通过 g2d.getComposite() 拯救旧复合材料,并在填充矩形后通过 g2d.setComposite(oldC) 设置复合材料;
  • 我认为你应该简单地在你的 Graphics 实例上调用 dispose() 来恢复之前的图形上下文。

标签: java image transparency bufferedimage


【解决方案1】:

为了完整起见,这里是一个跨平台兼容的工作、测试和快速功能。

  static public BufferedImage createTransparentBufferedImage(int width, int height) {
     // BufferedImage is actually already transparent on my system, but that isn't
     // guaranteed across platforms.
     BufferedImage bufferedImage = new BufferedImage(width, height, 
                        BufferedImage.TYPE_INT_ARGB);
     Graphics2D graphics = bufferedImage.createGraphics();

     // To be sure, we use clearRect, which will (unlike fillRect) totally replace
     // the current pixels with the desired color, even if it's fully transparent.
     graphics.setBackground(new Color(0, true));
     graphics.clearRect(0, 0, width, height);
     graphics.dispose();

     return bufferedImage;
  }

【讨论】:

    【解决方案2】:

    设置图形对象的背景似乎可以完成这项工作:

    g.setBackground(new Color(0, 0, 0, 0));
    

    (至少在为缩放目的绘制图像时)

    【讨论】:

      【解决方案3】:

      尽管你说它不起作用,但我用clearRect 很好。

      通过填充背景颜色来清除指定的矩形 当前绘图表面的。此操作不使用 当前绘制模式。

      从 Java 1.1 开始,屏幕外图像的背景颜色可能 依赖于系统。应用程序应使用 setColor 后跟 fillRect 以确保将屏幕外图像清除为特定的 颜色。


      填充指定的矩形。的左边缘和右边缘 矩形位于 x 和 x + 宽度 - 1。顶部和底部边缘位于 y 和 y + height - 1。生成的矩形覆盖区域宽度 像素宽乘高像素高。矩形填充使用 图形上下文的当前颜色。

      这里没有明确说明,一个会将矩形设置为背景色,而另一个将前景色绘制在当前颜色之上,但它似乎是这样做的。

      这纯粹是猜测,但我认为关于屏幕外图像的注释与从屏幕外 AWT 组件获得的 Graphics 对象有关,因为它们是原生的。我很难想象BufferedImage 的背景颜色如何取决于系统。由于 API 文档适用于 Graphics,因此这可能是不适用于 BufferedImage 案例的概括。

      我的测试代码:

      JFrame jf = new JFrame();
      jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      
      BufferedImage img = new BufferedImage(200, 300, BufferedImage.TYPE_INT_ARGB);
      
      Graphics2D g = img.createGraphics();
      
      //fill right half with opaque white
      g.setColor(Color.WHITE);
      g.fillRect(100, 0, 100, 300);
      
      //leave top third as it is
      
      //fill middle third with transparent color
      g.setColor(new Color(0, true));
      g.fillRect(0, 100, 200, 100);
      
      //clear bottom third with transparent color
      g.setBackground(new Color(0, true));
      g.clearRect(0, 200, 200, 100);
      
      g.dispose();
      
      jf.add(new JLabel(new ImageIcon(img)));
      
      jf.pack();
      jf.setVisible(true);
      

      结果是右上角的两个白色方块。如果没有绘制白色,或者使用clearRect 覆盖白色,则结果是浅灰色,即框架的默认背景颜色。

      在性能方面,它是常规绘图。 arraycopy 可能会更快,我不知道,但至少这可能是硬件加速,就像任何其他绘图操作一样。

      与数组解决方案相比,一个优点是 a) 没有额外的内存和 b) 独立于颜色模型;无论图像是如何设置的,这都应该有效。

      与复合解决方案相比,它的一个缺点是它只允许清除矩形;设置合成允许您清除任何类型的形状。

      【讨论】:

      • 您的测试有点错误,因为(至少在我的情况下)新创建的 BufferedImage 已经是空的(并且是透明的)。你应该先用另一种颜色填充它,这样你就可以正确地观察用完全透明的颜色绘制或清除是否真的在做任何事情。我在其中添加了它,结果表明 clearRect() 有效,但是具有 0 alpha 的 fillRect() 什么都不做(这是应该的)。我还是给了你+1。
      • //fill right half with opaque white。我的测试包含六个区域:左侧无填充(默认背景)/右侧白色填充 + 顶部未清除/中间透明填充/底部透明清除。最有趣的领域当然是背景和清除尝试重叠的领域。将生成的图像绘制为具有灰色默认背景的JFrame,应该会显示所有可查看的内容。还是我错过了什么?除了我的,你的测试还揭示了什么?
      【解决方案4】:

      使用 CLEAR 合成清除背景后,需要将其设置回 SRC_OVER 才能再次正常绘制。例如:

      //clear
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
      g2.fillRect(0,0,256,256);
      
      //reset composite
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
      //draw
      g2.setPaint(Color.RED);
      g2.fillOval(50,50,100,100);
      

      【讨论】:

        【解决方案5】:

        您可以获得BufferedImage 的底层int[] 数组(确保使用兼容的格式:即由int[] 支持的格式)。

        然后用 alpha 值为 0 的整数填充 int[](0 也可以;)

        System.arraycopy非常快。

        你要知道直接写int[]比使用setRGB很多

        现在BufferedImage 在 Java 中有点像黑魔法:根据你在做什么以及你在哪个平台/JVM 上做,你可能失去硬件加速(这可能一开始就没有出现过)。除此之外,您很可能根本不关心硬件加速,因为您可能没有在开发需要 60+ FPS 才能玩的游戏等。

        这是一个非常复杂的话题,给BufferedImage 猫换皮的方法不止一种。就我而言,当我不得不在像素级别搞砸时,我直接在int[] 中工作,因为我认为这比尝试使用更高级别的绘图原语更有意义,而且我确实 不用担心硬件加速的潜在损失。

        【讨论】:

        • 顺便说一句,有一个非常严重的 flush() / non-GC'ed SNAFU 多年来影响了几个 Java VM,而没有任何引用它们的 BufferedImage 仍然没有符合 GC 条件(在 Apple Java-dev 邮件列表中有详细说明)。在我看来,当您开始“使用像素”或“突破极限”时,BufferedImage 的作用远不止于此。请务必使用 Google 获取 flush() / non-GC'ed SNAFU。如果有的话,读起来很有趣。
        • 感谢您的回复,目前循环遍历数组似乎是最可行的方案。但是我(非常小的)之前在操作BufferedImage 的底层数组方面的经验是……痛苦的,我对效率的兴趣更多地来自于无处不在的强迫症般的冲动,而不是程序本身的任何特殊需求。所以我希望能保持在更高的水平,但明天我会尝试循环遍历数组。
        • @JBenson:普通循环仍然会对每个像素进行的数组边界检查产生惩罚:我真的建议使用 System.arraycopy 一旦你得到您的第一条水平线已填充。 stacker 对我在评论中链接的问题的回答包含 arraycopy 代码,您可以在此基础上进行自己的试用。 :)
        • 感谢您的建议,根据我的(小)测试,使用 arraycopy 远优于仅循环遍历每个像素。很高兴报告一切正常且高效!
        • @JBenson:很高兴知道它对您有所帮助...它确实对我有所帮助;) 请注意,您仍然需要注意您的 BufferedImage 必须 得到支持这一事实通过 int[] (并非全部都是)。但是您可以控制这一点:例如,您使用 ImageIO 读取 BufferedImage 的图像,该图像可能没有 int[] 支持,但您可以创建一个肯定的 BufferedImage由 int[] 支持并一次使用 getRGB/setRGB 复制所有像素。然后使用超快速的 int[] 像素操作。另请注意,可能存在非常、非常、奇怪的多线程问题(更多内容在下一条评论中)
        【解决方案6】:

        如果将 Graphics 对象转换为 Graphics2D 对象,则可以通过设置 Composite 对象

        AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
        Graphics2D g2d = (Graphics2D) image.getGraphics();
        g2d.setComposite(composite);
        g2d.setColor(new Color(0, 0, 0, 0));
        g2d.fillRect(0, 0, 10, 10);
        

        【讨论】:

        • 这很有趣,虽然您的代码确实填充了图像,但它似乎停止了对图像的任何进一步绘制。我已经用更多信息编辑了原始帖子。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-12
        • 2012-02-17
        • 1970-01-01
        • 2014-03-30
        • 2014-04-12
        相关资源
        最近更新 更多