4

I am trying to write a program to draw polygons and fill them with desired color.It is a simple painting application but the problem I am facing is when I draw polygons and paint them then a thin white line appears between the polygons. But when I don't antialias the polygons then the white line disappear but the polygons are not smooth. And the real problem is I need to have the polygons smooth as well as the white thin line also needs to be removed.

The class to paint the polygon is a:

public class GrayScaleManager{

    private final VisualizerController controller;

    private final BufferedImage grayScaledImage;
    private final HashMap<ToolsModel, BufferedImage> grayScaleportionList;

    public GrayScaleManager(VisualizerController drawingCanvas) {
        this.controller = drawingCanvas;
        grayScaleportionList = new HashMap<>();

        grayScaledImage = toGray(Utility.bufferedImageDeepCopy(Util.getImg()));
    }


    public void grayScaleSelectedPortion(Graphics2D g, ToolsModel selectedArea) {


        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setClip((Shape) selectedArea);
        g.drawImage(grayScaledImage, 0, 0, null);
        g.setClip(null);


    }

    private BufferedImage toGray(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                Color c = new Color(image.getRGB(j, i));
                int red = (int) (c.getRed() * 0.3);
                int green = (int) (c.getGreen() * 0.59);
                int blue = (int) (c.getBlue() * 0.11);

                int sum = red + green + blue;
                Color newColor = new Color(sum, sum, sum);
                image.setRGB(j, i, newColor.getRGB());
            }
        }
        return image;
    }
    public VisualizerController getController() {
        return controller;
    }
    public HashMap<ToolsModel, BufferedImage> getGrayScaleportionList() {
        return grayScaleportionList;
    }
}

And the image what I get when I run the code are enter image description here

Actually the code revolves in a traingle (of 3 scenarios):

Scenario 1: If the code is done like this

    public void grayScaleSelectedPotion(Graphics2D g, ToolsModel selectedArea){
         g.setClip((Shape) selectedArea);
         g.drawImage(grayScaledImage, 0, 0, null);
         g.setClip(null);
    }

Pros: 1. If multiple layers are drawn by coinciding each other with same color, the layers seem as a single one(Single layer). 2. No ghost white lines. Cons: 1. The edges of lines are not smooth.

Scenario 2: If rendering is applied, just applying below code inside above metod.

   public void grayScaleSelectedPotion(Graphics2D g, ToolsModel selectedArea){
              g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                                                                                RenderingHints.VALUE_ANTIALIAS_ON);
              g.setClip((Shape) selectedArea);
              g.drawImage(grayScaledImage, 0, 0, null);
              g.setClip(null);
    }

Pros: 1. Single layer. 2. The edges are smooth. Cons: 1. Ghost white lines appears.

Scenario 3: If rendered but drawImage() removed

     public void grayScaleSelectedPotion(Graphics2D g, ToolsModel selectedArea){
              g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                                                                                RenderingHints.VALUE_ANTIALIAS_ON);
              g.setClip((Shape) selectedArea);
           // g.drawImage(grayScaledImage, 0, 0, null);
              g.setClip(null);
      }

Pros: 1. The edges are smooth. 2. No ghost white lines.

Cons: 1. Multiple layers are distinctively seen even layers have same colors(which is not acceptable).

In conclusion, all the three cons in three scenarios should be cleared out.

After implementing the solution from @MadProgrammer the code looks as:

    super.paintComponent(g);
    grayImage = grayScaleManager.getGrayImage();
    BufferedImage mask = new BufferedImage(img.getWidth(),img.getHeight(),BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = mask.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    shadeList.forEach((shape)->{
        g2d.setColor(shape.getColor());
        if (shape.getColor().getAlpha() != NULL_ALPHA) {
          //g2d.fill((Shape)shape);  
        }
        g2d.fill((Shape)shape);
        if (shape.getColor().getAlpha() == SELECTION_ALPHA) {
            g2d.setStroke(new BasicStroke(1));
            g2d.setColor(Color.red.brighter().brighter().brighter());
            g2d.draw((Shape) shape);
        }
    });
    // g2d.dispose();
    masked = applyMask(mask,grayImage,AlphaComposite.SRC_IN);
    g.drawImage(img,0,0,null);
    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 0.0f));
    g.drawImage(masked, 0, 0, this);
    g2d.dispose();
    g.dispose();
}


/*Added methods for the changes applymask method*/

