How to stitch many small images into one large image? - java

I am making a game. It is a 2D game, and the map is drawn by a double loop like this:
for(int i = 0; i < mapArray.length; i++){
for(int j = 0; j < mapArray[1].length; j++){
//if statement for if its on the screen
g.drawImage(tiles.get(mapArray[i][j]).getImage(), j * textureSize, i * textureSize, null);
}
}
They are 32x32 images, and I was wondering stitching them all together to create one large image at the beginning would be more efficient in drawing. The resulting image would only be around 1500x1500.
I was thinking of making it stitched into one image (Especially since I am planning on making the images smaller which would make the loop need ever more time.) so that it didn't have to run through the double for loop every time it renders (shooting for 60 FPS). But I don't know how to do this, and would it actually improve the performance if I did?
Also, I could just stitch them into rows, and only render the rows that are on the screen(to remove the large image problem) So it would still be much less intensive than that crazy loop I've got right now.
Edit: And one last thing, if you can provide an example of how to do this without extra libraries that would be optimal.
I currently have this code for stitching:
Edit: now works. Leaving this here for future readers:
public void stitchImages(){
BufferedImage temp = new BufferedImage( <Width> , <height> , BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) temp.getGraphics();
for (int b = 0; b < mapArray.length; b++) {
for (int a = 0; a < mapArray[b].length; a++) {
g.drawImage(tiles.get(mapArray[b][a]).getImage(),
(int) mapX + a * textureSize, (int) mapY + b
* textureSize, null);
}
}
mapImage = temp;
}

Create a new Image to encapsulate all of your images. Draw your images when you load up, then just draw that in paintComponent()
BufferedImage im = new BufferedImage(1500,1500,BufferedImage.TYPE_INT_RGB);
private void init() {
Graphics g = im.getGraphics();
for(int i = 0; i < mapArray.length; i++){
for(int j = 0; j < mapArray[1].length; j++){
//if statement for if its on the screen
g.drawImage(tiles.get(mapArray[i][j]).getImage(), j * textureSize, i * textureSize, null);
}
}
}
public void paintCompoent(Graphics g) {
super.paintComponent(g);
g.drawImage(im,0,0,null);
}
EDIT:
As for your idea about just painting the lines that are on the screen, you can do that by creating an Image the size of the window and just drawing to that. But in general, it's not a big problem to paint a big Image (as long as the Image fits in memory and you don't get an OutOfMemoryException) as your GPU's capabilities smoke your CPU's

Related

Image array rotation

I have an array representing the pixels of a grayscale image. I need an algorithm that can produce arrays of the same size that represent this image rotated at any angle, preferably without cropping any part of the image.
I've done some research and found various methods to flip images as well as some stuff on rotational matrices but I still don't have a good algorithm to do this.
Pretty simple for loop once you figure it out. If you are using a image processing library do tell because those solutions can be a lot faster.
//this function assumes a rectangular image,
// and rotates it 90 degrees right
public static Color[][] rotate(Color[][] image) {
Color[][] newImage = new Color[image[0].length][image.length];
for(int i = 0; i < image[0].length; i++) {
for(int j = 0; j < image.length; j++) {
newImage[i][j] = image[image.length-j-1][i];
}
}
return newImage;
}

JavaFX SVGPath very slow with a large content

I am working with a large array of dots (4096x256) to be displayed as an image in a JavaFX application. I started with a BMP image built from the 2D array of dots, but the zooming operation produced a blurred image on screen. So, I decided to use SVGPath to draw my image which now zooms in properly (no blur) with a simple SVGPath content, but becomes veeeeeery slow when it comes to displaying a checkerboard of two colors, probably because the SVGPath content is so large for fail bits information, as shown in my code.
Is there an efficient way to achieve this? By efficient, I mean fast execution time while keeping the pixel-sharp zooming effect.
public Group createImage(final int width, final int height, final int boxIdx)
{
// assuming that pass data bits are more numerous than others, we can
// start by drawing full green colored lines
StringBuilder svgPass = new StringBuilder();
for (int y = 0; y < height; y++)
{
svgPass.append("M0,");
svgPass.append(Integer.toString(y));
svgPass.append(" ");
svgPass.append("L0,");
svgPass.append(Integer.toString(y));
svgPass.append(" ");
svgPass.append(Integer.toString(width - 1));
svgPass.append(",");
svgPass.append(Integer.toString(y));
svgPass.append(" z");
}
SVGPath imgPass = new SVGPath();
imgPass.setContent(svgPass.toString());
imgPass.setStrokeLineJoin(StrokeLineJoin.BEVEL);
imgPass.setStroke(Color.GREEN);
imgPass.setStrokeWidth(1);
// fail data bits
StringBuilder svgFail = new StringBuilder();
Bit[][] bits = WordLineManager.getInstance().getBlocksMap().get(boxIdx);
for (int y = 0; y < bits.length; y++)
{
for (int x = 0; x < bits[0].length; x++)
{
if (bits[y][x].getNature() == BitNature.DATA && !bits[y][x].getStatus())
{
svgFail.append("M");
svgFail.append(Integer.toString(x));
svgFail.append(",");
svgFail.append(Integer.toString(y));
svgFail.append(" ");
svgFail.append("L");
svgFail.append(Integer.toString(x));
svgFail.append(",");
svgFail.append(Integer.toString(y));
svgFail.append(" ");
svgFail.append(Integer.toString(x + 1));
svgFail.append(",");
svgFail.append(Integer.toString(y));
svgFail.append(" z");
}
}
}
SVGPath imgFail = new SVGPath();
imgFail.setContent(svgFail.toString());
imgFail.setStrokeLineJoin(StrokeLineJoin.BEVEL);
imgFail.setStroke(Color.RED);
imgFail.setStrokeWidth(1);
return new Group(imgPass, imgFail);
}

