1

Trying to do something relatively simple, given a 512x512 png of a map, I'm attempting to plot points. My code's fairly straightforward, I've tried using both the setRGB function and the Graphics2D object that is returned by the createGraphics function. I must be overlooking something simple. EDIT: I should mention that I'm not looking to create a new BufferedImage, I'm looking to modify the existing BufferedImage, since successive library calls will continue to modify the BufferedImage that I'm working with. (In the example code below, I read the BufferedImage from a file, for a simple way to replicate the issue.

            File outputImage = new File("before.png");
            BufferedImage img = ImageIO.read(outputImage);


            img.setRGB(255, 255, new Color(0f, 1f, 0).getRGB());

            File after = new File("after.png");
            ImageIO.write(img, "png", after);

If you zoom in on the resulting pixel, it's not green, but some darker grey. Since this behavior is uniform with the Graphics2D, I'm hoping solving this problem will address that as well.

before.png after.png

Parth Mehrotra
  • 2,712
  • 1
  • 23
  • 31
  • The problem is that the original image is using an `IndexColorModel` (or a color map or "palette" if you like). There is no green color that matches the color you specify, so instead the color model does a lookup to get the "closest" color to the one you specified. @camickr's solution should work fine, although it's the color *model* not the color *space* (they're both RGB) that is the issue. ;-) – Harald K Mar 13 '17 at 08:45

2 Answers2

3

The color space of the BufferedImage must be causing a problem.

In the code below I use your original image and paint it to a BufferedImage with the specified color space:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.io.*;
import javax.imageio.*;
import java.net.*;

public class SSCCE extends JPanel
{
    SSCCE()
    {
        try
        {
            BufferedImage original = ImageIO.read( new File("map.png") );
            int width = original.getWidth(null);
            int height = original.getHeight(null);
            int size = 100;
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = bi.createGraphics();
            g2d.drawImage(original, 0, 0, null);

            int color = new Color(0f, 1f, 0f).getRGB();
            bi.setRGB(10, 10, color);
            bi.setRGB(10, 11, color);
            bi.setRGB(11, 10, color);
            bi.setRGB(11, 11, color);
            add( new JLabel( new ImageIcon(bi) ) );
        }
        catch(Exception e2) {}
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new SSCCE());
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
/*
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
*/
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thank you, but is there any way to do this with the existing BufferedImage? Without creating a new one? I'm getting the BufferedImage from the library call and need to modify it, rather than create a new one. – Parth Mehrotra Mar 13 '17 at 17:21
  • @ParthMehrotra, I gave you the best solution I could think of. I don't know how ColorSpaces work or how to convert between the two. Read the BufferedImage API and look at the ColorSpace class. Maybe you can figure something out. Your requirement was to save an image to a file. I fail to see how it matters whether you first create a copy of the original image. All that matters is that the image with the changes gets saved to a file. – camickr Mar 13 '17 at 18:24
  • Well my requirement is to plot multiple points, I never really mentioned saving anything to a file at all. Thank you for directing me to the fact that this issue is being cause by the color space, it's something I can further research. But, unfortunately this does not solve my problem. – Parth Mehrotra Mar 13 '17 at 21:45
  • I've upvoted your answer since, it has helped me, and perhaps it will help someone in the future, but the accepted answer will have to work with the given BufferedImage, I'll try to provide an accepted answer tonight. – Parth Mehrotra Mar 13 '17 at 21:47
  • @ParthMehrotra, `Well my requirement is to plot multiple points,` - making a copy of the image when you first read it does not prevent you from doing this, so I still don't understand the problem. `I never really mentioned saving anything to a file at all.` - the code you posted shows you saving the updated image to a file. – camickr Mar 13 '17 at 22:01
  • Not sure what we're arguing about. Like I said earlier, it's a requirement that I deal with the existing BufferedImage, since the library I'm using will continue to use that BufferedImage. I've edited the question to reflect this requirement. – Parth Mehrotra Mar 14 '17 at 00:43
  • @ParthMehrotra The `BufferedImage` you have has a limited set of colors (a color map, represented as an `IndexColorModel`). **You can't change this property of an existing `BufferedImage`, so you can't paint your green color onto it.** The only way I know of, is to change it, as @camickr suggests. But maybe you could do this conversion "upstream" (ie. right after loading), and use only the converted image, if not changing the image in your method is a requirement? – Harald K Mar 14 '17 at 13:42
  • 1
    @haraldK, thanks for understanding my suggestion. Whatever this "library" is the OP is talking about. The "library" can convert the image when it is loaded into the "library" and then any other process accessing the "library" image will use the same converted image. – camickr Mar 14 '17 at 14:28
  • @haraldK, `you have has a limited set of colors` - I tried to do some research and it seemed that this only supports 256 colors so I still have a question how do you change an color to be on of the 256. I tried to simply use values from 0 - 255 but they all came out the same (or greyish) color. Even if "green" (or green shades) can't be specified there are shades of blue. I couldn't even get a shade of blue to be painted. Any idea on how to do this? – camickr Mar 14 '17 at 14:32
  • If I set the color to one matching the colors of the image, I can easily paint in that color. Try `new Color(0.8f, 0.9019608f, 0.6392157f)` or `0xffcce6a3` for the light green one. `new Color(0.6392157f, 0.8f, 1f)` or `0xffa3ccff`for the light blue. Assuming `colorModel` is an `IndexColorModel`, you can use `int[] rgbs = new int[colorModel.getMapSize()]; colorModel.getRGBs(rgbs);` to get the colors in the color map. – Harald K Mar 14 '17 at 17:30
  • @haraldK, thanks I'll look at the `getRGBs(...)` to try to understand the ColorModel better. You may want to consider making that an answer the OP can use. Even if Color.GREEN isn't supported you would think another shade of green would work. – camickr Mar 14 '17 at 17:35
  • If he's using a library to generate these images, they are probably rendered first in "true color", then color reduced/color mapped later. Unless they are using a fixed palette, the approach will not work (try plotting a green circle in the middle of the ocean, for example, when the palette is all blue). :-/ – Harald K Mar 14 '17 at 17:45
1

Here's an attempt to describe a few ways you could work around the problem, based on discussion in the comments:

The problem is that the original image is using an IndexColorModel (or a color map or "palette" if you like). There is no green color that exactly matches the color you specify, so instead the color model does a lookup to get the "closest" color to the one you specified (you may not agree to this color being the closest match, but it is given the algorithm used).

If you set the color to one matching the colors of the image, you can paint in that color. Try new Color(0.8f, 0.9019608f, 0.6392157f) or RGB value 0xffcce6a3 for the light green one. Use new Color(0.6392157f, 0.8f, 1f) or 0xffa3ccff for the light blue.

If you wonder how I found those values, here's the explanation. Assuming colorModel is an IndexColorModel, you can use:

int[] rgbs = new int[colorModel.getMapSize()]; 
colorModel.getRGBs(rgbs); 

...to get the colors in the color map. Choosing one of these colors should always work.

Now, if your "library" (which you haven't disclosed much details about) is using a fixed palette for generating these images, you are good, and can use one of the colors I mentioned, or use the approach described to get the colors, and choose an appropriate one. If not, you need to dynamically find the best color. And if you're really out of luck, there might be no suitable color available at all (ie., your map tile is all ocean, and the only color available is sea blue, it will be impossible to plot a green dot). Then there's really no other way to solve this, than to modify the library.

A completely different approach, could be similar to @camickr's solution, where you temporarily convert the image to true color (BufferedImage.TYPE_INT_RGB or TYPE_3BYTE_BGR), paint your changes onto this temporary image, then paint that image back onto the original. The reason why this might work better, is that the composing mechanism will use a dither and a better color lookup algorithm. But you'll still have the same issue related to available colors as described in the previous paragraph.


Here's a code sample, using the warm yellow color, and the output:

Color color = new Color(0.89411765f, 0.5686275f, 0.019607844f);
int argb = color.getRGB();

Graphics2D g = image.createGraphics();
try {
    g.setColor(color);
    g.fillRect(10, 10, 50, 50);
}
finally {
    g.dispose();
}

image with yellow rectangle

Harald K
  • 26,314
  • 7
  • 65
  • 111