Let me describe to you the context of my problem before I outline the issue. I'm currently in the middle of writing a game engine level editor and I'm working on the class that is going to act as the screen that the user interacts with in order to build their levels. I want to make it so the screen is proportional to the size of the editor.
The issue in question occurs when I begin resizing my screen and drawing on it at the same time. I draw from one thread and at the same time I'm editing the size of the raw pixel array that I'm drawing onto, from another thread (the EDT). I know this is a big no-no so naturally, with no safety in place, I get the occasional IndexOutOfBounds Exception on a resize.
My thought was, I could add a synchronize block on both the resizing code and the drawing code. This way, there would be no conflict and the issue should be avoided. However, the synchronization is being ignored completely. I'm still getting the same error and I'm really quite confused on why it isn't working. Below are the two methods of interest:
public void setPixel(int r, int g, int b, int x, int y) {
synchronized (pixels){
System.out.println("Start Draw...");
int color = (r << 16) | (g << 8) | b;
pixels[y * screenWidth + x] = color;
System.out.println("End Draw...");
}
}
#Override
public void componentResized(ComponentEvent e) {
synchronized (pixels) {
System.out.println("Start resize");
int width = e.getComponent().getWidth();
int height = e.getComponent().getHeight();
float aspectRatio = 4 / 3f;
if (width > height) {
width = (int) (height * aspectRatio);
} else if (height > width) {
height = width;
}
if (width < 0 || height < 0) {
width = 1;
height = 1;
}
this.screenWidth = width;
this.screenHeight = height;
image = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
System.out.println("End Resize");
}
}
I don't know if it matters (it shouldn't right?) but my screen class extends a AWT Canvas. Also it is a listener on its parent component, so when that gets resized, it fires an event that triggers componentResized to be called. Anyway, thank you, any help is appreciated.
EDIT: My drawing code can be found below.
new Thread(new Runnable(){
#Override
public void run() {
while(true) {
for (int y = 0; y < screen.getHeight(); y++) {
for(int x = 0; x < screen.getWidth(); x++){
int r = (int) (Math.random() * 255);
int g = (int) (Math.random() * 255);
int b = (int) (Math.random() * 255);
screen.setPixel(r, g, b, x, y);
}
}
}
}
}).start();
There is something else I can think of. Not only what is in the comments of the question.
Let me modify first the setPixel method:
public void setPixel(int r, int g, int b, int x, int y) {
System.out.println("Called with: " + x + ", " + y); //Just added a print statement.
synchronized (mySyncGuard){
System.out.println("Start Draw...");
int color = (r << 16) | (g << 8) | b;
pixels[y * screenWidth + x] = color;
System.out.println("End Draw...");
}
}
Where mySyncGuard is a final property used as the synchronization guard to both setPixel and componentResized.
And now imagine the following scenario:
There is a loop which calls the setPixel method: this loop calls the method starting from x = 0 and y = 0 up to x < screenWidth and y < screenHeight! Like the following one:
for (int x = 0; x < screenWidth; ++x)
for (int y = 0; y < screenHeight; ++y) {
int r = calculateNextR(),
g = calculateNextG(),
b = calculateNextB();
setPixel(r, g, b, x, y);
}
Where calculateNextR(), calculateNextG() and calculateNextB() are methods that produce the next red, green and blue color components respectively.
Now, for example, let screenWidth be 200 and screenHeight be also 200 and at some point resized to 100x100.
Now the problem is that x and y would be lets say 150 and 150 respectively while the component was about to be resized to 100, 100.
Our custom setPixel method is now called with x==150 and y==150. But before the printing statement we added which shows the x and the y values, the componentResized manages to be called and take the lock of the synchronization guard property mySyncGuard! So componentResized is now doing its job changing the size of the image to 100x100, which, in turn, changes the pixels array to something smaller than the previous data array.
In parallel, setPixel now prints "Called with 150, 150" and waits at the synchronized block to obtain the lock of mySyncGuard, because componentResized has already currently obtained it and changing the image to 100x100 and the pixels array accordingly.
So now the pixels array is smaller, the componentResized finishes resizing and finally setPixel can obtain the lock of mySyncGuard.
And now is the problem: the array pixels is traversed at location 150,150 while it actually has size 100x100. So there you go! IndexOutOfBoundsException...
Conclusion:
We need more of your code to determine what's wrong.
The variables changing (such as screenWidth, screenHeight, image and pixels) need to be synchronized everywhere, not only inside the setPixel and componentResized methods. For example, if your case is like the scenario I just described, then unfortunately the for loops should also be in a synchronized (mySyncGuard) block.
This wouldn't fit in a comment. If you post some more code, then we can probably tell you what went wrong (and I may delete this answer if it is not needed).
Related
This is a follow-up post to my previous question, here. I got a remarkable response to instead of using array data tracking, to use matrixes. Now, the code here works just as planned (as in, the rectangles somewhat most of the time get filled in properly with white), but it's very inconsistent. When holding the left or right mouse button the colors phase over each other in a battle of randomness, and I don't know nearly that much about why this is happening. Just for reference, I'm using Java in Processing 3.
This is a result that I made with the project. As you can see, it looks fine.
Except for that jitter when hovering over a rect, and that more than not the rectangles are not being filled in half the time. And plus, the hover color is cycling almost randomly.
int cols, rows;
int scl = 20;
boolean[][] matrix = new boolean[scl+1][scl+1];
void setup() {
size(400, 400);
int w = 400;
int h = 400;
cols = w / scl;
rows = h / scl;
}
void draw() {
background(255);
for (int x = 0; x < cols; x++) {
for (int y = 0; y < rows; y++) {
int xpos = x*scl;
int ypos = y*scl;
stroke(55);
if ((mouseX >= xpos && mouseX <= xpos+scl) &&
(mouseY >= ypos && mouseY <= ypos+scl)) {
fill(75);
if (mousePressed == true) {
println("Clicked at: " + xpos + " and " + ypos);
if (!matrix[xpos/scl][ypos/scl]) {
matrix[xpos/scl][ypos/scl] = true;
} else {
matrix[xpos/scl][ypos/scl] = false;
}
fill(100);
//here is the desired location for the fill to remain constant even
//after unclicking and leaving hover
}
println("Mouse at: " + xpos + " and " + ypos);
} else {
fill(50);
}
if (matrix[x][y]) {
//fill(204, 102, 0);
fill(240);
rect(xpos, ypos, scl, scl);
}
rect(xpos, ypos, scl, scl);
}
}
}
Remeber that Processing fires the draw() function 60 times per second.
So your check for whether the mouse is pressed is happening 60 times per second. That means you're toggling the state of whatever cell the mouse is in 60 times per second.
To fix that problem, you might switch to using the event functions like mousePressed() instead of constantly polling every frame.
From the reference:
int value = 0;
void draw() {
fill(value);
rect(25, 25, 50, 50);
}
void mousePressed() {
if (value == 0) {
value = 255;
} else {
value = 0;
}
}
As for certain cells being skipped over, that's because when you move the mouse, it doesn't actually go through every pixel. It "jumps" from frame to frame. Those jumps are usually small enough that humans don't notice it, but they're large enough that it's skipping over cells.
One solution to this is to use the pmouseX and pmouseY variables to calculate a line from the previous mouse position to the current mouse position, and fill in any cells that would have been hit along the way.
I am experimenting with game development in java. As of now, I can use the screen class to render to the Canvas of an image through pixel array. However, whenever I attempt to render to the canvas through the pixel array within the other class, it reads the pixels array's content. x y numbers and all but, does not render it to the canvas at all even though the other class extends the screen class (thus inheriting the pixels array and the x, y numbers).
What is causing this? How do I go about fixing it?
from the other class:
void origin(){
if(x <= ax && y <= ay)
pixels[ox + oy * width] = 0xff00ff;
}
From the screen class:
void waves(){
orga = new Organism(width, height);
for(y = 0; y < height; y++){
for(x = 0; x < width; x++){
Color wacol = new Color(0, 0, u);
int water = wacol.getRGB();
pixels[x + y * width] = water;
orga.origin();
}
}
}
Extending the class isn't enough, it has to be hooked up to the screen in the same way as the pixel array that works.
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
Well i have been watching a couple of videos of youtube on how take sprites from a spritesheet (8x8) and i really liked the tutorial by DesignsByZepher. However the method he uses results in him importing a sorite sheet and then changing the colors to in-code selected colours.
http://www.youtube.com/watch?v=6FMgQNDNMJc displaying the sheet
http://www.youtube.com/watch?v=7eotyB7oNHE for the color rendering
The code that i have made from watching his video is:
package exikle.learn.game.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class SpriteSheet {
public String path;
public int width;
public int height;
public int[] pixels;
public SpriteSheet(String path) {
BufferedImage image = null;
try {
image = ImageIO.read(SpriteSheet.class.getResourceAsStream(path));
} catch (IOException e) {
e.printStackTrace();
}
if (image == null) { return; }
this.path = path;
this.width = image.getWidth();
this.height = image.getHeight();
pixels = image.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (pixels[i] & 0xff) / 64;
}
}
}
^This is the code where an image gets imported
package exikle.learn.game.gfx;
public class Colours {
public static int get(int colour1, int colour2, int colour3, int colour4) {
return (get(colour4) << 24) + (get(colour3) << 16)
+ (get(colour2) << 8) + get(colour1);
}
private static int get(int colour) {
if (colour < 0)
return 255;
int r = colour / 100 % 10;
int g = colour / 10 % 10;
int b = colour % 10;
return r * 36 + g * 6 + b;
}
}
^ and the code which i think deals with all the colors but im kinda confused about this.
My question is how do i remove the color modifier and just import and display the sprite sheet as is, so with the color it already has?
So you're fiddling with the Minicraft source, I see. The thing about Notch's code is that he substantially limited himself technically in this game. What the engine is doing is basically saying every sprite/tile can have 4 colors (from the grey-scaled spritesheet), he generates his own color palette that he retrieves colors from and sets accordingly during rendering. I can't remember exactly how many bits per channel he set and such.
However, you obviously are very new to programming and imo there's nothing better than fiddling with and analyzing other people's code.. that is, if you actually can do so. The Screen class is where the rendering takes place and hence it's what uses the spritesheet and therefore gives color accordingly to whatever tile you tell it to get. Markus is quite clever, despite poorly written code (which is completely forgiven as he did have 48 hours to make the damned thing ;))
if you want to just display the spritesheet as is, you can either rewrite the render function or overload it to something like this... (in class Screen)
public void render() {
for(int y = 0; y < h; y++) {
if(y >= sheet.h) continue; //prevent going out of bounds on y-axis
for(int x = 0; x < w; x++) {
if(x >= sheet.w) continue; //prevent going out of bounds on x-axis
pixels[x + y * w] = sheet.pixels[x + y * sheet.w];
}
}
}
This will just put whatever of the sheet it can fit into the screen for rendering (it's a really simple piece of code, but should work), the next step will be copying the pixels over to the actual raster for display, which I'm sure you can handle. (If you have copy-pasted all of the minicraft source code or some other slightly modified source code, you might want to change some things about that as well.)
All the cheers!
This basics would be to replace the get(int) method...
private static int get(int colour) {
//if (colour < 0)
// return 255;
//int r = colour / 100 % 10;
//int g = colour / 10 % 10;
//int b = colour % 10;
//return r * 36 + g * 6 + b;
return colour;
}
I'd also get rid of
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (pixels[i] & 0xff) / 64;
}
From the main method
But to be honest, wouldn't it be easier to simply use BufferedImage#getSubImage?
it's my first time asking a question here so I'll try to stay on-topic. I'm trying to randomly generate a background by creating an appropriately-sized ArrayList of Bitmap objects, and drawing them in order. This implementation works fine loading a single Bitmap, by the way; it's just stumbling with a list.
Before I get to the code, I'd like to point out that Ideally I would make a single Bitmap by adding the individual pixels or tiles, and indeed have tried a few variations of that, but they all result in black screens; I'm starting to think it might be a problem with how I draw to the Canvas. Anyways, here's what I have:
First, I generate the random ArrayList, only using 3 colors right now. I'd make it return the list, but it's just a private method inside the thread referencing one of the thread's variables so it doesn't matter much.
private void genMap(Resources res)
{
// Load basic tiles.
Bitmap green = BitmapFactory.decodeResource(res, R.drawable.green);
Bitmap red = BitmapFactory.decodeResource(res, R.drawable.red);
Bitmap blue = BitmapFactory.decodeResource(res, R.drawable.blue);
// All tiles must be the same size.
int tile_width = green.getWidth();
int tile_height = green.getHeight();
int num_x = mCanvasWidth / tile_width;
int num_y = mCanvasHeight / tile_height;
for (int j = 0; j < num_y; j++)
{
for (int i = 0; i < num_x; i++)
{
double r = Math.random();
Bitmap tile;
if (r <= 1/3) {tile = green;}
else if (r <= 2/3) {tile = red;}
else {tile = blue;}
// Create a new Bitmap in order to avoid referencing the old value.
mBackgroundImages.add(Bitmap.createBitmap(tile));
}
}
}
So, that's how the random values are mapped to a pattern. The method is called in the thread's constructor, which is in turn called every time onCreate is called; for now, I'm just clearing the list and making a new random pattern each time:
...
Resources res = context.getResources();
mBackgroundImages = new ArrayList<Bitmap>();
genMap(res);
...
And finally, the draw method; it works fine loading a single Bitmap via BitmapFactory.decodeResources, but shows a black screen when doing this:
private void doDraw(Canvas canvas)
{
/* Draw the bg.
* Remember, Canvas objects accumulate.
* So drawn first = most in the background. */
if (canvas != null)
{
if (mBackgroundImages.size() > 0)
{
int tile_width = mBackgroundImages.get(0).getWidth();
int tile_height = mBackgroundImages.get(0).getHeight();
for (int y = 0; y < mCanvasHeight / tile_height; y++)
{
for(int x = 0; x < mCanvasWidth / tile_width; x++)
{
// Draw the Bitmap at the correct position in the list; Y * XWIDTH + X, at pos X * XWIDTH, Y * YWIDTH.
canvas.drawBitmap(mBackgroundImages.get((x + y * (mCanvasWidth/tile_width))), x * tile_width, y * tile_height, null);
}
}
}
}
}
Any help would be appreciated, thanks.
Have you double-checked the order of precedence of the following line?
((x + y * (mCanvasWidth/tile_width)))
Interesting question though: are you drawing solid colors? I would also see if this is actually making a Bitmap:
mBackgroundImages.add(Bitmap.createBitmap(tile));
You might actually be able to just keep references instead of creating a lot of bitmaps, but that can come later