BufferedImage fill rectangle with transparent pixels - java

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.

Related

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:

Disable dithering on a TYPE_BYTE_INDEXED BufferedImage

I have a 256-color BufferedImage on which I want to draw another BufferedImage (> 256 colors).
Java's default behaviour is to dither colors which cannot be representeed in the low-color model. I want to disable this (thus choosing the next best color available in the low-color model), so i tried to use RenderingHints to specify a new rendering behaviour but it does not work:
public BufferedImage filter(BufferedImage src) {
BufferedImage convertedImage = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
Graphics2D g2d = (Graphics2D) convertedImage.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.drawImage(src, 0, 0, null);
return convertedImage;
}
Ideas?
UPDATE:
I solved the problem by drawing the new image pixel-by-pixel which may not be very fast, but it works. See my answer for details.
I solved the problem by drawing the new image pixel-by-pixel which may not be very fast, but it works:
public BufferedImage filter(BufferedImage src) {
BufferedImage convertedImage = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
for (int x = 0; x < src.getWidth(); x++) {
for (int y = 0; y < src.getHeight(); y++) {
convertedImage.setRGB(x, y, src.getRGB(x, y));
}
}
return convertedImage;
}

Getting a JPanel graphics color is always the same color

I have a vertical color bar, it has 7 main colors all combined as a gradient. I then take that and paint it to a JPanel like this:
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
int w = getWidth();
int h = getHeight();
Point2D start = new Point2D.Float(0, 0);
Point2D end = new Point2D.Float(0, h);
float[] dist = {
0.02f,
0.28f,
0.42f,
0.56f,
0.70f,
0.84f,
1.0f
};
Color[] colors = {
Color.red,
Color.magenta,
Color.blue,
Color.cyan,
Color.green,
Color.yellow,
Color.red
};
LinearGradientPaint p = new LinearGradientPaint(start, end, dist, colors);
g2d.setPaint(p);
g2d.fillRect(0, 0, w, h);
}
I then have have a click event in the same class, which looks like this:
public void mouseClick(MouseEvent evt){
BufferedImage img = (BufferedImage)this.createImage(getWidth(), getHeight());
int[] colors = new int[3];
int x = evt.getX();
int y = evt.getY();
img.getRaster().getPixel(evt.getX(), evt.getY(), colors);
ColorPickerDialog.sldColor = new Color(colors[0], colors[1], colors[2]);
getParent().invalidate();
getParent().repaint();
}
The line img.getRaster().getPixel(evt.getX(), evt.getY(), colors); always returns the RGB colors:
240
240
240
And I can click anywhere, on the Red, Yellow, Green, Cyan, etc. and I always get those RGB colors back. Why?
I think I see the problem. The line
img.getRaster().getPixel(evt.getX(), evt.getY(), colors);
returns an int[] corresponding to the RGB colors. The getPixel method takes in your array as
a parameter, but it returns its own array. It never actually touches your array. what you want to do is this.
int[] colors = img.getRaster().getPixel(evt.getX(), evt.getY(), new int[3]);
that should store the return value of the method in your array instead of whatever it's default value is.
I got it!
I replaced this line:
BufferedImage img = (BufferedImage)this.createImage(getWidth(), getHeight());
With this:
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
this.paint(g);
And now it works perfectly!

Inner-Transparent Selection Window in Java using GlassPane

I am trying to achieve the following
http://www.qksnap.com/i/3hunq/4ld0v/screenshot.png
I am currently able to draw rectangles successfully on a semi-transparent glasspane background using the following code:
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.black); // black background
g.fillRect(0, 0, frame.getWidth(), frame.getHeight());
g2.setColor(Color.GREEN.darker());
if (getRect() != null && isDrawing()) {
g2.draw(getRect()); // draw our rectangle (simple Rectangle class)
}
g2.dispose();
}
Which works great, however, I would love to have the area within the rectangle be completely transparent while the outside was still darken much like the screenshot above.
Any ideas?
..have the area within the rectangle be completely transparent while the outside was still darken much like the screenshot above.
Create a Rectangle (componentRect) that is the size of the component being painted.
Create an Area (componentArea) of that shape (new Area(componentRect)).
Create an Area (selectionArea) of the selectionRectangle.
Call componentArea.subtract(selectionArea) to remove the selected part.
Call Graphics.setClip(componentArea)
Paint the semi-transparent color.
(Clear the clipping area if more paint operations are required).
As Andrew has suggested (just beat me while I was finishing off my example)
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g.setColor(Color.black); // black background
Area area = new Area();
// This is the area that will filled...
area.add(new Area(new Rectangle2D.Float(0, 0, getWidth(), getHeight())));
g2.setColor(Color.GREEN.darker());
int width = getWidth() - 1;
int height = getHeight() - 1;
int openWidth = 200;
int openHeight = 200;
int x = (width - openWidth) / 2;
int y = (height - openHeight) / 2;
// This is the area that will be uneffected
area.subtract(new Area(new Rectangle2D.Float(x, y, openWidth, openHeight)));
// Set up a AlphaComposite
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2.fill(area);
g2.dispose();
}

Cursor with Semi-Transparency / Anti-Aliasing

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...

Categories