Is there a better way to do this? BufferedImage related - java

I have a method in my project that runs only if both of the following conditions are true: the user presses the E key, and a pixel in the player's immediate vicinity is red on the unseen bitmap. The bitmap governs collision in my game. Here is the method:
private void doors(int x, int y) {
int doorColor = 0xFFFF0000;
int w = 0;
int h = 0;
while (bitmap.getRGB(x - 1, y) == doorColor)
x--;
while (bitmap.getRGB(x, y - 1) == doorColor)
y--;
while (bitmap.getRGB(x + w + 1, y) == doorColor)
w++;
while (bitmap.getRGB(x, y + h + 1) == doorColor)
h++;
for (int x2 = x; x2 < x + w + 1; x2++) {
for (int y2 = y; y2 < y + h + 1; y2++) {
foreground.setRGB(x2, y2, 0x00FFFFFF);
bitmap.setRGB(x2, y2, 0xFFFFFFFF);
}
}
}
}
This method, when given the x and y coordinates of the found red pixel, finds the top left corner of the red rectangle on the bitmap, and then finds the width and height of said rectangle. Once it has done this, it changes the colors of all the pixels in the rectangle to white, and clears the section of the foreground image to give the appearance of an opened door.
This method of opening the door works, but it takes long enough that the game visibly freezes for a few frames. Is there any way to avoid this?
Here is my bitmap:
And here is my foreground image:

Related

Color data is no being stored properly

I am attempting to make a rendering system with a depth map involved with the usual pixels for dealing with alpha. My problem is that no color is being set correctly! I have tried to debug using System.out.println and testing various components, but to no avail I have not found a solution.
The Variables
The variables that are involved with dealing with drawing, setting, and clearing of pixels are: private int[][] node, private int[] pixels, and private ArrayList<Integer> changedPixels.
private int[][] node deals with storing pixels and dealing with depth [depth][x + y * width] before transferring over to the BufferedImage pixels. The data is set to a clear black and the lowest depth it is a fully visible black.
private int[] pixels is the data from a BufferedImage to change it up, it is the only image every used! All data is by default fully visible black
private ArrayList<Integer> changedPixels deals with pixels that are there from the last frame so as to help boost FPS by not clearing the entire screen if not needed. Empty by default since not pixels were changed from a previous frame.
The Methods
I have several methods for the rendering system: setNode(int x, int y, int z, int color, int alpha, drawScreen(), and clearScreen(). I also have a drawing rectangle and sprite function which deals with adding pixels by calling the setNode() method to add in colors.
private void setNode(int x, int y, int z, int color, float alpha)
{
color = Pixel.getColor(alpha, color);
if (translate) // Move the pixel to the correct location
{
x -= transX;
y -= transY;
}
if (x < 0 || x >= width || y < 0 || y >= height || alpha <= 0.0f || nodeMap[z][x + y * width] == color) // Check if we need to draw the pixel
return;
for (int zz = z + 1; zz < maxDepth; zz++)
if (Pixel.getAlpha(nodeMap[zz][x + y * width]) >= 1)
return;
if (alpha < 1.0f) // If pixel isn't completely opaque, then set it's alpha to the given one
if (nodeMap[z][x + y * width] != color) // If color isn't equal to the one we supply, change it up correctly
color = Pixel.getColorBlend(color, nodeMap[z][x + y * width]);
if (color == Pixel.WHITE) System.out.println("Pixel is white at x: " + x + ", y: " + y);
nodeMap[z][x + y * width] = color;
}
public void drawScreen()
{
int color = clearColor;
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
for (int z = maxDepth - 1; z > 0; z--)
{
if (Pixel.getAlpha(nodeMap[z][x + y * width]) > 0f)
color = Pixel.getColorBlend(color, nodeMap[z][x + y * width]);
if (Pixel.getAlpha(color) >= 1f)
break;
}
if (pixels[x + y * width] != color)
{
pixels[x + y * width] = color;
changedPixels.add(x + y * width);
}
}
}
public void clearScreen()
{
for (Integer pixel : changedPixels)
{
for (int z = 0; z < maxDepth; z++)
{
if (z > 0)
nodeMap[z][pixel] = clearColor;
else
nodeMap[z][pixel] = bgColor;
}
}
changedPixels.clear();
}
public void drawRect(int offX, int offY, int z, int width, int height, int color)
{
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
setNode(x + offX, y + offY, z, color);
}
public static int getColorBlend(int color1, int color2)
{
float a1 = getAlpha(color1);
float a2 = getAlpha(color2);
float a = Math.max(a1, a2);
float r = ((getRed(color1) * a1) + (getRed(color2) * a2 * (1 - a1))) / a;
float g = ((getGreen(color1) * a1) + (getGreen(color2) * a2 * (1 - a1))) / a;
float b = ((getBlue(color1) * a1) + (getBlue(color2) * a2 * (1 - a1))) / a;
return Pixel.getColor(a, r, g, b);
}
The Test
What I do currently is initalize the rendering system and set the nodeMap and pixel map to the previously mentioned settings. After this has been completed a game engine begins and then a method in a gui button (you might need it), but it calls drawRect(0(x), 0(y), 1(z), 100(width), 20(height), Pixel.WHITE(color)) which works as I have testing to see if it's running the method and which pixels it's drawing to.
The Problem
The overall problem is that the screen is completely white, I can't quite figure out the reason! I do know it has nothing with the alpha blending, which works fine as I have used it will a previous version of a rendering system I did.
Any help is appreciate and sorry that this is quite a long question, I just wanted to make sure you had everything you may need help me solve this. I do realize this is not be very effiecent, but I still like the system. Thanks again!

