Reputation: 329
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
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.
Upvotes: 4
Views: 834
Reputation: 347314
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..
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 ;)
Upvotes: 1
Reputation: 10640
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.
Upvotes: 0
Reputation: 11
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.
Upvotes: -2