public static BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) {

    System.out.println("I am in applymask");

    BufferedImage maskedImage = null;
    if (sourceImage != null) {
        System.out.println("I am in applymask in if case");

        int width = maskImage.getWidth(null);
        int height = maskImage.getHeight(null);

        maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D mg = maskedImage.createGraphics();

        int x = (width - sourceImage.getWidth(null)) / 2;
        int y = (height - sourceImage.getHeight(null)) / 2;

        //mg.setColor(Color.RED);
        //mg.drawImage(sourceImage, x, y, null);
        mg.drawImage(sourceImage, 0, 0, null);
        mg.setComposite(AlphaComposite.getInstance(method,0.0f));

        mg.dispose();
    }

    return maskedImage;

}

But now the grayScaledImage is not painted and the polygons are overlapping, and when the grayScaledImage is added then we can't add other colors to the polygon.

Dilip Poudel
  • 329
  • 3
  • 11
  • From memory, `setClip` doesn't generate "soft" edges – MadProgrammer Feb 08 '17 at 06:18
  • @MadProgrammer please can you be more specefic. – Dilip Poudel Feb 08 '17 at 06:23
  • If you use `setClip`, you won't get the benifits of the antialiasing (from memory), have a look at [Soft Clipping](https://community.oracle.com/blogs/campbell/2006/07/19/java-2d-trickery-soft-clipping) for some more ideas. You should also use `Graphics#create` and `Graphics#dispose` which will allow you to make modifications to the context (like using `setClip`) which won't be carried on – MadProgrammer Feb 08 '17 at 06:29
  • It's hard to be 100% sure without more code – MadProgrammer Feb 08 '17 at 06:30
  • @MadProgrammer thanks for those links I will take deep look on that and try to implement those first. – Dilip Poudel Feb 08 '17 at 06:37
  • I demonstrate a concept of it [here](http://stackoverflow.com/questions/25394366/custom-round-skin-gui/25395267#25395267), but it may not meet your needs – MadProgrammer Feb 08 '17 at 07:03
  • @MadProgrammer I tried without clipping as your sugesstions but I am kind of lost there. – Dilip Poudel Feb 08 '17 at 09:39
  • Another approach might be to generate a mask, using the shape and cut the image out, something like [this](http://stackoverflow.com/questions/26961190/can-i-have-image-alpha-fade-from-left-to-right-in-java/26962173#26962173), [this](http://stackoverflow.com/questions/14655643/java-create-shadow-effect-on-image/14656403#14656403), [this](http://stackoverflow.com/questions/34123731/add-glow-to-a-basic-java-rectangle/34124063#34124063) – MadProgrammer Feb 08 '17 at 09:56
  • [this](http://stackoverflow.com/questions/14225518/tinting-image-in-java-improvement/14225857#14225857), [this](http://stackoverflow.com/questions/31423130/how-to-make-circle-image-label-in-java/31424601#31424601) ... as an idea – MadProgrammer Feb 08 '17 at 09:56
  • Why is this tagged with JavaFX? – user1803551 Feb 08 '17 at 10:30
  • @MadProgrammer I would love to have private chat with you about the problem if you would have time. But due to my reputation level it is impossible. And I tried with the removing the setclip but that didn't worked for me. – Dilip Poudel Feb 09 '17 at 08:20
  • @MadProgrammer No luck on anything else could you please help me with replacing setclip witht the [implementation in this link](https://community.oracle.com/blogs/campbell/2006/07/19/java-2d-trickery-soft-clipping). – Dilip Poudel Feb 09 '17 at 16:44
  • No ignoring you, just in the middle of a crunch :P – MadProgrammer Feb 09 '17 at 20:29
  • @MadProgrammer Thank you for your response. I had tried a lot but I couldn't get what I want from the code. So I was looking for more help and if you would have free time to look at my code I would be happy to share. – Dilip Poudel Feb 10 '17 at 05:35
  • @DilipPoudel I'm guessing here, but you "seem" to have a number of shapes, which you are using to clip a colorized image (presumably of the background)? My idea would be to generate an image of the shapes and then apply that as a mask to the colorized image, but I'll need time to nut out the idea and example – MadProgrammer Feb 10 '17 at 05:50
  • @MadProgrammer yes as you said, I need to draw multiple polygons and some might overlap on each other but needs to maintain the same transparent level. And I am also following your steps but couldn't implement correctly. So yes take your time and I would be happy to have your idea and example. And again I am ready to share my project with you in private if you have time. – Dilip Poudel Feb 10 '17 at 06:28

3 Answers3

1

Okay, so this might seem a little convoluted, but it should get your closer to your goal. Basically setClip doesn't generate "soft edges", you have to cheat.

The basic idea is to generate an transparent image of the area you want to capture, this becomes the "mask". We then use the mask to "cut out" a portion of the source image, which is then finally applied to the original image.

This is the same basic concepts demonstrated in the following examples...

Let me try and walk you through the important parts...

// The master image or background
master = ImageIO.read(...);
// Generate a gray scaled version of the master image
// This is what we will be cutting out
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
grayScaled = op.filter(master, null);

int width = master.getWidth() / 4;
int height = master.getHeight() / 4;

// This simply what I'm using as the base "selection"
// But for all intents purpose, it can be anything which can
// be painted through Graphics
Shape shape = new Rectangle2D.Double(0, 0, width, height);

// The base mask, not that it's transparent
BufferedImage mask = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = mask.createGraphics();
// Offset the location to apply the "selection"
g2d.translate((master.getWidth() - shape.getBounds().width) / 2, (master.getHeight() - shape.getBounds().height) / 2);
// Any color will do, it could even have an alpha applied
g2d.setColor(Color.BLUE);
// Rotate it a bit, because square is boring...
shape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(45.0), width / 2, height / 2));
// Fill the area
g2d.fill(shape);
// Rotate again, so you can see that we're compounding the selection
shape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(45.0), width / 2, height / 2));
// Fill the new selection
g2d.fill(shape);        
// Clean up 
g2d.dispose();