Java Graphics - Drawing Lines

I am having some problems drawing lines in my Java program.
I wish to play with some Maze Generation Algorithms but am currently struggling to set up a board to visualize the process and creation of the mazes. Below is my current Setup:
I generate a grid (2D Array) 50 x 25, each block is 20 pixels wide and 20 pixels tall, now for each grid I want to draw each of the four walls as lines.
I get the starting x and y coordinate of each block by multiplying the for loop values (used to traverse the array) by the block size. For Example if I am currently looking at grid[2][3] then the pixel coordinates will be (40, 60). My problem is that the South wall and the East Wall are not being drawn, nothing is showing in my program even though the coordinates are being generated, my North Wall and West Wall are however being drawn.
I am trying to get the walls Coordinates by adjusting the positions as follows, if I want to draw the south wall and the starting x and y coordinates of the block are (40, 60). Then the 2 points that should be used to draw the South Wall should be, (40, 80) and (60, 80).
Below is the Render Method that I am currently using:
private void renderBoard(Graphics g) {
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
Node node = grid[i][j];
// Draw the Node's Background
g.setColor(Color.BLACK);
g.fillRect(i * blockSize, j * blockSize, blockSize, blockSize);
// Draw the Node's North Wall
if (node.walls.contains(Walls.NORTH)) {
Line line = new Line(i, j, Walls.NORTH, blockSize);
// System.out.println("Point1 (X1: " + line.x1 + " Y1: " + line.y1 + ") Point2 (X2: " + line.x2 + " Y2: " + line.y2 + ")");
g.setColor(Color.WHITE);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
// Draw the Node's South Wall
if (node.walls.contains(Walls.SOUTH)) {
Line line = new Line(i, j, Walls.SOUTH, blockSize);
g.setColor(Color.WHITE);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
}
}
}
Again the North wall is drawn while nothing is drawn for the South Wall. I initially thought it might be because I was passing in the two for loop variables in the wrong order. But that didn't seem to change anything
This is my Line Class:
class Line {
// Current X Y Coordinates in the Grid
private int x;
private int y;
// The Lines Start and End Coordinates
public int x1, x2;
public int y1, y2;
public Line(int x, int y, Walls wall, int blocksize) {
// Here we get the pixel coordinate based off of the current Array Position
this.x = (x * blocksize);
this.y = (y * blocksize);
switch(wall) {
case NORTH:
x1 = this.x;
y1 = this.y;
x2 = this.x + blocksize;
y2 = this.y;
break;
case SOUTH:
x1 = this.x;
y1 = this.y + blocksize;
x2 = this.x + blocksize;
y2 = this.y + blocksize;
break;
case EAST:
x1 = this.x + blocksize;
y1 = this.y;
x2 = this.x + blocksize;
y2 = this.y + blocksize;
break;
case WEST:
x1 = this.x;
y1 = this.y;
x2 = this.x;
y2 = this.y + blocksize;
break;
}
}
}
I have a sneaking suspicion that I am doing something really really silly or missing something very small, any help would be gladly appreciated

Get average color on bufferedimage and bufferedimage portion as fast as possible

