Avoid Transparent pixels of an image - java

I use GWT and I want to implement a simple MouseMoveHandler on an image.
So I simply implement it like this :
Image icon = new Image(Context.IMAGES.infoIcon());
icon.addMouseMoveHandler(new MouseMoveHandler() {
#Override
public void onMouseMove(MouseMoveEvent event) {
int left = event.getScreenX();
int top = event.getScreenY();
}
});
This is OK, but I want the event avoid transparent pixels.
Is there any way?

The only option I can think of is to draw the picture on an HTML5 canvas. Then analyze every pixels RGB values and decide whether to handle the event or not.
Canvas canvas = Canvas.createIfSupported();
Image imagePng = new Image("test.png");
if (_canvas != null) {
panel.add(canvas);
} else {
// No canvas support in this browser
panel.add(imagePng);
}
canvas.getContext2d().drawImage(ImageElement.as(imagePng.getElement()), 0, 0);
Then later in your MouseHandler code. Evaluate the position of the mouse handler on the image (x, y), then analyze the image color on this position.
int width = canvas.getOffsetWidth();
int height = canvas.getOffsetHeight();
ImageData imageData = canvas.getContext2d().getImageData(0, 0, width, height);
int r = imageData .getRedAt(x, y);
int g = imageData .getGreenAt(x, y);
int b = imageData .getBlueAt(x, y);
if (!isRGBValueTransparent(r, g, b)) {
// Do something;
}
The code can be improved, if you don't do the calculation every time but keep some kind of matrix where you store whether a pixel is transparent or not. Then just look it up in said matrix.

Related

How to change the color of a .png image in JavaFX?

I have a PNG image like this:
how could the color be changed in JavaFX?
You can use a Lighting effect, here is an example:
Lighting lighting = new Lighting(new Light.Distant(45, 90, Color.RED));
ColorAdjust bright = new ColorAdjust(0, 1, 1, 1);
lighting.setContentInput(bright);
lighting.setSurfaceScale(0.0);
imageView.setEffect(lighting);
Output:
I really liked the solution of #M.S which uses Lighting. However if you want to generate a reusable Image to render multiple ImageView nodes, below is one possible solution.
Please note that the below solution is to change the color of entire image by keeping the transparent pixels. May be if you have a white background images, you can tweak the code accordingly.
public static Image blendColor(final Image sourceImage, final Color blendColor) {
final double r = blendColor.getRed();
final double g = blendColor.getGreen();
final double b = blendColor.getBlue();
final int w = (int) sourceImage.getWidth();
final int h = (int) sourceImage.getHeight();
final WritableImage outputImage = new WritableImage(w, h);
final PixelWriter writer = outputImage.getPixelWriter();
final PixelReader reader = sourceImage.getPixelReader();
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
// Keeping the opacity of every pixel as it is.
writer.setColor(x, y, new Color(r, g, b, reader.getColor(x, y).getOpacity()));
}
}
return outputImage;
}

Java recoloring BufferedImage not working with an image of a larger height

