I looked for an easy way to tint an image in Java but I found nothing that suited my needs. I went to the following solution:

First create a new Image that serves as a copy of the Image I want to tint, then I create a second Image that is a transparent mask of the Image I want to tint and then draw the tint - mask over my copy and return the copy:

public static BufferedImage tintImage(Image original, int r, int g, int b){
    int width = original.getWidth(null);
    int height = original.getHeight(null);
    BufferedImage tinted = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
    Graphics2D graphics = (Graphics2D) tinted.getGraphics();
    graphics.drawImage(original, 0, 0, width, height, null);
    Color c = new Color(r,g,b,128);
    Color n = new Color(0,0,0,0);
    BufferedImage tint = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
    for(int i = 0 ; i < width ; i++){
        for(int j = 0 ; j < height ; j++){
            if(tinted.getRGB(i, j) != n.getRGB()){
                tint.setRGB(i, j, c.getRGB());
            }
        }
    }
    graphics.drawImage(tint, 0, 0, null);
    graphics.dispose();
    return tinted;
}

A solution for images that had no transparent pixels (e.g. did not make use of the alpha - channel) was to simply use fillRect() on the whole image, but that didn't work on images with transparent pixels as those then had the chosen color instead of still being invisible.

Does anyone know a way to do this more efficiently as methods I found here were rather unsatisfying and I plan on doing this tinting on many images (most having a grey-ish tone to them so they are easy to be tinted) at runtime about 50 times per second.

Pre - Generating all needed images at startup and / or caching generated images might be a solution but it feels awkward in some way to me, though if nothing can be done then nothing can be done.

Someone linked this: http://www.javalobby.org/articles/ultimate-image/

It was helpful but did not cover tinting.

有帮助吗?

解决方案

Essentially, you need to use a little black magic and it wouldn't hurt to have a sacrifice or two on hand...

enter image description here

public class TestTint {

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

    public TestTint() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

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

    public BufferedImage tint(BufferedImage master, BufferedImage tint) {
        int imgWidth = master.getWidth();
        int imgHeight = master.getHeight();

        BufferedImage tinted = createCompatibleImage(imgWidth, imgHeight, Transparency.TRANSLUCENT);
        Graphics2D g2 = tinted.createGraphics();
        applyQualityRenderingHints(g2);
        g2.drawImage(master, 0, 0, null);
        g2.drawImage(tint, 0, 0, null);
        g2.dispose();

        return tinted;
    }

    public class TestPane extends JPanel {

        private BufferedImage master;
        private BufferedImage mask;
        private BufferedImage tinted;

        public TestPane() {
            try {
                master = ImageIO.read(new File("C:/Users/swhitehead/Documents/My Dropbox/MegaTokyo/Miho_Small.png"));
                mask = generateMask(master, Color.RED, 0.5f);
                tinted = tint(master, mask);
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = super.getPreferredSize();
            if (master != null && mask != null) {
                size = new Dimension(master.getWidth() + mask.getWidth() + tinted.getWidth(), Math.max(Math.max(master.getHeight(), mask.getHeight()), tinted.getHeight()));
            }
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            int x = (getWidth() - (master.getWidth() + mask.getWidth() + tinted.getWidth())) / 2;
            int y = (getHeight() - master.getHeight()) / 2;
            g.drawImage(master, x, y, this);

            x += mask.getWidth();
            y = (getHeight() - mask.getHeight()) / 2;
            g.drawImage(mask, x, y, this);

            x += tinted.getWidth();
            y = (getHeight() - tinted.getHeight()) / 2;
            g.drawImage(tinted, x, y, this);
        }

    }

}

The general idea behind this technique is to generate a "mask" of the image, I take no credit of this idea, I stole it of the web, if I can find where, I'll post a link.

Once you have the mask, you can then render the two images together. Because I've already applied a alpha level to the mask, I don't need to reapply a alpha composite once I'm done.

PS - I create a compatible image for this example. I do this simply because it will render faster on the graphics device, this is not a requirement, it is simply the code I have on hand ;)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top