How do I fade a BufferedImage when manipulating it's pixel data? - java

I am trying to do something I have never done before.
My goal is to be able to manipulate the opacity of a BufferedImage. First off, I do not use Graphics. I am developing a simple Game Engine and I only use pixel data from BufferedImages.
What have I tried?
I made my own "Image" class that takes in a BufferedImage.
BufferedImage image = null;
try {
image = ImageIO.read(Image.class.getResourceAsStream(resourcePath));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
width = image.getWidth();
height = image.getHeight();
hasAlpha = image.getColorModel().hasAlpha();
pixels = image.getRGB(0, 0, width, height,null,0,width);
After that I proceeded to my rendering code. I have a function called "drawImage()" that takes the image and it's data and then puts it out on a specific spot on the screen depending on the users wishes. I however want the user to choose it's opacity as well.
I started out by taking the pixel data for that pixel and I instead made a Color.
Color c = new Color(image.getPixels()[value2],true);
The "true" statement, what I know of, says that it should "care" about Alpha.
After that I place the pixel by using this code:
pixels[x + y * pWidth] = new Color(c.getRed(),c.getGreen(),c.getBlue(),alphaValue).getRGB();
This doesn't work. It doesnt output any errors and I can change the alphaValue to a number between 0 and infinity. However one issue I found is that it doesnt affect the image at all when I change the alpha from 255 to something like 50. It stays the same. The affect comes at 0-5. Another thing that I noticed is that when I set alphaValue to something like 2 I get an animation instead of the image with lower opacity. Sounds weird, that's why I provided a .GIF of the issue:
I guarantee you that there is no code that changes the alphaValue. During that "animation" it is set to 2 and nothing else.
So something is weird here and I wonder if you guys know the issue?
It might have something to do with the Image class and that I don't set it to BufferedImage.RGBA but I don't know how I can do so when I load an image?
If you need any more information, please leave a comment and I will provide more info!
Thanks in advance!
EDIT
I clear the screen every 1/60 of a second by manipulating the backgrounds pixels (also a bufferedImage):
public void clear() {
for(int i = 0; i < pixels.length; i++) {
pixels[i] = clearColor.getRGB();
}
}
Possible issue
I did some troubleshooting and I found out that it actually somehow increased in opacity each frame as #Joni said. Somehow the opacity or alpha seems to increase each frame but I am clearing the screen between drawings. I will troubleshoot further.
UPDATE
I made so I could move the image around and found this weird behaviour:
Link to Imgur
It seems that the "alpha" isn't reset. I know that the RGB is being reset but something is really off when it comes to the Alpha.
I changed the clear function to this:
public void clear() {
for(int i = 0; i < pixels.length; i++) {
pixels[i] = new Color(clearColor.getRed(),clearColor.getGreen(),clearColor.getBlue(),255).getRGB();
}
}
And I defined clearColor as:
Color clearColor = new Color(Color.black.getRGB(),true);
That did however not work either. Does anyone have a solution?

Your game engine is drawing the image on top of itself in a loop. After drawing it enough many times the effect is the same as not having used transparency at all. The lower the alpha, the longer it takes though: with 50% alpha you need 7 frames to get 99% opacity, with 5% alpha you need about 90 frames.
For example, suppose you are drawing a pixel value of 100 on a screen that's intially 0 (black) with 50% opacity. After the first frame, the output pixel value is .5*100 + .5*0 = 50. The second frame is drawn on top of the first frame, so the output pixel value is .5*100 + .5*50 = 75. The third frame, drawn on top of the second frame, will show .5*100 + .5*75 = 87.5.
To avoid this, you need to fill a solid background color under the image in every frame.

Related

checking to see if the image is the same

So i have a lot of questions to this but first here is my assignment.
Write a method public Picture authenticate(Picture p) which checks to
see if ”this” picture and the picture p are identical. It returns a
new picture that it the same size as ”this” picture. For each pixel
position in the original picture ”this”, compare the color of that
pixel with the color of the pixel in the same position from second
picture. If the two pixels have the same color (i.e. the original is
not modified in the copy), set the color of this position in the
resulting picture to white. If the two pixels do not have the same
color, set the color of this position in the resulting picture to the
color of the pixel from the original picture. Return the resulting
picture. If the two pictures are not the same size, the resulting
picture should contain all black pixels
This is my code as of right now
public Picture Authenticate(Picture p)
{
Pixel sourcePixel = null;
Pixel targetPixel = null;
Picture result = new Picture(this.getWidth(), this.getHeight());
for(int sourceX = 0, targetX= getWidth()-1; sourceX < getWidth(); sourceX++,targetX++)
{
for(int sourceY = 0, targetY= getHeight(); sourceY< getHeight(); sourceY++,targetY++)
{
sourcePixel = this.getPixel(sourceX,sourceY);
targetPixel = result.getPixel(targetX, targetY);
}
if (sourcePixel.getColor() == targetPixel.getColor())
targetPixel.setColor(Color.WHITE);
else
targetPixel.setColor(Color.BLACK);
}
return result;
}
Here are my questions:
How do i make p in the authenticate parameter fit into this?
Am i going about this the right way within the loop?
With my if and else statement, i feel as if that is not even close
to changing the pixel i need tooa
How do i make p in the authenticate parameter fit into this?
this is your source image; p is the target image; result is where you place the output pixels resulting from comparing the source and target.
Am i going about this the right way within the loop?
Yes, but the if/else statement belongs inside the inner loop
With my if and else statement, i feel as if that is not even close to changing the pixel i need to
You are close. Keep working on it and step through the code in your debugger to see what is actually happening.
Don't get discouraged, this is closer than you think.

2D Dynamic Lighting in Java

I am making a game that has campfire objects. What I want to do is to brighten all pixels in a circle around each campfire. However, looping through every pixel and changing those within the radius is not all that efficient and makes the game run at ~7 fps. Ideas on how to either make this process efficient or simulate light differently?
I haven't written the code for the fires but this is the basic loop to check each pixel/change its brightness based on a number:
public static BufferedImage updateLightLevels(BufferedImage img, float light)
{
BufferedImage brightnessBuffer = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
brightnessBuffer.getGraphics().drawImage(img, 0, 0, null);
for(int i = 0; i < brightnessBuffer.getWidth(); i++)
{
for(int a = 0; a < brightnessBuffer.getHeight(); a++)
{
//get the color at the pixel
int rgb = brightnessBuffer.getRGB(i, a);
//check to see if it is transparent
int alpha = (rgb >> 24) & 0x000000FF;
if(alpha != 0)
{
//make a new color
Color rgbColor = new Color(rgb);
//turn it into an hsb color
float[] hsbCol = Color.RGBtoHSB(rgbColor.getRed(), rgbColor.getGreen(), rgbColor.getBlue(), null);
//lower it by the certain amount
//if the pixel is already darker then push it all the way to black
if(hsbCol[2] <= light)
hsbCol[2] -= (hsbCol[2]) - .01f;
else
hsbCol[2] -= light;
//turn the hsb color into a rgb color
int rgbNew = Color.HSBtoRGB(hsbCol[0], hsbCol[1], hsbCol[2]);
//set the pixel to the new color
brightnessBuffer.setRGB(i, a, rgbNew);
}
}
}
return brightnessBuffer;
}
I apologize if my code is not clean, I'm self taught.
I can give you lots of approaches.
You're currently rendering on the CPU, and you're checking every single pixel. That's hardcore brute force, and brute force isn't what the CPU is best at. It works, but as you've seen, the performance is abysmal.
I'd point you in two directions that would massively improve your performance:
Method 1 - Culling. Does every single pixel really need to have its lighting calculated? If you could instead calculate a general "ambient light", then you could paint most of the pixels in that ambient light, and then only calculate the really proper lighting for pixels closest to lights; so lights throw a "spot" effect which fades into the ambient. That way you're only ever performing checks on a few of the pixels of the screen at a time (the circle area around each light). The code you posted just looks like it paints every pixel, I'm not seeing where the "circle" dropoff is even applied.
Edit:
Instead, sweep through the lights, and just loop through local offsets of the light position.
for(Light l : Lights){
for(int x = l.getX() -LIGHT_DISTANCE, x< l.getX() + LIGHT_DISTANCE, y++){
for(int y = l.getY() - LIGHT_DISTANCE, y < l.getY() + LIGHT_DISTANCE, y++){
//calculate light
int rgb = brightnessBuffer.getRGB(x, y);
//do stuff
}
}
You may want to add a check with that method so overlapping lights don't cause a bunch of rechecks, unless you DO want that behavior (ideally those pixels would be twice as bright)
Method 2 - Offhand calculation to the GPU. There's a reason we have graphics cards; they're specifically built to be able to number crunch those situations where you really need brute force. If you can offload this process to the GPU as a shader, then it'll run licketysplit, even if you run it on every pixel several times over. This will require you to learn graphics APIs however, but if you're working in java, LibGDX makes it very painless to render using the GPU and pass off a couple shaders to the GPU.
I am uncertain about the way in which you are going about calculating light values, but I do know that using the BufferedImage.getRGB() and BufferedImage.setRGB() methods is very slow.
I would suggest accessing the pixels of the BufferedImage directly from an array (much faster IMO)
to do this:
BufferedImage lightImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
Raster r = lightImage.getRaster();
int[] lightPixels = ((DataBufferInt)r.getDataBuffer()).getData();
Now, changing any pixel in this array will show on your image. Note that the values used in this array are color values in the format of whatever format you defined your image with.
In this case it is TYPE_INT_ARGB meaning you will have to include the alpha value in the number when setting the coloar (RRGGBB*AA*)
Since this array is a 1D array, it is more difficult to access pixels using x and y co-ordinates. The following method is an implementation of accessing pixels from the lightPixels array more easily.
public void setLight(int x, int y,int[] array,int width, int value){
array[width*y+x] = value;
}
*note: width is the width of your level, or the width of the 2D array your level might exist as, if it was a 2D array.
You can also get pixels from the lightPixels array with a similar method, just excluding the value and returning the array[width*y+x].
It is up to you how you use the setLight() and getLight() methods but in the cases that I have encountered, using this method is much faster than using getRGB and setRGB.
Hope this helps

Drawing objects behind circle except the ones behind 'background'

Situation: I have a canvas on an Android game, I have some objects (I will keep it as simple as possible):World(where are storaged all Laser and Block objects), Block and Laser. I can draw all this objects in the canvas.
I would like to 'hide' them behind a black 'background', and then draw a blurry 'transparent' circle, so all objects are hidden behind the black background, except the objects behing the circle.
I have thought about it, but I can't think of an approach to do this.
Images:
This is my actual situation:
This is the expected:
Do something like this:
public void drawBitmapsInCanvas(Canvas c){
c.drawBitmap(block, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawBitmap(block2, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawBitmap(laser, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawColor(Color.BLACK);//this hides everything under your black background.
c.drawBitmap(circle, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
}
If you want transparency:
Paint paint =new Paint();
paint.setARGB(120,0,0,0); //for the "120" parameter, 0 is completely transparent, 255 is completely opaque.
paint.setAntiAlias(true);
c.drawBitmap(bmp,Rect r,Rect rr, paint);
or if you are trying to change the opacity of individual pixels, the approach is a bit more complicated (I have not tested the code, but you get the gist of it):
public static final Bitmap getNewBitmap(Bitmap bmp, int circleCenterX,
int circleCenterY,int circleRadius){
//CIRCLE COORDINATES ARE THE DISTANCE IN RESPECT OF (0,0) of the bitmap
//, not (0,0) of the canvas itself. The circleRadius is the circle's radius.
Bitmap temp=bmp.copy(Bitmap.Config.ARGB_8888, true);
int[]pixels = new int[temp.getWidth()*temp.getHeight()];
temp.getPixels(pixels,0 ,temp.getWidth(),0,0,temp.getWidth(), temp.getHeight());
int counter=0;
for(int i=0;i<pixels.length;i++){
int alpha=Color.alpha(pixels[i]);
if(alpha!=0&&!((Math.pow(counter/temp.getWidth()-circleCenterY,2.0)+
Math.pow(counter%temp.getWidth()-circleCenterX,2.0))<Math.pow(circleRadius,2.0))){
//if the pixel itself is not completely transparent and the pixel is NOT within range of the circle,
//set the Alpha value of the pixel to 0.
pixels[i]=Color.argb(0,Color.red(pixels[i]),Color.green(pixels[i]),Color.blue(pixels[i]));
}
counter++;
}
temp.setPixels(pixels,0, temp.getWidth(),0,0,temp.getWidth(),temp.getHeight());
return temp;
}
and then draw temp.
I'm not completely sure what you are trying to ask, so you may have to modify as necessary.
If you try the second answer of qwertyuiop5040, you will get a ver low - perfomance when you try to apply it to a large image. Let's say a 1000*800 pixels image. Then you will have a loop:
for (int i = 0 ; i < 1000*800; i++)
You could create an image that's a black rectangle with a transparent hole in it. The hole would be the circle that you can see through, and the image would be rendered over the spot you want to be visible. Then, you can draw four black rectangles around the image to cover the rest of the screen.

Why does VolatileImage have no set/getPixel() method

I am a relative newbie in game programming. I know how to draw pixels to a BufferedImage using setPixel(). It is horribly slow on larger formats so I moved on and found VolatileImage (took me a week or so). It is fairly easy to draw lines, strings, rects, etc but I can't draw individual pixels. I already tried using drawLine(x,y,x,y) but I get 3-4 FPS on an 800x600 image.
The fact that java didn't include setPixel() or setRGB() in the VolatileImage makes me pretty angry and confused.
I have 4 questions:
Is there a way to draw individual pixels on a VolatileImage? (on 1440x900 formats with FPS > 40)
Can I draw pixels in a BufferedImage with a faster method? (same 1440x900, FPS > 40)
Is there any other way to draw pixels fast enough for 3D games?
Can I make my BufferedImage hardware accelerated( tried using setAccelerationPriority(1F) but it doesn't work)
Please if you have any idea tell me. I can't continue making my game wihout this information. I already made 3D rendering algorithms but i need to be able to draw fast pixels. I have got a good feeling about this game.
Here's the code if it can help you help me:
public static void drawImageRendered (int x, int y, int w, int h) { // This is just a method to test the performance
int a[] = new int[3]; // The array containing R, G and B value for each pixel
bImg = Launcher.contObj.getGraphicsConfiguration().createCompatibleImage(800, 600); // Creates a compatible image for the JPanel object i am working with (800x600)
bImg.setAccelerationPriority(1F); // I am trying to get this image accelerated
WritableRaster wr = bImg.getRaster(); // The image's writable raster
for (int i = 0; i < bImg.getWidth(); i++) {
for (int j = 0; j < bImg.getHeight(); j++) {
a[0] = i % 256;
a[2] = j % 256;
a[1] = (j * i) % 256;
wr.setPixel(i, j, a); // Sets the pixels (You get a nice pattern)
}
}
g.drawImage(bImg, x, y, w, h, null);
}
I would much prefer not using OpenGL or any other external libraries, just plain Java.
Well you're basically drawing one pixel after the other using the CPU. There's no way that this can be accelerated, thus such a method does simply not make any sense for a VolatileImage. The low FPS you get suggest that this even causes a significant overhead, as each pixel drawing operation is sent to the graphics card (with information such as location & colour), which takes longer than to modify 3 or 4 bytes of RAM.
I suggest to either stop drawing each pixel separately or to figure out a way to make your drawing algorithm run directly on the graphics card (which most likely requires another language than Java).
It's been over 4 years since this post got an answer. I was looking for an answer to this question as well and stumbled on this post. After some more searching, I got it to work. Below I'll post the source to rendering pixels with a VolatileImage.
It seems Java hides our ability to plot pixels directly to a VolatileImage, but we can draw buffered images to it. For good reason. Using the software to plot a pixel doesn't really help with acceleration(in Java it seems). If you can plot pixels to a BufferedImage, and then render it on a VolatileImage, you may get a speed bonus since it's hardware accelerated from that point.
The source down below is a self-contained example. You can copy-pasta practically all of it to your project and run it.
https://github.com/Miekpeeps/JavaSnippets-repo/blob/master/src/graphics_rendering/pixels_03/PlottingVolatile.java
In the constructor I save the Graphics environment of the app/game.
private GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
private GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
Then, when I call a method to enable hardware we create a buffer. I set the transparency to Opaque. In my little engine, I deal with transparency/alpha blending on another thread in the pipeline.
public void setHardwareAcceleration(Boolean hw)
{
useHW = hw;
if (hw)
{
vbuffer = gc.createCompatibleVolatileImage(width, height, Transparency.OPAQUE);
System.setProperty("sun.java2d.opengl", hw.toString()); // may not be needed.
}
}
For each frame I update, I get the Graphics from the VolatileImage and render my buffer there. Nothing gets rendered if I dont flush().
#Override
public void paintComponent(Graphics g)
{
if(useHW)
{
g = vbuffer.getGraphics();
g.drawImage(buffer, 0, 0, null);
vbuffer.flush();
}
else
{
g.drawImage(buffer, 0, 0, null);
buffer.flush();
}
}
There is still a little bit of overhead when calling to plot a pixel on the BufferedImage writable raster. But when we update the screen, we get a speed boost when using the Volatile image instead of using the Buffered image.
Hope this helps some folks out. Cheers.

Java's Graphics.drawImage produces an inaccurate image

I'm trying to draw one image onto another using graphics.drawImage() but it's only working accurately for some images, others it messes up. My code's below, I've made sure texture is the right image when it enters the method so that's definitely not it. Any ideas?
private BufferedImage currentSheet;
public void swapRegionWithTexture(Rectangle region, Image texture) {
Graphics sheetGraphics = currentSheet.createGraphics();
for (int ix = region.x; ix < region.x + region.width; ix++) {
for (int iy = region.y; iy < region.y + region.height; iy++) {
currentSheet.setRGB(ix, iy, 0x000000);
}
}
sheetGraphics.drawImage(texture, region.x, region.y, null);
sheetGraphics.dispose();
}
The general idea is:
Grab the graphics to draw to.
Clear out the section of the graphics that will be drawn on.
Draw the image at the given location on the graphics.
Dispose of the graphics.
Your current atomic steps are thus:
create a new buffered image
write to that buffered image some crazy manipulation of the original texture (looks like you're reversing it?)
write the original image to the current sheet.
So what if you change this
sheetGraphics.drawImage(texture, region.x, region.y, null);
to this
sheetGraphics.drawImage(bufferedTexture, region.x, region.y, null);
Otherwise you're spending time reversing the image and putting into buffered texture and then never doing anything with that buffer... so chances are you intended to use that buffered texture somewhere.

Categories