I have a program that is supposed to take the RGB values of an image and then multiply them by some constants, and then draw the new image on a JPanel. The problem is that if my image is over a certain height, specifically over 187 pixels, the new colored image is different than an image with a height of less than 187px.
The JPanel shows this: example.
Notice how the longer recolored image is different than the shorter one. I'm sure that the shorter image's colors are correct, and I have no idea how it's getting messed up.
public class RecolorImage extends JPanel {
public static int scale = 3;
public static BufferedImage walk, walkRecolored;
public static BufferedImage shortWalk, shortWalkRecolored;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(200*scale, 400*scale);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RecolorImage());
walk = ImageLoader.loadImage("/playerWalk.png");
walkRecolored = recolor(walk);
shortWalk = ImageLoader.loadImage("/playerWalkShort.png");
shortWalkRecolored = recolor(shortWalk);
frame.setVisible(true);
}
#Override
public void paint(Graphics graphics) {
Graphics2D g = (Graphics2D) graphics;
g.scale(scale, scale);
g.drawImage(walk, 10, 10, null);
g.drawImage(walkRecolored, 40, 10, null);
g.drawImage(shortWalk, 70, 10, null);
g.drawImage(shortWalkRecolored, 100, 10, null);
}
The recolor method:
public static BufferedImage recolor(BufferedImage image) {
BufferedImage outputImage = deepCopy(image);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int rgb = image.getRGB(x, y);
Color c = new Color(rgb);
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
r *= 0.791;
g *= 0.590;
b *= 0.513;
int newRGB = (rgb & 0xff000000) | (r << 16) | (g << 8) | b;
outputImage.setRGB(x, y, newRGB);
}
}
return outputImage;
}
How I load the images and make deep copies:
public static BufferedImage loadImage(String path) {
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static BufferedImage deepCopy(BufferedImage image) {
ColorModel colorModel = image.getColorModel();
boolean isAlphaPremultiplied = colorModel.isAlphaPremultiplied();
WritableRaster raster = image.copyData(null);
return new BufferedImage(colorModel, raster, isAlphaPremultiplied, null);
}
My original images: the tall image and short image. Thanks for any help!
Your source images have different color models:
the short image uses 4 bytes per pixel (RGB and alpha)
the tall image uses 1 byte per pixel (index into a palette of 256 colors)
Your recolored images use the same color model as the source images (thanks to the deepCopy method), therefore the recolored image for the tall image also uses the same color palette as the source image, meaning that it cannot contain all the colors you want.
Since your recoloring code overwrites each pixel of the output image anyway the deep copy operation is unnecessary. Instead you would better create a full color image as target image like this:
public static BufferedImage recolor(BufferedImage image) {
BufferedImage outputImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
//... code as before
}

Why isn't my method for removing this color working?

I'm trying to make a Mario game clone, and right now, in my constructor, I have a method that is supposed to make a certain color transparent instead of the current pinkish (R: 255, G: 0, B: 254). According to Photoshop, the hex value is ff00fe. My method is:
public Mario(){
this.state = MarioState.SMALL;
this.x = 54;
this.y = 806;
URL spriteAtLoc = getClass().getResource("sprites/Mario/SmallStandFaceRight.bmp");
try{
sprite = ImageIO.read(spriteAtLoc);
int width = sprite.getWidth();
int height = sprite.getHeight();
int[] pixels = new int[width * height];
sprite.getRGB(0, 0, width, height, pixels, 0, width);
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] == 0xFFff00fe) {
pixels[i] = 0x00ff00fe; //this is supposed to set alpha value to 0 and make the target color transparent
}
}
} catch(IOException e){
System.out.println("sprite not found");
e.printStackTrace();
}
}
it runs and compiles, but sprite comes out exactly the same when I render it. (edit: perhaps of note I do not have super.paintComponent(g) in my paintComponent(g) method. The sprites are .bmps.
You are only retrieving the pixels using BufferedImage.getRGB. That returns a copy of the data in a certain area of the BufferedImage.
Any change you make to the int[] returned is not automatically reflected back into the image.
To update the image, you need to call BufferedImage.setRGB after you change the int[]:
sprite.setRGB(0, 0, width, height, pixels, 0, width);
Another change you should probably make (and this involves a little guesswork as I don't have your bmp to test with) - the BufferedImage returned by ImageIO.read may have type BufferedImage.TYPE_INT_RGB - meaning that it doesn't have an alpha channel. You can verify by printing sprite.getType(), if that prints 1 it's TYPE_INT_RGB without an alpha channel.
To get an alpha channel, create a new BufferedImage of the right size and then set the converted int[] on that image, then use the new image from then on:
BufferedImage newSprite = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
newSprite.setRGB(0, 0, width, height, pixels, 0, width);
sprite = newSprite; // Swap the old sprite for the new one with an alpha channel
BMP images don't provide an alpha channel, you have to set it manually (as you do in your code)...
when you check your pixel to have a certain color you have to check without alpha (BMP has no alpha it's always 0x0).
if (pixels[i] == 0x00ff00fe) { //THIS is the color WITHOUT alpha
pixels[i] = 0xFFff00fe; //set alpha to 0xFF to make this pixel transparent
}
so in short: you did all right but mixed it up a bit ^^
This works:
private BufferedImage switchColors(BufferedImage img) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// top left pixel is presumed to be BG color
int rgb = img.getRGB(0, 0);
for (int xx=0; xx<w; xx++) {
for (int yy=0; yy<h; yy++) {
int rgb2 = img.getRGB(xx, yy);
if (rgb2!=rgb) {
bi.setRGB(xx, yy, rgb2);
}
}
}
return bi;
}

Blurring a copy of the current screen then rendering it as a texture in the game pause screen

I call applyEffect in my game logic when the screen is paused, but the image is not set. In its place i get a black background. I instead expect it to show a blurred texture, of the form in image below as expected once I set the gaussian blur shader .
This is sadly the result ;
Why is the screen black?
Here is how i call the method
if (Gdx.input.isKeyPressed(Keys.P) || pause)
{
state = State.GAME_PAUSED;
setBackgroundTexture(applyEffect());
gameScreenGamePauseMenu.sendInMenu();
}
public TextureRegion applyEffect()
{
boolean m_fboEnabled = true;
FrameBuffer m_fbo = null;
float m_fboScaler = 1;
TextureRegion m_fboRegion = null;
int width = Gdx.graphics.getWidth();
int height = Gdx.graphics.getHeight();
if(m_fboEnabled) // enable or disable the supersampling
{
if(m_fbo == null)
{
// m_fboScaler increase or decrease the antialiasing quality
m_fbo = new FrameBuffer(Format.RGB565, (int)(width * m_fboScaler), (int)(height * m_fboScaler), false);
m_fboRegion = new TextureRegion(m_fbo.getColorBufferTexture());
m_fboRegion.flip(false, true);
}
m_fbo.begin();
}
if(m_fbo != null)
{
m_fbo.end();
getStage().getBatch().begin();
getStage().getBatch().draw(m_fboRegion, 0, 0, width, height);
getStage().getBatch().end();
}
return m_fboRegion;
}
Iam using Libgdx to make my game

Cropping image lowers quality and border looks bad

Using some math, i created the following java-function, to input a Bitmap, and have it crop out a centered square in which a circle is cropped out again with a black border around it.
The rest of the square should be transparent.
Additionatly, there is a transparent distance to the sides to not damage the preview when sending the image via Messengers.
The code of my function is as following:
public static Bitmap edit_image(Bitmap src,boolean makeborder) {
int width = src.getWidth();
int height = src.getHeight();
int A, R, G, B;
int pixel;
int middlex = width/2;
int middley = height/2;
int seitenlaenge,startx,starty;
if(width>height)
{
seitenlaenge=height;
starty=0;
startx = middlex - (seitenlaenge/2);
}
else
{
seitenlaenge=width;
startx=0;
starty = middley - (seitenlaenge/2);
}
int kreisradius = seitenlaenge/2;
int mittx = startx + kreisradius;
int mitty = starty + kreisradius;
int border=2;
int seitenabstand=55;
Bitmap bmOut = Bitmap.createBitmap(seitenlaenge+seitenabstand, seitenlaenge+seitenabstand, Bitmap.Config.ARGB_8888);
bmOut.setHasAlpha(true);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < height; ++y) {
int distzumitte = (int) (Math.pow(mittx-x,2) + Math.pow(mitty-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
distzumitte = (int) Math.sqrt(distzumitte);
pixel = src.getPixel(x, y);
A = Color.alpha(pixel);
R = (int)Color.red(pixel);
G = (int)Color.green(pixel);
B = (int)Color.blue(pixel);
int color = Color.argb(A, R, G, B);
int afterx=x-startx+(seitenabstand/2);
int aftery=y-starty+(seitenabstand/2);
if(x < startx || y < starty || afterx>=seitenlaenge+seitenabstand || aftery>=seitenlaenge+seitenabstand) //seitenrand
{
continue;
}
else if(distzumitte > kreisradius)
{
color=0x00FFFFFF;
}
else if(distzumitte > kreisradius-border && makeborder) //border
{
color = Color.argb(A, 0, 0, 0);
}
bmOut.setPixel(afterx, aftery, color);
}
}
return bmOut;
}
This function works fine, but there are some problems occuring that i wasn't able to resolve yet.
The quality of the image is decreased significantly
The border is not really round, but appears to be flat at the edges of the image (on some devices?!)
I'd appreciate any help regarding that problems. I got to admit that i'm not the best in math and there should probably be a better formula to ceate the border.
your source code is hard to read, since it is a mix of German and English in the variable names. Additionally you don't say which image library you use, so we don't exactly know where the classes Bitmap and Color come from.
Anyway, it is very obvious, that you are operating only on a Bitmap. Bitmap means the whole image is stored in the RAM pixel by pixel. There is no lossy compression. I don't see anything in your source code, that can affect the quality of the image.
It is very likely, that the answer is in the Code that you don't show us. Additionally, what you describe (botrh of the problems) sounds like a very typical low quality JPEG compression. I am sure, somewhere after you call you function, you convert/save the image to a JPEG. Try to do that at that position to BMP, TIFF or PNG and see that the error disappears magically. Maybe you can also set the quality level of the JPEG somewhere to avoid that.
To make it easier for others (maybe) also to find a good answer, please allow me to translate your code to English:
public static Bitmap edit_image(Bitmap src,boolean makeborder) {
int width = src.getWidth();
int height = src.getHeight();
int A, R, G, B;
int pixel;
int middlex = width/2;
int middley = height/2;
int sideLength,startx,starty;
if(width>height)
{
sideLength=height;
starty=0;
startx = middlex - (sideLength/2);
}
else
{
sideLength=width;
startx=0;
starty = middley - (sideLength/2);
}
int circleRadius = sideLength/2;
int middleX = startx + circleRadius;
int middleY = starty + circleRadius;
int border=2;
int sideDistance=55;
Bitmap bmOut = Bitmap.createBitmap(sideLength+sideDistance, sideLength+sideDistance, Bitmap.Config.ARGB_8888);
bmOut.setHasAlpha(true);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < height; ++y) {
int distanceToMiddle = (int) (Math.pow(middleX-x,2) + Math.pow(middleY-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
distanceToMiddle = (int) Math.sqrt(distanceToMiddle);
pixel = src.getPixel(x, y);
A = Color.alpha(pixel);
R = (int)Color.red(pixel);
G = (int)Color.green(pixel);
B = (int)Color.blue(pixel);
int color = Color.argb(A, R, G, B);
int afterx=x-startx+(sideDistance/2);
int aftery=y-starty+(sideDistance/2);
if(x < startx || y < starty || afterx>=sideLength+sideDistance || aftery>=sideLength+sideDistance) //margin
{
continue;
}
else if(distanceToMiddle > circleRadius)
{
color=0x00FFFFFF;
}
else if(distanceToMiddle > circleRadius-border && makeborder) //border
{
color = Color.argb(A, 0, 0, 0);
}
bmOut.setPixel(afterx, aftery, color);
}
}
return bmOut;
}
I think that you need to check PorterDuffXferMode.
You will find some technical informations about compositing images modes HERE.
There is some good example of making bitmap with rounded edges HERE. You just need to tweak a bit source code and you're ready to go...
Hope it will help.
Regarding the quality I can't see anything wrong with your method. Running the code with Java Swing no quality is lost. The only problem is that the image has aliased edges.
The aliasing problem will tend to disappear as the screen resolution increases and would be more noticeable for lower resolutions. This might explain why you see it in some devices only.The same problem applies to your border but in that case it would be more noticable since the color is single black.
Your algorithm defines a square area of the original image. To find the square it starts from the image's center and expand to either the width or the height of the image whichever is smaller. I am referring to this area as the square.
The aliasing is caused by your code that sets the colors (I am using pseudo-code):
if ( outOfSquare() ) {
continue; // case 1: this works but you depend upon the new image' s default pixel value i.e. transparent black
} else if ( insideSquare() && ! insideCircle() ) {
color = 0x00FFFFFF; // case 2: transparent white. <- Redundant
} else if ( insideBorder() ) {
color = Color.argb(A, 0, 0, 0); // case 3: Black color using the transparency of the original image.
} else { // inside the inner circle
// case 4: leave image color
}
Some notes about the code:
Case 1 depends upon the default pixel value of the original image i.e. transparent black. It works but better to set it explicitly
Case 2 is redundant. Handle it in the same way you handle case 1. We are only interested in what happens inside the circle.
Case 3 (when you draw the border) is not clear what it expects. Using the alpha of the original image has the potential of messing up your new image if it happens that the original alpha varies along the circle's edges. So this is clearly wrong and depending on the image, can potentially be another cause of your problems.
Case 4 is ok.
Now at your circle's periphery the following color transitions take place:
If border is not used: full transparency -> full image color (case 2 and 4 in the pseudocode)
If border is used: full transparency -> full black -> full image color (cases 2, 3 and 4)
To achieve a better quality at the edges you need to introduce some intermediate states that would make the transitions smoother (the new transitions are shown in italics):
Border is not used: full transparency -> partial transparency with image color -> full image color
Border is used: full transparency -> partial transparency of Black color -> full Black color -> partial transparency of Black color + Image color (i.e. blending) -> Full image color
I hope that helps

Categories

Resources