Draw large bitmaps in Android - java

I don't know whether or not this question was answered. At least I didn't find an answer.
So here is a thing: I'm making some space-themed 2D game on android, and I'm testing it on emulator with screen size = 2560x1600. In this game there is a field where space ship is flying. And of course it (a field) must have a beautiful background with high resolution. My background image's resolution is 4500x4500. I want to make my image move in opposite direction relative to camera movement, so thats why I can't use small static image. At the time only a part of this image is visible:
When I tried to draw it I got fps = 1-2 (of course it is low because of the image size):
canvas.drawBitmap(getBigImage(), -x, -y, null);
/* getBigImage() method does nothing but returning
a Bitmap object (no calculation or decoding is performing in there) */
I tried to cut out the needed image from the big one but fps was still low:
Bitmap b = Bitmap.createBitmap(getBigImage(), x, y, sw, sh);
canvas.drawBitmap(b, 0, 0, null);
How can I draw this big bitmap with high fps?

Try
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
which takes a rectangle of pixels from the source image to display in a rectangle on the Canvas. I found this to be faster back when I did a scrolling game.

I was thinking a lot and came up with an idea to divide input bitmap to small chunks and save them to an array. So now to draw that bitmap all I have to do is to draw visible chunks.
Picture:
Big black rectangle means input bitmap, green rectangle means viewport, red rectangle means visible chunks that are drawn
I've wrote an object that does that all (I didn't check it for bugs yet :/). I've tested it and it draws 3000x3000 bitmap with ~45 fps. I'm considering this way as very effective. The object itself may need to be developed more but I think this functionality is enough for my needs. Hope it'll help someone :)
P.S. https://stackoverflow.com/a/25953122/6121671 - used this for inspiration :)
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
public final class DividedBitmap {
private final Bitmap[][] mArray; // array where chunks is stored
private final int mWidth; // original (full) width of source image
private final int mHeight; // original (full) height of source image
private final int mChunkWidth; // default width of a chunk
private final int mChunkHeight; // default height of a chunk
/* Init */
public DividedBitmap(Bitmap src) {
this(new Options(src, 100, 100));
}
public DividedBitmap(Options options) {
mArray = divideBitmap(options);
mWidth = options.source.getWidth();
mHeight = options.source.getHeight();
mChunkWidth = options.chunkWidth;
mChunkHeight = options.chunkHeight;
}
/* Getters */
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public Bitmap getChunk(int x, int y) {
if (mArray.length < x && x > 0 && mArray[x].length < y && y > 0) {
return mArray[x][y];
}
return null;
}
/* Methods */
/**
* x, y are viewport coords on the image itself;
* w, h are viewport's width and height.
*/
public void draw(Canvas canvas, int x, int y, int w, int h, Paint paint) {
if (x >= getWidth() || y >= getHeight() || x + w <= 0 || y + h <= 0)
return;
int i1 = x / mChunkWidth; // i1 and j1 are indices of visible chunk that is
int j1 = y / mChunkHeight; // on the top-left corner of the screen
int i2 = (x + w) / mChunkWidth; // i2 and j2 are indices of visible chunk that is
int j2 = (y + h) / mChunkHeight; // on the right-bottom corner of the screen
i2 = i2 >= mArray.length ? mArray.length - 1 : i2;
j2 = j2 >= mArray[i2].length ? mArray[i2].length - 1 : j2;
int offsetX = x - i1 * mChunkWidth;
int offsetY = y - j1 * mChunkHeight;
for (int i = i1; i <= i2; i++) {
for (int j = j1; j <= j2; j++) {
canvas.drawBitmap(
mArray[i][j],
(i - i1) * mChunkWidth - offsetX,
(j - j1) * mChunkHeight - offsetY,
paint
);
}
}
}
/* Static */
public static Bitmap[][] divideBitmap(Bitmap bitmap) {
return divideBitmap(new Options(bitmap, 100, 100));
}
public static Bitmap[][] divideBitmap(Options options) {
Bitmap[][] arr = new Bitmap[options.xCount][options.yCount];
for (int x = 0; x < options.xCount; ++x) {
for (int y = 0; y < options.yCount; ++y) {
int w = Math.min(options.chunkWidth, options.source.getWidth() - (x * options.chunkWidth));
int h = Math.min(options.chunkHeight, options.source.getHeight() - (y * options.chunkHeight));
arr[x][y] = Bitmap.createBitmap(options.source, x * options.chunkWidth, y * options.chunkHeight, w, h);
}
}
return arr;
}
public static final class Options {
final int chunkWidth;
final int chunkHeight;
final int xCount;
final int yCount;
final Bitmap source;
public Options(Bitmap src, int chunkW, int chunkH) {
chunkWidth = chunkW;
chunkHeight = chunkH;
xCount = ((src.getWidth() - 1) / chunkW) + 1;
yCount = ((src.getHeight() - 1) / chunkH) + 1;
source = src;
}
public Options(int xc, int yc, Bitmap src) {
xCount = xc;
yCount = yc;
chunkWidth = src.getWidth() / xCount;
chunkHeight = src.getHeight() / yCount;
source = src;
}
}
}