Recalculate X and Y Coordinates From New Screensize

I'm trying to display tiles from an array I have so that they always fill the size of the screen when drawn together. I am ignoring aspect ratio for now.
Here's how my code works. I have tile objects that are passed on to a tileset (class for managing an array of tile objects), and then I iterate through the tileset array, returning each tile object id and rendering a subimage of my tileset image based on said ids.
Here's my mapUpdate method, which is called on every JFrame resize event:
public synchronized void mapUpdate(Screen screen) {
factorX = (float)(screen.getWidth() / scW);
factorY = (float)(screen.getHeight() / scH);
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
int x = tileset.getTile(i, j).getX();
int y = tileset.getTile(i, j).getY();
tileset.getTile(i, j).setX((int)(x * factorX));
tileset.getTile(i, j).setY((int)(y * factorY));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}
mapTiles is an ArrayList of Images, and on each resize event it resets the arraylist, scales my subimages to 1/10th width and height, and then re-adds the newly sized images for me to pull out for rendering (the tileset image is only 3 tiles with an original size of 70x70).
And here is the componentResized method if you were curious:
public void componentResized(ComponentEvent e) {
canvas.setSize(app.getContentPane().getWidth(), app.getContentPane().getHeight());
if (level1 != null) {
level1.mapUpdate(this);
}
}
As you can see in my mapUpdate method, I attempt to get a float to multiply each current x and y value by to receive the new correct values (it will round the integers), but this doesn't work at all.
Is there any solution to easily re-calculate my X and Y coordinates so that the tiles are drawn correctly?
Your factorX should not be a float. It should be an int. If you use a float you will get rounding, so occasionally you will have a pixel gap between tiles because of rounding. If you just use an int then you don't have to worry about this. Then the location is just the factor * the index value of the for loop.
On the other hand the easiest solution is to just use a JPanel with a GridLayout. Then you can add a JLabel with an Image icon. The GridLayout will resize each component equally.
You can then even use the Stretch Icon and the images will be dynamically resized as the frame is resized.
SOLUTION courtesy of #MadProgrammer - The solution was to simply calculate the new X and Y "origins" for each tile. I still need to implement adjusting the position for tiles that have moved based on the newly calculated origins, but this solution works for tiles that do not move (again, simply recalculate the X and Y origin positions).
public synchronized void mapUpdate(Screen screen) {
int originX = screen.getWidth() / 10;
int originY = screen.getHeight() / 10;
for (int i = 0; i < tileset.getRows(); i++) {
for (int j = 0; j < tileset.getCols(); j++) {
tileset.getTile(i, j).setX((originX * j));
tileset.getTile(i, j).setY((originY * i));
}
}
mapTiles.clear();
for (int i = 0; i * 70 < mapImage.getWidth(); i++) {
mapTiles.add(mapImage.getSubimage(70 * i, 0, 70, 70).getScaledInstance(screen.getWidth() / 10, screen.getHeight() / 10, Image.SCALE_SMOOTH));
}
}

JPanel to BufferedImage

