Cursor with Semi-Transparency / Anti-Aliasing - java

I'm trying to create a custom cursor with the following code:
BufferedImage cursor = new BufferedImage(30, 30, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) cursor.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.drawOval(0, 0, 26, 26);
jframe.getContentPane().setCursor(Toolkit.getDefaultToolkit().createCustomCursor(
cursor, new Point(13, 13), "cursor"));
Without Anti-Aliasing this looks really bad, but once I try to activate Anti-Aliasing, the pixels that are supposed to be semi transparent seem to become completely solid - as if the JFrame cursor cannot handle semi-transparency.
I've tried loading the BufferedImage from a .png file, but the results are the same.
I do not want to draw the cursor manually on my canvas because it has a slight input lag compared to this method.
All I want to do is have a Cursor that can support semi-transparency.
Any idea on how I can achieve this?
Thanks in advance.

This one is subtle (I've been stuck for a couple of years :) but I've cracked it today.
The cursor appears to only support on/off for transparency, so drawing first to a TYPE_INT_RGB image then copying that to a TYPE_INT_ARGB (then fixing the transparency) works.
Not sure if it's optimal, but it doesn't matter too much for small images like this.
int size = 32;
/*
* we need two buffered images as the cursor only supports on/off for alpha
*
* so we need to draw to an image without alpha support
* then draw that to one with alpha support
* then make "white" transparent
*/
BufferedImage image = new BufferedImage(size, size,
BufferedImage.TYPE_INT_RGB);
BufferedImage image2 = new BufferedImage(size, size,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
Graphics2D g2 = image2.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, size, size);
// turn on anti-aliasing.
g.setStroke(new BasicStroke(4.0f)); // 4-pixel lines
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(new Color(0.5f, 0f, 0f));
g.drawOval(3, 3, size-7, size-7);
g2.drawImage(image, 0, 0, null, null);
for (int y = 0 ; y < size ; y++) {
for (int x = 0 ; x < size ; x++) {
int rgb = image.getRGB(x, y);
int blue = rgb & 0xff;
int green = (rgb & 0xff00) >> 8;
int red = (rgb & 0xff0000) >> 16;
//int alpha = (rgb & 0xff000000) >> 24;
if (red == 255 && green == 255 && blue == 255) {
// make white transparent
image2.setRGB(x, y, 0);
}
}
}
eraserCursor = Toolkit.getDefaultToolkit().createCustomCursor(
image2, new Point(size / 2, size / 2), "eraserCursor");

In my code, to achieve translucent image, I use this snippet:
g.setComposite(AlphaComposite.SrcOver.derive(0.8f));
g.drawImage(image, 0, 0, null);
g.setComposite(AlphaComposite.SrcOver);
See whether it works for you...

Related

BufferedImage fill rectangle with transparent pixels

I have a BufferedImage and I am trying to fill a rectangle with transparent pixels. The problem is, instead of replacing the original pixels, the transparent pixels just go on top and do nothing. How can I get rid of the original pixel completely? The code works fine for any other opaque colors.
public static BufferedImage[] slice(BufferedImage img, int slices) {
BufferedImage[] ret = new BufferedImage[slices];
for (int i = 0; i < slices; i++) {
ret[i] = copyImage(img);
Graphics2D g2d = ret[i].createGraphics();
g2d.setColor(new Color(255, 255, 255, 0));
for(int j = i; j < img.getHeight(); j += slices)
g2d.fill(new Rectangle(0, j, img.getWidth(), slices - 1));
g2d.dispose();
}
return ret;
}
public static BufferedImage copyImage(BufferedImage source){
BufferedImage b = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = b.getGraphics();
g.drawImage(source, 0, 0, null);
g.dispose();
return b;
}
Using AlphaComposite, you have at least two options:
Either, use AlphaComposite.CLEAR as suggested, and just fill a rectangle in any color, and the result will be a completely transparent rectangle:
Graphics2D g = ...;
g.setComposite(AlphaComposite.Clear);
g.fillRect(x, y, w, h);
Or, you can use AlphaComposite.SRC, and paint in a transparent (or semi-transparent if you like) color. This will replace whatever color/transparency that is at the destination, and the result will be a rectangle with exactly the color specified:
Graphics2D g = ...;
g.setComposite(AlphaComposite.Src);
g.setColor(new Color(0x00000000, true);
g.fillRect(x, y, w, h);
The first approach is probably faster and easier if you want to just erase what is at the destination. The second is more flexible, as it allows replacing areas with semi-transparency or even gradients or other images.
PS: (As Josh says in the linked answer) Don't forget to reset the composite after you're done, to the default AlphaComposite.SrcOver, if you plan to do more painting using the same Graphics2D object.

Buffered Image padding not working

I have a image of dimension 215*112. I want to make it 215*142.
Src Img:
I used the following code:
BufferedImage image = ImageIO.read(new File("src.png"));
int h = 15;
BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight() + 2 * h, image.getType());
Graphics g = newImage.getGraphics();
g.setColor(Color.red);
g.fillRect(0, 0, image.getWidth(), image.getHeight() + 2 * h);
g.drawImage(image, 0, h, null);
g.dispose();
ImageIO.write(newImage, "png", new File("dest.png"));
I am getting following result:
Why the padding is also getting added to x direction?
Because the source you are giving has two transparent vertical bars on the left and right of the image
Use this image

Graphics2D - Antialias a Nearest-Neighbor along pixel borders

I have a (small) BufferedImage wich needs to be enlarged using nearest neighbor interpolation and then drawn to a Graphics2D. The Image has 1-bit alpha information and rendering it with antialiasing on and this code
AffineTransform oldT = g.getTransform();
Paint oldP = g.getPaint();
int w = img.getWidth(), h = img.getHeight();
g.transform(at);
g.setPaint(new TexturePaint(img, new Rectangle2D.Double(0, 0, w, h)));
g.fillRect(0, 0, w, h);
g.setPaint(oldP);
g.setTransform(oldT);
Where img is theBufferedImage to be rendered using at as an AffineTransform. The antialiasing activated by
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
produces good results, but only for the border of the entire image; looking very strange:
Notice the top border is actually the image border while the right is a transition from opaque to transparent pixel.
Is there a best practice to achieve the same antialiasing within the image as is applied to the border? Note that other interpolation methods than NearestNeighbor are unacceptable.
Thanks in advance for any hints.
The rendering shouldn't take too long since it is part of a paintComponent method, but preprocessing the BufferedImage is possible. The AffineTransform is (very) variable, though.
EDIT
I have achieved a small improvement by using a two-step method:
AffineTransform oldT = g.getTransform();
Paint oldP = g.getPaint();
int w = img.getWidth(), h = img.getHeight(), texw = (int) (w*oldT.getScaleX()), texh = (int) (h * oldT.getScaleY());
BufferedImage tex = new BufferedImage(texw, texh, BufferedImage.TYPE_INT_ARGB);
Graphics2D tg = tex.createGraphics();
tg.scale(oldT.getScaleX(), oldT.getScaleY());
tg.drawImage(img, 0, 0, this);
g.transform(at);
g.scale(1/oldT.getScaleX(), 1/oldT.getScaleY());
g.setPaint(new TexturePaint(tex, new Rectangle2D.Double(0, 0, texw, texh)));
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.fillRect(0, 0, texw, texh);
g.setPaint(oldP);
g.setTransform(oldT);
Resulting in this image:
This is still not perfect, though. I tried VALUE_INTERPOLATION_BICUBIC, but this is just incredibly slow and essentially produces the same result. I hope there is a way the get the exact same antialiasing effect, since it is still irritating.
A fairly complicated solution with a lot of tweaking and testing put into provides a very good quality by rendering each pixel as a shape reading its color and transparency from the BufferedImage:
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
public class VectorizedImage {
private final BufferedImage img;
public VectorizedImage(BufferedImage img) {
this.img = img;
}
public void draw(Graphics2D g) {
Color oldC = g.getColor();
Composite oldCo = g.getComposite();
AlphaComposite comp = AlphaComposite.SrcOver;
double lap = 1/Math.sqrt(g.getTransform().getDeterminant()); // This deals with inter-pixel borders letting the background shine through
Path2D pixel = new Path2D.Double();
pixel.moveTo(0, 0);
pixel.lineTo(0, 1);
pixel.lineTo(1, 1);
pixel.lineTo(1, 0);
pixel.lineTo(0, 0);
pixel.transform(AffineTransform.getScaleInstance(1+lap, 1+lap));
for (int i = 0; i < img.getWidth(); i++)
for (int j = 0; j < img.getHeight(); j++) {
g.setColor(new Color(img.getRGB(i, j)));
g.setComposite(comp.derive(img.getAlphaRaster().getSampleFloat(i, j, 0) / 255));
g.fill(pixel.createTransformedShape(AffineTransform.getTranslateInstance(i, j)));
}
g.setColor(oldC);
g.setComposite(oldCo);
}
}
The result is this nicely rendered edge:

Reading pixels from BufferedImage - wrong color values

When a spaceship is destroyed I create list containg pixels of spaceship's image. Pixels are objects of my Pixel class. After creating list it's added to main list where various actions are performed on them. This is how my code looks like:
//Code which creates an array
List<Pixel> pixels = new LinkedList<>();
BufferedImage buff = (BufferedImage)image;
for (int px = 0; px < buff.getWidth(); px++) {
for (int py = 0; py < buff.getHeight(); py++) {
int rgb = buff.getRGB(px, py);
int red = (rgb & 0x00ff0000) >> 16;
int green = (rgb & 0x0000ff00) >> 8;
int blue = rgb & 0x000000ff;
int alpha = (rgb >> 24) & 0xff;
if (alpha == 255) {
pixels.add(new Pixel(px, py, red, green, blue));
}
}
}
//Pixel class constructor
Pixel(float x, float y, int red, int green, int blue) {
super(x, y);
BufferedImage buff = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = buff.getRaster();
//LOOKS EVERYTHING IS OKAY SINCE THIS LINE SO THE ERROR MUST BE SOMEWHERE IN THOSE 2 LINES
raster.setPixel(0, 0, new int[]{red, blue, green, 255});
image = buff;
}
Short explanation: image is private field of type Image. It's used in repaint() method which paints pixel using drawImage() method. And about my problem: Eveything works almost okay. Pixels are creating on right position but all are violet-color. They have different tones(brighter and darker) but are all violet instead of having the same colors as image's colors! Why is this happening? Why violet? Could someone help me unserstand this strange behaviour?
It's probably a mixup of green and blue values in your setPixel method. Colors are usually given in RGB order, which is how you unpacked them from your BufferedImage.
Instead of
raster.setPixel(0, 0, new int[]{red, blue, green, 255});
try
raster.setPixel(0, 0, new int[]{red, green, blue, 255});
If that doesn't work you may have to tinker with different variable orders in your array until it looks right.

Removing BufferedImage pixel values and or setting them transparent

I have been working with the polygon class and trying to set the pixel values inside of the polygon to transparent or remove them all together if this is possible, however I have hit a bit of a wall as I am trying to store the values as RGB int values and don't know how I would be able to make a pixel transparent/removed via this method.
Additionally to this I would also like to do the same thing but keeping pixels inside the polygon and deleting those outside if possible in order to be left with only the pixels contained within the polygon. I have searched around for this before but to no avail.
I did attempt to create a SSCCE for this to make it easier to work with and view for anyone taking the time to help however as its part of a much larger programme that I am working on creating one is proving to take some time, however once I have one working to better demonstrate this problem I will edit this post.
Thank you to anyone for taking the time to help me with this problem
Below I have some code for what I am currently using to segment the pixels that are contained within an already specified polygon. This is extremely similar to the way i do it for setting pixels outside the polygon to transparent only with the if statement arguments swapped around to remove a segment of the image and haveing a return for newImage rather than save image stuff and it works perfectly, however when I do it this way to save the pixels contained in the polygon it doesn't save for some reason.
public void saveSegment(int tabNum, BufferedImage img) {
segmentation = new GUI.Segmentation();
Polygon p = new Polygon();
Color pixel;
p = createPolygon(segmentation);
int height = img.getHeight();
int width = img.getWidth();
newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
//loop through the image to fill the 2d array up with the segmented pixels
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
//If the pixel is inside polygon
if(p.contains(x, y) == true) {
pixel = new Color(img.getRGB(x, y));
//set pixel equal to the RGB value of the pixel being looked at
int r = pixel.getRed(); // red component 0...255
int g = pixel.getGreen(); // green component 0...255
int b = pixel.getBlue(); // blue component 0...255
int a = pixel.getAlpha(); // alpha (transparency) component 0...255
int col = (a << 24) | (r << 16) | (g << 8) | b;
newImage.setRGB(x, y, col);
}
else {
pixel = new Color(img.getRGB(x, y));
int a = 0; // alpha (transparency) component 0...255
int col = (a << 24);
newImage.setRGB(x, y, col);
}
}
}
try {
//then save as image once all in correct order
ImageIO.write(newImage, "bmp", new File("saved-Segment.bmp"));
JOptionPane.showMessageDialog(null, "New image saved successfully");
} catch (IOException e) {
e.printStackTrace();
}
}
An easier way is to use Java2D's clipping capability:
BufferedImage cutHole(BufferedImage image, Polygon holeShape) {
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
Graphics2D g = newImage.createGraphics();
Rectangle entireImage =
new Rectangle(image.getWidth(), image.getHeight());
Area clip = new Area(entireImage);
clip.subtract(new Area(holeShape));
g.clip(clip);
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
BufferedImage clipToPolygon(BufferedImage image, Polygon polygon) {
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
Graphics2D g = newImage.createGraphics();
g.clip(polygon);
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}

Categories