// Now, apply the mask to the image to get a "cut out"
masked = applyMask(mask, grayScaled, AlphaComposite.SRC_IN);

Now, this is a simple example. When you want to change the selection, you could recreate the mask from scratch, or simply paint new objects onto the original mask and then re-apply it against the grayScaled to generate a new masked image.

Then, all you need to do, is paint the masked image over the master image at whatever alpha level you want..

Example

That "grayish" star in the middle, that's the mask ;)

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.Shape;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

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

    public class TestPane extends JPanel {

        private BufferedImage master;
        private BufferedImage grayScaled;
        private BufferedImage masked;

        public TestPane() {
            try {
                master = ImageIO.read(...);
                ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
                grayScaled = op.filter(master, null);

                int width = master.getWidth() / 4;
                int height = master.getHeight() / 4;

                Shape shape = new Rectangle2D.Double(0, 0, width, height);
                shape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(45.0), width / 2, height / 2));

                BufferedImage mask = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = mask.createGraphics();
                g2d.translate((master.getWidth() - shape.getBounds().width) / 2, (master.getHeight() - shape.getBounds().height) / 2);
                g2d.setColor(Color.BLUE);
                g2d.fill(shape);
                shape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(45.0), width / 2, height / 2));
                g2d.fill(shape);            
                g2d.dispose();

                masked = applyMask(mask, grayScaled, AlphaComposite.SRC_IN);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return master == null ? getPreferredSize() : new Dimension(master.getWidth(), master.getHeight());
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (master != null && masked != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                int x = (getWidth() - master.getWidth()) / 2;
                int y = (getHeight() - master.getHeight()) / 2;

                g2d.drawImage(master, x, y, this);
                g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
                g2d.drawImage(masked, x, y, this);
                g2d.dispose();
            }
        }

    }

    public static BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) {

        BufferedImage maskedImage = null;
        if (sourceImage != null) {

            int width = maskImage.getWidth(null);
            int height = maskImage.getHeight(null);

            maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D mg = maskedImage.createGraphics();

            int x = (width - sourceImage.getWidth(null)) / 2;
            int y = (height - sourceImage.getHeight(null)) / 2;

            mg.drawImage(sourceImage, x, y, null);
            mg.setComposite(AlphaComposite.getInstance(method));

            mg.drawImage(maskImage, 0, 0, null);

            mg.dispose();
        }

        return maskedImage;

    }

}