Related

Calculating 'color distance' between 2 points in a 3-dimensional space

I have a homework task where I have to write a class responsible for contour detection. It is essentially an image processing operation, using the definition of euclidean distance between 2 points in the 3-dimensional space. Formula given to us to use is:
Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
We need to consider each entry of the two dimensional array storing the colors of the pixels of an image, and if some pixel, pix, the color distance between p and any of its neighbors is more than 70, change the color of the pixel to black, else change it to white.
We are given a seperate class as well responsible for choosing an image, and selecting an output, for which method operationContouring is applied to. Java syntax and convention is very new to me having started with python. Conceptually, I'm struggling to understand what the difference between pix1 and pix2 is, and how to define them. This is my code so far.
Given:
import java.awt.Color;
/* Interface for ensuring all image operations invoked in same manner */
public interface operationImage {
public Color[][] operationDo(Color[][] imageArray);
}
My code:
import java.awt.Color;
public class operationContouring implements operationImage {
public Color[][] operationDo(Color[][] imageArray) {
int numberOfRows = imageArray.length;
int numberOfColumns = imageArray[0].length;
Color[][] results = new Color[numberOfRows][numberOfColumns];
for (int i = 0; i < numberOfRows; i++)
for (int j = 0; j < numberOfColumns; j++) {
int red = imageArray[i][j].getRed();
int green = imageArray[i][j].getGreen();
int blue = imageArray[i][j].getBlue();
double DistanceColor = Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
int LIMIT = 70;
if (DistanceColor> LIMIT ) {
results[i][j] = new Color((red=0), (green=0), (blue=0));
}
else {
results[i][j] = new Color((red=255), (green=255), (blue=255));
}
}
return results;
}
}
This is a solution I wrote that uses BufferedImages. I tested it and it should work. Try changing it such that it uses your data format (Color[][]) and it should work for you too. Note that "pix1" is nothing more than a description of the color of some pixel, and "pix2" is the description of the color of the pixel you are comparing it to (determining whether the color distance > 70).
public static boolean tooDifferent(Color c1, Color c2) {
return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(),2) + Math.pow(c1.getGreen()- c2.getGreen(),2) + Math.pow(c1.getBlue()- c2.getBlue(),2)) > 70;
}
public static Color getColor(int x, int y, BufferedImage img) {
return new Color(img.getRGB(x, y));
}
public static BufferedImage operationDo(BufferedImage img) {
int numberOfRows = img.getHeight();
int numberOfColumns = img.getWidth();
BufferedImage results = new BufferedImage(numberOfColumns, numberOfRows, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < numberOfRows; y++) {
for (int x = 0; x < numberOfColumns; x++) {
Color color = new Color(img.getRGB(x, y));
boolean aboveExists = y > 0;
boolean belowExists = y < numberOfRows - 1;
boolean leftExists = x > 0;
boolean rightExists = x < numberOfColumns - 1;
if ((aboveExists && tooDifferent(color, getColor(x, y - 1, img))) ||
(belowExists && tooDifferent(color, getColor(x, y + 1, img))) ||
(leftExists && tooDifferent(color, getColor(x - 1, y, img))) ||
(rightExists && tooDifferent(color, getColor(x + 1, y, img)))) {
results.setRGB(x, y, Color.black.getRGB());
} else {
results.setRGB(x, y, Color.white.getRGB());
}
}
}
return results;
}

java - a more efficient way to draw tiles?

