有没有办法让 Java 中的 ImageIcon 的一部分变灰?

Is there a way to gray out a portion of an ImageIcon in Java?

我正在尝试将图片的一部分变灰。 normalImage 是 100% 彩色的,而 grayedImage 是 0% 彩色的。我怎样才能生成 grayedImage,比方说,左边 50% 着色,右边 50% 变灰,反之亦然?

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

public class TestPicture {

    public static void main(String[] args) {
        JFrame jf = new JFrame("Test Picture");
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setLayout(new BorderLayout());
        jf.setPreferredSize(new Dimension(600, 300));
        jf.setVisible(true);
        jf.pack();

        JPanel jp = new JPanel();
        JLabel jl1 = new JLabel();
        JLabel jl2 = new JLabel();
        ImageIcon normalImage = new ImageIcon("normalImage.png");
        ImageIcon grayedImage = new ImageIcon(GrayFilter.createDisabledImage(normalImage.getImage()));
        jl1.setIcon(normalImage);
        jl2.setIcon(grayedImage);
        jp.add(jl1);
        jp.add(jl2);
        jf.add(jp);

        jf.repaint();
        jf.revalidate();
    }
}

What if I wanted to do something more complex, such as graying out an image in a clock form? Are you familiar with League of Legends? In the game, there are skills that have cool down. As time passes, it undarkens itself going clockwise

你可以...

使用 BufferedImage#subImage 生成彩色图像的“进度”切片并将其绘制在灰度图像的顶部,但这只会给你一个线性进展,而不是......

你可以...

使用 AlphaComposite 生成进度蒙版,将其绘制在灰度图像的顶部

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.LinearGradientPaint;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Arc2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage color;
        private BufferedImage gray;

        private Duration duration = Duration.ofSeconds(5);
        private Instant startedAt;

        private double progress;

        public TestPane() throws IOException {
            color = ImageIO.read(getClass().getResource("Progress.png"));
            // You don't need to do this, but my original image was transparent
            // and it was hard to see the fill effect
            color = fillImage(color);
            gray = ImageIO.read(getClass().getResource("Progress.png"));
            gray = fillImage(gray);
            ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
            op.filter(gray, gray);

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startedAt == null) {
                        startedAt = Instant.now();
                    }
                    Instant now = Instant.now();
                    Duration runTime = Duration.between(startedAt, now);
                    progress = (double) runTime.toMillis() / (double) duration.toMillis();
                    if (progress >= 1.0) {
                        ((Timer) (e.getSource())).stop();
                        progress = 1.0;
                    }
                    System.out.println(progress);
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            g2d.setRenderingHints(hints);
            g2d.drawImage(progressEffect(), 0, 0, this);
            g2d.dispose();
        }

        protected BufferedImage progressEffect() {
            int width = color.getWidth();
            int height = color.getHeight();

            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D og = img.createGraphics();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            og.setRenderingHints(hints);
            og.drawImage(gray, 0, 0, this);
            og.drawImage(maskedEffect(), 0, 0, this);
            og.dispose();
            return img;
        }

        protected BufferedImage maskedEffect() {
            int width = color.getWidth();
            int height = color.getHeight();
            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

            Graphics2D og = img.createGraphics();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            og.setRenderingHints(hints);
            // This will "overfill" the are so it occupies the entire image
            Arc2D.Double expose = new Arc2D.Double(-(width / 2d), -(height / 2d), width * 2d, height * 2d, 90, -(360.0d * progress), Arc2D.PIE);
            og.fill(expose);
            og.setComposite(AlphaComposite.SrcIn);
            og.drawImage(color, 0, 0, this);
            og.dispose();
            return img;
        }

        protected BufferedImage fillImage(BufferedImage original) {
            int width = original.getWidth();
            int height = original.getHeight();
            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = img.createGraphics();
            RenderingHints hints = new RenderingHints(
                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
            );
            g2d.setRenderingHints(hints);
            LinearGradientPaint lgp = new LinearGradientPaint(
                    new Point2D.Double(0, 0),
                    new Point2D.Double(0, height),
                    new float[]{0f, 1f},
                    new Color[]{Color.YELLOW, Color.RED});
            g2d.setPaint(lgp);
            g2d.fillRect(0, 0, width, height);
            g2d.drawImage(original, 0, 0, this);
            g2d.dispose();
            return img;
        }

    }

    public static GraphicsConfiguration getGraphicsConfiguration() {
        return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
    }

    public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
        BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
        image.coerceData(true);
        return image;
    }

    public static void applyQualityRenderingHints(Graphics2D g2d) {
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    }

    public static BufferedImage generateMask(BufferedImage imgSource, Color color, float alpha) {
        int imgWidth = imgSource.getWidth();
        int imgHeight = imgSource.getHeight();

        BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight, Transparency.TRANSLUCENT);
        Graphics2D g2 = imgMask.createGraphics();
        applyQualityRenderingHints(g2);

        g2.drawImage(imgSource, 0, 0, null);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha));
        g2.setColor(color);

        g2.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight());
        g2.dispose();

        return imgMask;
    }
}