I am trying to convert the contents of a JPanel into a BufferedImage. After looking around I have got this code.
BufferedImage image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
this.paint(g);
I iterate through the image looking for pixels that are colored black using the following.
for(int i = 0; i < image.getWidth(); i++){
for(int j = 0; j < image.getHeight(); j++){
Color tempColor = new Color(image.getRGB(i, j));
if(tempColor == Color.BLACK){
System.out.println(tempColor); //Debugging
}
}
}
The JPanel contains many pixels that were painted using Color.BLACK (so yes they are black), although when running this code, it never prints the debugging line.
I believe the error in my code has to do with the way I am copying the contents of the JPanel into the BufferedImage, I can't seem to figure out what I am doing wrong. Any help is greatly appreciated, thanks.
You are performing a reference equality test when testing tempColor == Color.BLACK. But new Color(…) always creates a new object which can never be the same object as the predefined Color.BLACK instance, thus the == check will always be false.
Use equals or simply omit dealing with Color objects at all and just check whether image.getRGB(i, j) == 0 or if you don't want to use zero for black you can also use image.getRGB(i, j) == Color.BLACK.getRGB()
Thanks to #Holger for this answer.
for(int i = 0; i < image.getWidth(); i++){
for(int j = 0; j < image.getHeight(); j++){
Color tempColor = new Color(image.getRGB(i, j));
if(tempColor.equals(Color.BLACK)){ // Error was here
System.out.println(tempColor); //Debugging
}
}
}
Originally I had the code
if(tempColor == Color.BLACK)
instead of
if(tempColor.equals(Color.BLACK))
What I had to begin with will always evaluate to false, which was the error.

Slow map in java

I'm making a game in java, is a rpg, however, only with the map the game is slow.
The map is made ​​in TiledMap Editor, therefore, an XML that is read and loaded into an ArrayList. My PC is a dual-core 3.0, 4GB RAM, 1GB Video.
The do the rendering is done as follows:
//method of tileset class
public void loadTileset(){
positions = new int[1 + tilesX * tilesY][2];
int yy = 0;
int xx = 0;
int index = 0;
// save the initial x and y point of each tile in an array named positions
// positions[tileNumber] [0] - X position
// positions[tileNumber] [1] - Y position
for(int i = 1 ; i < positions.length; i++){
if(index == tilesX ){
yy += tileHeight;
xx = 0;
index = 0;
}
positions[i][0] = xx;
positions[i][1] = yy;
xx += tileWidth;
index++;
}
}
//method of map class
public void draw(Graphics2D screen){
//x and y position of each tile on the screen
int x = 0; int y = 0;
for(int j = 0; j < 20 ; j++){
for(int i = initialTile ; i < initialTile + quantTiles ; i++){
int tile = map[j][i];
if(tile != 0){
screen.drawImage(tileSet.getTileImage().getSubimage(tileSet.getTileX(tile), tileSet.getTileY(tile),tileSet.getTileWidth(), tileSet.getTileHeight()),x,y,null);
}
x += tileSet.getTileWidth();
}
x = 0;
y += tileSet.getTileHeight();
}
}
Am I doing something wrong?
Note: I'm new to the forum and to make matters worse I do not understand very much English, so excuse any mistake.
First of all, you should not create the subimages for the tiles during each call. Strictly speaking, you should not call getSubimage at all for images that you want to paint: It will make the image "unmanaged", and this can degrade rendering performance by an order of magnitude. You should only call getSubimage for images that you do not want to render - for example, when you are initially creating individual images for the tiles.
You obviously already have a TileSet class. You could add a bit of functionality to this class so that you can directly access images for the tiles.
Your current code looks like this:
screen.drawImage(
tileSet.getTileImage().getSubimage(
tileSet.getTileX(tile),
tileSet.getTileY(tile),
tileSet.getTileWidth(),
tileSet.getTileHeight()),
x,y,null);
You could change it to look like this:
screen.drawImage(tileSet.getTileImage(tile), x,y,null);
The getTileImage(int tile) method suggested here could then obtain tiles that have been stored internally.
I'll sketch a few lines of code from the tip of my head, you'll probably be able to transfer this into your TileSet class:
class TileSet
{
private Map<Integer, BufferedImage> tileImages;
TileSet()
{
....
prepareTileImages();
}
private void prepareTileImages()
{
tileImages = new HashMap<Integer, BufferedImage>();
for (int tile : allPossibleTileValuesThatMayBeInTheMap)
{
// These are the tiles that you originally rendered
// in your "draw"-Method
BufferedImage image =
getTileImage().getSubimage(
getTileX(tile),
getTileY(tile),
getTileWidth(),
getTileHeight());
// Create a new, managed copy of the image,
// and store it in the map
BufferedImage managedImage = convertToARGB(image);
tileImages.put(tile, managedImage);
}
}
private static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
// This is the new method: For a given "tile" value
// that you found at map[x][y], this returns the
// appropriate tile:
public BufferedImage getTileImage(int tile)
{
return tileImages.get(tile);
}
}

Categories