I have a specific tile object in my program. It consists of two images which can also be flipped. The first column of tiles (whether it's from the left or the right side it depends on the 'flip' boolean) has to be the 'bricks0' image, while all other should be 'bricks1'. So, this works just as I want it to and I'm just explaining this to make things clear about the code.
I have two of these objects in my program (one of them is flipped and the other isn't), which . So, the problem is that it ruins the performance of the game when more images need to be drawn. For example, if I lower the height of these objects, or render only one object, there is no lag.
So, how do I make this work more efficiently?
#Override
public void render(Graphics g) {
if(destroyed)
return;
//Initialises the height and width of a tile
int tW = 0, tH = 0;
BufferedImage img = bricks0;
if(!flip) {
for(int i = 0; i < w; i += tW) {
//Sets the image to 'bricks1' if it's not the first column, else, uses the initialised type of the image 'bricks0'
if(i != 0)
img = bricks1;
for(int j = 0; j < h; j += tH) {
//Calculates the remaining width and height yet to be drawn
int W = Math.abs(w - i), H = Math.abs(h - j);
/*Sets the width and height of the image;
* The width of 'bricks0' is 53, while for 'bricks1' it's 64;
* Height is 64
*/
tW = img.getWidth();
tH = img.getHeight();
/* Calculates the width and height of the subimage that is cropped from the original;
* This is done to prevent an image getting drawn out of the object's bounds
*/
if(W > tW)
W = tW;
if(H > tH)
H = tH;
//Crops and draws the image
img = img.getSubimage(0, 0, W, H);
g.drawImage(img, this.x + i, this.y + j, null);
}
}
}
else {
for(int i = w; i > 0; i -= tW) {
//Sets the image to 'bricks1' if it's not the first column, else, uses the initialised type of the image 'bricks0'
if(i != w)
img = bricks1;
for(int j = 0; j < h; j += tH) {
//Calculates the remaining width and height yet to be drawn
int W = i, H = Math.abs(h - j);
/*Sets the width and height of the image;
* The width of 'bricks0' is 53, while for 'bricks1' it's 64;
* Height is 64
*/
tW = img.getWidth();
tH = img.getHeight();
/* Calculates the width and height of the subimage that is cropped from the original;
* This is done to prevent an image getting drawn out of the object's bounds
*/
int x = 0, y = 0;
if(W > tW) {
W = tW;
}
else {
x = tW - W;
}
if(H > tH) {
H = tH;
}
//Crops and draws the image
img = img.getSubimage(x, y, W, H);
g.drawImage(img, this.x + i - tW, this.y + j, null);
}
}
}
}
I am currently drawing 26 images for both objects. 1 column is the 'bricks0' image, while the second is the 'bricks1' for each one of them.
edit: Okay, I've reduced the probability of a tile getting cropped by getSubimage(), but it still lags a little in cases when it has to cropp it..

Java OpenCV Layer small image onto larger image with transparency

I am trying to write a function that overlays an image at a rectangle with transparency over top of another image, However it doesn't layer the images it just erases the section that I overlay and the transparency cuts through the entire image. Here is my code.
public static void overlayImage(String imagePath, String overlayPath, int x, int y, int width, int height) {
Mat overlay = Imgcodecs.imread(overlayPath, Imgcodecs.IMREAD_UNCHANGED);
Mat image = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_UNCHANGED);
Rectangle rect = new Rectangle(x, y, width, height);
Imgproc.resize(overlay, overlay, rect.size());
Mat submat = image.submat(new Rect(rect.x, rect.y, overlay.cols(), overlay.rows()));
overlay.copyTo(submat);
Imgcodecs.imwrite(imagePath, image);
}
EDIT: Here are some example pictures:
Before:
After:
Found this function that does exactly what I needed.
public static void overlayImage(Mat background,Mat foreground,Mat output, Point location){
background.copyTo(output);
for(int y = (int) Math.max(location.y , 0); y < background.rows(); ++y){
int fY = (int) (y - location.y);
if(fY >= foreground.rows())
break;
for(int x = (int) Math.max(location.x, 0); x < background.cols(); ++x){
int fX = (int) (x - location.x);
if(fX >= foreground.cols()){
break;
}
double opacity;
double[] finalPixelValue = new double[4];
opacity = foreground.get(fY , fX)[3];
finalPixelValue[0] = background.get(y, x)[0];
finalPixelValue[1] = background.get(y, x)[1];
finalPixelValue[2] = background.get(y, x)[2];
finalPixelValue[3] = background.get(y, x)[3];
for(int c = 0; c < output.channels(); ++c){
if(opacity > 0){
double foregroundPx = foreground.get(fY, fX)[c];
double backgroundPx = background.get(y, x)[c];
float fOpacity = (float) (opacity / 255);
finalPixelValue[c] = ((backgroundPx * ( 1.0 - fOpacity)) + (foregroundPx * fOpacity));
if(c==3){
finalPixelValue[c] = foreground.get(fY,fX)[3];
}
}
}
output.put(y, x,finalPixelValue);
}
}
}

Wave generation with the "Hugo Elias" algorithm please! Java

I appear to have hit a wall in my most recent project involving wave/ripple generation over an image. I made one that works with basic colors on a grid that works perfectly; heck, I even added shades to the colors depending on the height of the wave.
However, my overall goal was to make this effect work over an image like you would see here. I was following an algorithm that people are calling the Hugo Elias method (though idk if he truly came up with the design). His tutorial can be found here!
When following that tutorial I found his pseudo code challenging to follow. I mean the concept for the most part makes sense until I hit the height map portion over an image. The problem being the x and y offsets throw an ArrayIndexOutOfBoundsException due to him adding the offset to the corresponding x or y. If the wave is too big (i.e. in my case 512) it throws an error; yet, if it is too small you can't see it.
Any ideas or fixes to my attempted implementation of his algorithm?
So I can't really make a compile-able version that is small and shows the issue, but I will give the three methods I'm using in the algorithm. Also keep in mind that the buffer1 and buffer2 are the height maps for the wave (current and previous) and imgArray is a bufferedImage represented by a int[img.getWidth() * img.getHeight()] full of ARGB values.
Anyways here you go:
public class WaveRippleAlgorithmOnImage extends JPanel implements Runnable, MouseListener, MouseMotionListener
{
private int[] buffer1;
private int[] buffer2;
private int[] imgArray;
private int[] movedImgArray;
private static double dampening = 0.96;
private BufferedImage img;
public WaveRippleAlgorithmOnImage(BufferedImage img)
{
this.img = img;
imgArray = new int[img.getHeight()*img.getWidth()];
movedImgArray = new int[img.getHeight()*img.getWidth()];
imgArray = img.getRGB(0, 0,
img.getWidth(), img.getHeight(),
null, 0, img.getWidth());
//OLD CODE
/*for(int y = 0; y < img.getHeight(); y++)
{
for(int x = 0; x < img.getWidth(); x++)
{
imgArray[y][x] = temp[0 + (y-0)*img.getWidth() + (x-0)];
}
}*/
buffer1 = new int[img.getHeight()*img.getWidth()];
buffer2 = new int[img.getHeight()*img.getWidth()];
buffer1[buffer1.length/2] = (img.getWidth() <= img.getHeight() ? img.getWidth() / 3 : img.getHeight() / 3);
//buffer1[25][25] = 10;
back = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
//<editor-fold defaultstate="collapsed" desc="Used Methods">
#Override
public void run()
{
while(true)
{
this.update();
this.repaint();
this.swap();
}
}
//Called from Thread to update movedImgArray prior to being drawn.
private void update()
{
//This is my attempt of trying to convert his code to java.
for (int i=img.getWidth(); i < imgArray.length - 1; i++)
{
if(i % img.getWidth() == 0 || i >= imgArray.length - img.getWidth())
continue;
buffer2[i] = (
((buffer1[i-1]+
buffer1[i+1]+
buffer1[i-img.getWidth()]+
buffer1[i+img.getWidth()]) >> 1)) - buffer2[i];
buffer2[i] -= (buffer2[i] >> 5);
}
//Still my version of his code, because of the int[] instead of int[][].
for (int y = 1; y < img.getHeight() - 2; y++)
{
for(int x = 1; x < img.getWidth() - 2; x++)
{
int xOffset = buffer1[((y)*img.getWidth()) + (x-1)] - buffer1[((y)*img.getWidth()) + (x+1)];
int yOffset = buffer1[((y-1)*img.getWidth()) + (x)] - buffer1[((y+1)*img.getWidth()) + (x)];
int shading = xOffset;
//Here is where the error occurs (after a click or wave started), because yOffset becomes -512; which in turn gets
//multiplied by y... Not good... -_-
movedImgArray[(y*img.getWidth()) + x] = imgArray[((y+yOffset)*img.getWidth()) + (x+xOffset)] + shading;
}
}
//This is my OLD code that kidna worked...
//I threw in here to show you how I was doing it before I switched to images.
/*
for(int y = 1; y < img.getHeight() - 1; y++)
{
for(int x = 1; x < img.getWidth() - 1; x++)
{
//buffer2[y][x] = ((buffer1[y][x-1] +
//buffer1[y][x+1] +
//buffer1[y+1][x] +
//buffer1[y-1][x]) / 4) - buffer2[y][x];
buffer2[y][x] = ((buffer1[y][x-1] +
buffer1[y][x+1] +
buffer1[y+1][x] +
buffer1[y-1][x] +
buffer1[y + 1][x-1] +
buffer1[y + 1][x+1] +
buffer1[y - 1][x - 1] +
buffer1[y - 1][x + 1]) / 4) - buffer2[y][x];
buffer2[y][x] = (int)(buffer2[y][x] * dampening);
}
}*/
}
//Swaps buffers
private void swap()
{
int[] temp;
temp = buffer2;
buffer2 = buffer1;
buffer1 = temp;
}
//This creates a wave upon clicking. It also is where that 512 is coming from.
//512 was about right in my OLD code shown above, but helps to cause the Exeception now.
#Override
public void mouseClicked(MouseEvent e)
{
if(e.getX() > 0 && e.getY() > 0 && e.getX() < img.getWidth() && e.getY() < img.getHeight())
buffer2[((e.getY())*img.getWidth()) + (e.getX())] = 512;
}
private BufferedImage back;
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
back.setRGB(0, 0, img.getWidth(), img.getHeight(), movedImgArray, 0, img.getWidth());
g.drawImage(back, 0, 0, null);
}
}
P.S. Here are two images of the old code working.
Looking at my original pseudocode, I assume the Array Out Of Bounds error is happening when you try to look up the texture based on the offset. The problem happens because the refraction in the water is allowing us to see outside of the texture.
for every pixel (x,y) in the buffer
Xoffset = buffer(x-1, y) - buffer(x+1, y)
Yoffset = buffer(x, y-1) - buffer(x, y+1)
Shading = Xoffset
t = texture(x+Xoffset, y+Yoffset) // Array out of bounds?
p = t + Shading
plot pixel at (x,y) with colour p
end loop
The way to fix this is simply to either clamp the texture coordinates, or let them wrap. Also, if you find that the amount of refraction is too much, you can reduce it by bit-shifting the Xoffset and Yoffset values a little bit.
int clamp(int x, int min, int max)
{
if (x < min) return min;
if (x > max) return max;
return x;
}
int wrap(int x, int min, int max)
{
while (x<min)
x += (1+max-min);
while (x>max)
x -= (1+max-min);
return x;
}
for every pixel (x,y) in the buffer
Xoffset = buffer(x-1, y) - buffer(x+1, y)
Yoffset = buffer(x, y-1) - buffer(x, y+1)
Shading = Xoffset
Xoffset >>= 1 // Halve the amount of refraction
Yoffset >>= 1 // if you want.
Xcoordinate = clamp(x+Xoffset, 0, Xmax) // Use clamp() or wrap() here
Ycoordinate = clamp(y+Yoffset, 0, Ymax) //
t = texture(Xcoordinate, Ycoordinate)
p = t + Shading
plot pixel at (x,y) with colour p
end loop

How to loop sprite when it touches the border (android game development)

please help. I am new to android game development and java. I want my deer to loop or start from 0,0 again when it touches the border of the screen but i have no idea how to code it.
package com.cmanres.bunnyjourney;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
public class Spriteleft {
private static final int BMP_COLUMNS = 3;
private static final int BMP_ROWS = 2;
private int x = 0;
private int y=0;
private int xSpeed = 3;
private Levelunogame gameView;
private Bitmap deer1;
private int width;
private int height;
private int currentFrame=0;
public Spriteleft(Levelunogame gameView, Bitmap deer1) {
this.gameView=gameView;
this.deer1=deer1;
this.width = deer1.getWidth() / BMP_COLUMNS;
this.height = deer1.getHeight() / BMP_ROWS;
}
private void update() {
if (x > gameView.getWidth() - width - xSpeed) {
xSpeed = -3;
}
if (x + xSpeed< 0) {
xSpeed = 3;
}
x = x + xSpeed;
currentFrame = ++currentFrame % BMP_COLUMNS;
}
public void onDraw(Canvas canvas) {
update();
int srcX = currentFrame * width;
int srcY = 1 * height;
Rect src = new Rect(srcX, srcY, srcX + width, srcY + height);
Rect dst = new Rect(x, y, x + width, y + height);
canvas.drawBitmap(deer1, src , dst, null);
}
}
that is my code that i followed on a android mobile game tutorial. can anyone show me how to do it? or give any links for a tutorial? I will appreciate it very much.
Assuming your sprite is positioned at x, y with sprite_width, sprite_height
// Check if your sprite position is outside the screen bounds
if( x < 0 || x > gameView.getWidth() - sprite_width
|| y < 0 || y > gameView.getHeight() - sprite_height ) {
// Set x, y to 0,0
x = 0;
y = 0;
}

Categories