This script draws the controls, hero, surface and the map:
public void render(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
Drawable myImage;
int tileWidth = 50;
int tileHeight = 50;
int rowBaseX = 0;
int rowBaseY = 0;
int[][] board = new int[][] {
{0,0,0,0,0,0,2,0,0,0,},
{0,0,0,0,0,2,2,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,0,0,2,0,0,0,0,},
{0,0,0,2,2,2,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,0,2,0,0,0,0,0,0,},
{0,0,2,2,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,},
{0,0,2,0,0,0,0,0,0,0,}
};
int mapWidth = 10;
int mapHeight = 10;
for (int row = 0; row < mapHeight; row++)
{
for (int col = 0; col < mapWidth; col++)
{
Resources res = this.getContext().getResources();
switch(board[row][col])
{
case 0:
myImage = res.getDrawable(R.drawable.tile1);
break;
case 1:
myImage = res.getDrawable(R.drawable.tile2);
break;
default:
myImage = res.getDrawable(R.drawable.tile3);
break;
}
int curL = rowBaseX + (col * tileWidth);
int curU = rowBaseY + (row * tileHeight);
int curR = curL + tileWidth;
int curD = curU + tileHeight;
myImage.setBounds(curL,curU,curR,curD);
myImage.draw(canvas);
}
}
droid.draw(canvas);
butt.draw(canvas);
butt1.draw(canvas);
butt2.draw(canvas);
butt3.draw(canvas);
buttz.draw(canvas);
buttz1.draw(canvas);
buttz2.draw(canvas);
buttz3.draw(canvas);
buttx.draw(canvas);
}
There is a hero, which has to be redrawn when player moves him with the controls, and all other drawables also has to be redrawn. The problem is that drawing a map is a long process, so the bigger map i create, the slower hero moves, because every tile of the map has to be painted. Is there a way to put all the tiles to a one bitmap in other method and draw that one bitmap in the canvas method?
If creating a static map outside your application is not an option, you could separate the drawing of static content from the dynamic content.
Create a Bitmap, create a Canvas with it and draw your map on that Canvas. You only need to do this once. Render the dynamic content once per frame on another Canvas.
Then draw both Bitmaps on the "real" Canvas of your SurfaceView. It could look like this :
the once-per-map part :
Bitmap mapBitmap = Bitmap.createBitmap(width, height, myConfig);
Canvas c = new Canvas(mapBitmap);
//draw map on c
...and the once-per-frame part:
//draw dynamic stuff
Canvas canvas = getHolder().lockCanvas();
canvas.drawBitmap(mapBitmap, null, targetRect, null);
canvas.drawBitmap(heroBitmap, null, targetRect, null);
getHolder().unlockCanvasAndPost();
EDIT:
You draw your tiles like you used to with myImage.draw(canvas), but passing the mapBitmap-Canvas as argument instead of your "real" canvas. myConfig has to be a Bitmap.Config. Take the RGB_565 as it is the internal format.
The best option is to only draw the portion of your map that is visible on the screen. That way no matter how big the overall map becomes the drawing of that map is always constant. Since you're on a grid system you can easily figure out which cell the hero is in:
heroGridX = hero.x % mapWidth;
heroGridY = hero.y % mapHeight;
From there you can calculate how many cells around the player you want to draw by using the width and height of the canvas and the constant size of your grid cell's width and height:
leftGrid = heroGridX - (canvas.getWidth() / tileWidth) / 2;
topGrid = heroGridY - (canvas.getHeight() / tileHeight) / 2;
rightGrid = heroGridX + (canvas.getWidth() / tileWidth) / 2;
bottomGrid = heroGridY + (canvas.getHeight() / tileHeight) / 2;
You could use a data structure to store these values independent of the hero and only move them once the player gets close to an edge to scroll the map. That way the hero moves up and down without scrolling the map until they get X or Y pixels to the edge of a tile. Instead of calculating these inside the rendering routine.
This will use far less memory than drawing the entire map into one large bitmap. Drawing into a large bitmap is trading more memory usage for less CPU time. As your map grows larger so does the memory needed to draw that map. This algorithm merely keeps drawing the map a constant because the size of your screen doesn't change at runtime. And, in comparison to the other option your memory usage doesn't grow any larger as your map grows larger (it grows very small in comparison to drawing more tiles in a canvas). One important fact about this is that if you did get a larger screen (say a tablet vs a phone). This algorithm will scale up properly too so the player will see more of the surrounding terrain of the map.
Related
How can I blend a moving circle's motion in an CPU efficient way which still allows for fairly accurate collision detection? I've tried stretching and rotating and ellipse, but it looks unnatural. I've tried drawing in between objects my sketch below. But the motion is still jagged between the frames, and it's expensive to draw a lot of in between circles with transparency. Is there a better way to do this? For example, bridging the shapes or smoothing motion with a bezier transformation? Maybe holding the last two locations in an array and using the current and last two to draw a bezier curve and drawing a curved shape between the last location and current location with the data from all three points? How do you deal with fast motion where you need to avoid the "..." effect, but also need to use the current location for collision detection and so on?
int xprev;
int yprev;
int xcur;
int ycur;
float size = 10;
float scale;
int rotation = 0;
void setup() {
size(600, 600);
}
void draw () {
background(0);
xcur= int(cos(radians(rotation))*200) + width/2;
ycur = int(sin(radians(rotation))*200) + height/2;
rotation = rotation + 5;
//scale = dist(xcur, ycur, xprev, yprev)/2;
//println(scale);
drawEllipse();
xprev = xcur;
yprev = ycur;
}
void drawEllipse() {
for (int i = 1; i < 10; i++) {
noStroke();
fill(255, i*20);
ellipse(xprev+ (xcur - xprev) * i/10, yprev + (ycur - yprev) * i/10, size, size);
}
fill(255);
ellipse(xcur, ycur, size, size);
}
I am using the Java HeatMap library (http://www.mbeckler.org/heatMap/) to generate heatMap for my data.
I am working with a daatset which has NA values. So, basically I want no color (white color) for the pixels which have NA value. But, unfortunately this library does not support data with NA value and what I get is a plan block of image with the base color. I tried looking into the source code, so as to make some changes. Within the code, the drawData() method is used to color each pixel in the bufferedImage (possibly!). Can someone help me with how I can implement support for NA values and to show them with no color? I have little to no experience with BufferedImage and Graphics2D class.
Here is the drawData() method from the source code of the library:
/**
* Creates a BufferedImage of the actual data plot.
*
* After doing some profiling, it was discovered that 90% of the drawing
* time was spend drawing the actual data (not on the axes or tick marks).
* Since the Graphics2D has a drawImage method that can do scaling, we are
* using that instead of scaling it ourselves. We only need to draw the
* data into the bufferedImage on startup, or if the data or gradient
* changes. This saves us an enormous amount of time. Thanks to
* Josh Hayes-Sheen (grey#grevian.org) for the suggestion and initial code
* to use the BufferedImage technique.
*
* Since the scaling of the data plot will be handled by the drawImage in
* paintComponent, we take the easy way out and draw our bufferedImage with
* 1 pixel per data point. Too bad there isn't a setPixel method in the
* Graphics2D class, it seems a bit silly to fill a rectangle just to set a
* single pixel...
*
* This function should be called whenever the data or the gradient changes.
*/
private void drawData()
{
// System.out.println("Column: " + data.length + " row: " + data[0].length);
bufferedImage = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_ARGB);
bufferedGraphics = bufferedImage.createGraphics();
for (int x = 0; x < data.length; x++)
{
for (int y = 0; y < data[0].length; y++)
{
bufferedGraphics.setColor(colors[dataColorIndices[x][y]]);
bufferedGraphics.fillRect(x, y, 1, 1);
}
}
}
sample data can be found at: http://www.filedropper.com/data_13
So, this is how it looks at the moment:
From Java:
From R:
Please ignore the orientation of the two images
As the BufferedImage is created as ARGB, you could just not paint anything on those pixels that should be undefined, and they'll stay transparent. Something like:
public static int NA = ...;
private void drawData()
{
bufferedImage = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_ARGB);
Graphics2D bufferedGraphics = bufferedImage.createGraphics();
try {
for (int x = 0; x < data.length; x++)
{
for (int y = 0; y < data[0].length; y++)
{
int colorIndex = dataColorIndices[x][y];
if (colorIndex != NA) {
bufferedGraphics.setColor(colors[colorIndex]);
bufferedGraphics.fillRect(x, y, 1, 1);
}
// Alternate flow, if you really want the pixels to be white
// else {
// bufferedGraphics.setColor(Color.WHITE);
// bufferedGraphics.fillRect(x, y, 1, 1);
// }
}
}
}
finally {
bufferedGraphics.dispose();
}
}
I also dispose the Graphics2D instance, to avoid resource leaks.
We are trying to get only the portion of the image out of the captured image. But in java we only get subimage in rectangular form using image.getImage(x,y,width, height). Let say if i virutally split the image as 10 parts as shown below. How can i able to extract only 1,2,4,6,8,9,10 out of it as show in the second image using native java very without consuming too many resources and time.
Update
Below is the sample code
for (int x = 0; x < columns; x++) {
for (int y = 0; y < rows; y++) {
imagePart = img.getSubimage(x * this.smallWidth, y
* this.smallHeight, this.smallWidth,
this.smallHeight);
if (!ifSelectedPart(imagePart)) {
smallImages[x][y] = imagePart;
}
else {
smallImages[x][y] = fillwithAlpha();
}
}
createImage(smallImages[][])
If these rectangles are all the same size you can treat the image as a grid and calculate what region of the image you need with a little math.
int numberColumns = 2;
int numberRows = 5;
public Rectangle getSubregion(int row, int column, int imgWidth, int imgHeight){
int cellWidth = imgWidth / numberColumns;
int cellHeight = imgHeight / numberRows;
return new Rectangle(column*cellWidth, row*cellHeight,cellWidth, cellHeight);
}
//usage
Rectangle cellOne = getSubregion(0, 0, img.getWidth(),img.getHeight());
Then just render each of those subregions to a new image in memory.
Images are by their nature rectangular. Perhaps you wish to draw over the image with 0 alpha composite color to cover up the region that you don't want to see. Either that or create a grid of rectangular sub-images, and keep the ones from the grid that you want to display.
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
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