You can also apply rendering hints to any of it at any time you want, to help clean it, since it's all just images, it shouldn't give you (to many) issues ;)

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks a lot for detail descripttion I will try it and let you know asap. – Dilip Poudel Feb 10 '17 at 15:11
  • Don't be afraid to play around with the order of the image parameters and AlphaComposite type of applyMask, took me a couple of tries to get it to do what I wanted ;) you might want to take a look at [Compositing Graphics](https://docs.oracle.com/javase/tutorial/2d/advanced/compositing.html) for more details – MadProgrammer Feb 10 '17 at 21:46
  • I think I need your help seriously. I am going mad with all these graphics code. Can you please help me with this, I can send you my project if you are interested cause I don't have much time left to finish this project and implementing your code looks promising but I couldn't get it through. So would you have time to look at this and help to sort out the problem. – Dilip Poudel Feb 13 '17 at 02:56
  • we have created three buffered image one is editorimage to store the actual image, grayscaled for grayscaled image and masked to store the draw object and alphacomposite image. But this code doesn't meet our goal as we also want to color that drawn object but it doesn't not accept any color or not showing any change in object that we draw at runtime. – Dilip Poudel Feb 13 '17 at 09:39
  • @DilipPoudel If I understand correctly, then this [example](http://stackoverflow.com/questions/14225518/tinting-image-in-java-improvement/14225857#14225857) does – MadProgrammer Feb 13 '17 at 10:12
  • Ok now I am almost done as I can now also color the polygons but the problem now is due to the transparency of the polygons the polygons earlier drawn are also visible when overlapped. And I tried SRC_OVER and SRC_IN but both doesn't work. Do you have any Idea. And again can I contact you personally its serious problem. – Dilip Poudel Feb 13 '17 at 10:16
  • Oh god, I need some help. – Dilip Poudel Feb 13 '17 at 10:25
  • You either need to add the transparency after you've drawn the polygons (using an AlphaComposite and painting it to another image) or combine the polygons into a single shape using either Path2D or GeneralPath – MadProgrammer Feb 13 '17 at 10:25
  • OK can be it the problem of white line with the shape converting to ToolsModel – Dilip Poudel Feb 13 '17 at 10:31
  • I have posted the code with the changes I have made using your answer. Please a have a look and help me. – Dilip Poudel Feb 13 '17 at 11:36
  • Hi, Madprogrammer Sorry to disturb you again, but we need your help seriously as our project has crossed the time and we are paying fine for this issue So if you would help us in this project we would pay you through PayPal. Please let us know asap. We are in emergency. – Dilip Poudel Feb 14 '17 at 04:36
  • And if you have time please mail me at revdilip[at]gmail. – Dilip Poudel Feb 14 '17 at 04:48
  • @DilipPoudel Sorry mate, I'm right in the middle of crunch, I'm working 16-20 hours days 6-7 days a week, I've not seen my daughter awake in near 2 weeks and they've just handed me a pile of defects, so as I'm sure you can understand, I 0 spare time :P – MadProgrammer Feb 15 '17 at 03:14
  • sorry mate,but I couldn't help myself and its emergency as I already told you, so if you would help us by refering someone to deal with it. We would be in debt of you and yes we are ready to pay for the service but couldn't get a guy with much knowledge as you have here. – Dilip Poudel Feb 15 '17 at 04:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/135729/discussion-between-dilip-poudel-and-madprogrammer). – Dilip Poudel Feb 15 '17 at 04:36
  • So, since there are no new downvotes to the question or close votes I can assume the issue isn't with the question be of low quality. Since none of the other answers have not been downvoted, I can only assume there's something wrong with the question. In what ways does it not answer the OPs question? In what ways could it be improved? Since you've failed to provide any kind of feedback, at this point, I can only assume that you have a personal dislike of me trying to help people – MadProgrammer Feb 17 '17 at 02:05
  • To the downvoter: Since you've also failed to provide an additional answer, I can only assume you don't know how to solve the problem either, which begs the question, why did you downvote the answer? – MadProgrammer Feb 17 '17 at 02:31
0

You could solve your problem by creating the union of your individual clipping shapes via the Area class and then apply this as your grayscale image clipping mask.

mipa
  • 10,369
  • 2
  • 16
  • 35
-2

i guess your image show that your code build polygons in 2D, that is why this shows the white line to feel 2D that one polygons is overlapping other polygons.

  • Sorry Maulin that may not be the real cause as I have googled a lot and the white line doesn't appear if the border of the polyogon are straight line like if I draw a square or rectangle there will not be any white line. So I think its issue on Antialias for triangles – Dilip Poudel Feb 08 '17 at 06:28