I am trying to find image in an image. I do this for desktop automation. At this moment, I'm trying to be fast, not precise. As such, I have decided to match similar image solely based on the same average color.
If I pick several icons on my desktop, for example:
And I will search for the last one (I'm still wondering what this file is):
You can clearly see what is most likely to be the match:
In different situations, this may not work. However when image size is given, it should be pretty reliable and lightning fast.
I can get a screenshot as BufferedImage object:
MSWindow window = MSWindow.windowFromName("Firefox", false);
BufferedImage img = window.screenshot();
//Or, if I can estimate smaller region for searching:
BufferedImage img2 = window.screenshotCrop(20,20,50,50);
Of course, the image to search image will be loaded from template saved in a file:
BufferedImage img = ImageIO.read(...whatever goes in there, I'm still confused...);
I explained what all I know so that we can focus on the only problem:
Q: How can I get average color on buffered image? How can I get such average color on sub-rectangle of that image?
Speed wins here. In this exceptional case, I consider it more valuable than code readability.
I think that no matter what you do, you are going to have an O(wh) operation, where w is your width and h is your height.
Therefore, I'm going to post this (naive) solution to fulfil the first part of your question as I do not believe there is a faster solution.
/*
* Where bi is your image, (x0,y0) is your upper left coordinate, and (w,h)
* are your width and height respectively
*/
public static Color averageColor(BufferedImage bi, int x0, int y0, int w,
int h) {
int x1 = x0 + w;
int y1 = y0 + h;
long sumr = 0, sumg = 0, sumb = 0;
for (int x = x0; x < x1; x++) {
for (int y = y0; y < y1; y++) {
Color pixel = new Color(bi.getRGB(x, y));
sumr += pixel.getRed();
sumg += pixel.getGreen();
sumb += pixel.getBlue();
}
}
int num = w * h;
return new Color(sumr / num, sumg / num, sumb / num);
}
There is a constant time method for finding the mean colour of a rectangular section of an image but it requires a linear preprocess. This should be fine in your case. This method can also be used to find the mean value of a rectangular prism in a 3d array or any higher dimensional analog of the problem. I will be using a gray scale example but this can be easily extended to 3 or more channels simply by repeating the process.
Lets say we have a 2 dimensional array of numbers we will call "img".
The first step is to generate a new array of the same dimensions where each element contains the sum of all values in the original image that lie within the rectangle that bounds that element and the top left element of the image.
You can use the following method to construct such an image in linear time:
int width = 1920;
int height = 1080;
//source data
int[] img = GrayScaleScreenCapture();
int[] helperImg = int[width * height]
for(int y = 0; y < height; ++y)
{
for(int x = 0; x < width; ++x)
{
int total = img[y * width + x];
if(x > 0)
{
//Add value from the pixel to the left in helperImg
total += helperImg[y * width + (x - 1)];
}
if(y > 0)
{
//Add value from the pixel above in helperImg
total += helperImg[(y - 1) * width + x];
}
if(x > 0 && y > 0)
{
//Subtract value from the pixel above and to the left in helperImg
total -= helperImg[(y - 1) * width + (x - 1)];
}
helperImg[y * width + x] = total;
}
}
Now we can use helperImg to find the total of all values within a given rectangle of img in constant time:
//Some Rectangle with corners (x0, y0), (x1, y0) , (x0, y1), (x1, y1)
int x0 = 50;
int x1 = 150;
int y0 = 25;
int y1 = 200;
int totalOfRect = helperImg[y1 * width + x1];
if(x0 > 0)
{
totalOfRect -= helperImg[y1 * width + (x0 - 1)];
}
if(y0 > 0)
{
totalOfRect -= helperImg[(y0 - 1) * width + x1];
}
if(x0 > 0 && y0 > 0)
{
totalOfRect += helperImg[(y0 - 1) * width + (x0 - 1)];
}
Finally, we simply divide totalOfRect by the area of the rectangle to get the mean value:
int rWidth = x1 - x0 + 1;
int rheight = y1 - y0 + 1;
int meanOfRect = totalOfRect / (rWidth * rHeight);
Here's a version based on k_g's answer for a full BufferedImage with adjustable sample precision (step).
public static Color getAverageColor(BufferedImage bi) {
int step = 5;
int sampled = 0;
long sumr = 0, sumg = 0, sumb = 0;
for (int x = 0; x < bi.getWidth(); x++) {
for (int y = 0; y < bi.getHeight(); y++) {
if (x % step == 0 && y % step == 0) {
Color pixel = new Color(bi.getRGB(x, y));
sumr += pixel.getRed();
sumg += pixel.getGreen();
sumb += pixel.getBlue();
sampled++;
}
}
}
int dim = bi.getWidth()*bi.getHeight();
// Log.info("step=" + step + " sampled " + sampled + " out of " + dim + " pixels (" + String.format("%.1f", (float)(100*sampled/dim)) + " %)");
return new Color(Math.round(sumr / sampled), Math.round(sumg / sampled), Math.round(sumb / sampled));
}

Rotating a BufferedImage and Saving it into a pixel array

I am trying to properly rotate a sword in my 2D game. I have a sword image file, and I wish to rotate the image at the player's location. I tried using Graphics2D and AffineTransform, but the problem is that the player moves on a different coordinate plane, the Screen class, and the Graphics uses the literal location of the pixels on the JFrame. So, I realized that I need to render the sword by rotating the image itself, and then saving it into a pixel array for my screen class to render. However, I don't know how to do this. Here is the code for my screen rendering method:
public void render(double d, double yOffset2, BufferedImage image, int colour,
int mirrorDir, double scale, SpriteSheet sheet) {
d -= xOffset;
yOffset2 -= yOffset;
boolean mirrorX = (mirrorDir & BIT_MIRROR_X) > 0;
boolean mirrorY = (mirrorDir & BIT_MIRROR_Y) > 0;
double scaleMap = scale - 1;
for (int y = 0; y < image.getHeight(); y++) {
int ySheet = y;
if (mirrorY)
ySheet = image.getHeight() - 1 - y;
int yPixel = (int) (y + yOffset2 + (y * scaleMap) - ((scaleMap * 8) / 2));
for (int x = 0; x < image.getWidth(); x++) {
int xPixel = (int) (x + d + (x * scaleMap) - ((scaleMap * 8) / 2));
int xSheet = x;
if (mirrorX)
xSheet = image.getWidth() - 1 - x;
int col = (colour >> (sheet.pixels[xSheet + ySheet
* sheet.width])) & 255;
if (col < 255) {
for (int yScale = 0; yScale < scale; yScale++) {
if (yPixel + yScale < 0 || yPixel + yScale >= height)
continue;
for (int xScale = 0; xScale < scale; xScale++) {
if (x + d < 0 || x + d >= width)
continue;
pixels[(xPixel + xScale) + (yPixel + yScale)
* width] = col;
}
}
}
}
}
}
Here is one of my poor attempts to call the render method from the Sword Class:
public void render(Screen screen) {
AffineTransform at = new AffineTransform();
at.rotate(1, image.getWidth() / 2, image.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(at,
AffineTransformOp.TYPE_BILINEAR);
image = op.filter(image, null);
screen.render(this.x, this.y, image, SwordColor, 1, 1.5, sheet);
hitBox.setLocation((int) this.x, (int) this.y);
for (Entity entity : level.getEntities()) {
if (entity instanceof Mob) {
if (hitBox.intersects(((Mob) entity).hitBox)) {
// ((Mob) entity).health--;
}
}
}
}
Thank you for any help you can provide, and please feel free to tell me if theres a better way to do this.
You can rotate() the image around an anchor point, also seen here in a Graphics2D context. The method concatenates translate(), rotate() and translate() operations, also seen here as explicit transformations.
Addendum: It rotates the image, but how do I save the pixels of the image as an array?
Once you filter() the image, use one of the ImageIO.write() methods to save the resulting RenderedImage, for example.

Java: Rectangle2D and negative dimensions

I have a script in which the user clicks once to begin a Rectangle2D. When he moves the mouse, the rectangle is updated with the new coordinates. He clicks again to end it. It's then stored in an ArrayList, and all these items are painted. This all works fine; it's not the problem.
However, if the second click is less than the first (i.e. getWidth() is negative), the rectangle doesn't show up (as stated in the documentation). My script to fix this doesn't work. It's supposed to detect negative values, and then 1) decrement the position value and 2) make the negatives positive. Instead, it just moves the entire rectangle up or left (depending on which axis is negative) and keeps it at 1px.
What's wrong?
private void resizeRectangle(final MouseEvent e) {
double x = rectangle.getX(), y = rectangle.getY(), w = e.getX() - x, h = e.getY() - y;
if (w < 0) {
x = e.getX();
w = -w;
}
if (h < 0) {
y = e.getY();
h = -h;
}
rectangle.setRect(x, y, w, h);
}
Thanks!
UPDATE: This is closer, but still doesn't quite work:
double x = rectangle.getX();
double y = rectangle.getY();
double w = e.getX() - x;
double h = e.getY() - y;
if (w < 0) {
x = e.getX();
w = originalClickPoint.getX() - e.getX();
}
if (h < 0) {
y = e.getY();
h = originalClickPoint.getY() - e.getY();
}
rectangle.setRect(x, y, w, h);
Once you assign the new rectangle you also shift the origin (i.e.where the mouse button was pressed down initially) to the current point. You'll have to save this point in a separate field (not in the rectangle itself).
x = Math.min(e.getX(), originalClickPoint.getX());
w = Math.abs(originalClickPoint.getX() - e.getX());
y = Math.min(e.getY(), originalClickPoint.getY());
h = Math.abs(originalClickPoint.getY() - e.getY());
Another way is to not correct the negative widths/heights of the rectangle but create a new (corrected) one when you draw it.
Take a look at the Custom Painting Approaches. The source code for the DrawOnComponent example shows how I did this.

Categories

Resources