In my custom view I have to draw on a base Bitmap when the user selects a flag. Suppose I have a base face bitmap and the user turns the mustache flag on. Since this custom View has to be scalable, I want to work with relative coordinates (in the range [0,1]) instead of bitmap width and height. The following doesn't work
Bitmap orig, face;
public void onDraw(Canvas c) {
c.drawBitmap(face);
}
public void onMustacheFlag() {
face = Bitmap.create(orig);
Canvas c = new Canvas(face);
c.save();
c.scale(1f / face.getWidth(), 1f / face.getHeight());
// Draw lines, circle, rectangles with all vertices in the range [0.0f, 1.0f]
c.restore();
}
Also, this custom view forces height to be equal to width (it's a square form factor)
The parameters to Canvas.scale(float, float) serve as coefficients for x e and y coordinates in drawing routines. Passing 1f / width means multiplying values assumed in the range (0, 1) by a number less then 1, thus the result of all drawing routines end up in the top left corner and is likely not visible. Instead, if I pass width and height, the code works as intended, because the range (0, 1) is mapped to (0, width):
public void onMustacheFlag() {
face = Bitmap.create(orig);
Canvas c = new Canvas(face);
c.save();
c.scale(face.getWidth(), face.getHeight()); // instead of 1 / width
// Draw lines, circle, rectangles with all vertices in the range [0.0f, 1.0f]
c.restore();
}
Related
I have to create a custom view where I have to draw a rectangle. I'm trying to use the canvas.drawRect method. I wanted to create rectangles somethig like this
the gray colored one is my custom view which is extending the View class.
Inside the onDraw method I'm trying to draw the rectangle.
But actually I'm confused with the parameters of the drawRect method.
As per the documentation
/**
* Draw the specified Rect using the specified paint. The rectangle will
* be filled or framed based on the Style in the paint.
*
* #param left The left side of the rectangle to be drawn
* #param top The top side of the rectangle to be drawn
* #param right The right side of the rectangle to be drawn
* #param bottom The bottom side of the rectangle to be drawn
* #param paint The paint used to draw the rect
*/
What I assume is that left and top forms the x,y coordinates of the starting point, and right is the width and bottom is the height. But it doesn't seem to work that way.
I tried something like this to draw one rectangle but it does not draw anything
paint.setColor(Color.BLUE);
canvas.drawRect(5, canvas.getHeight()/2, 30, 30, paint );
Can anyone please tell how exactly a rectangle is drawn using these values?
It would be very helpful if someone could show the code to draw at least the first rectangle.
My requirement is like, the number of inner rectangles are dynamic, so if I pass some 4 to this View, it should create 4 equal width rectangles horizontally. something like
Thanks in advance!!
But actually I'm confused with the parameters of the drawRect method.
The drawRect method requires only two coordinates to draw a rectangle.
The top left corner and the bottom right corner. So the 4 points form these 2 coordinates
in your canvas. Hope it is clear from the below images
P1 and P2 are points formed by (left,top) and (right,bottom), So the drawn rectangle would be like this.
To draw rectangles dynamically like you have shown in you image , try something like this
int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}; // given some fixed colors
in you onDraw method
#Override
protected void onDraw(Canvas canvas) {
int padding = 5;
float rectangleWidth = (getMeasuredWidth() - padding * 2) / colors.length;
for (int i = 0; i < colors.length; i++) {
paint.setColor(colors[i]);
canvas.drawRect(padding + (rectangleWidth * i),
getMeasuredHeight() / 2,
padding + rectangleWidth * (i + 1),
getMeasuredHeight() - padding, paint
); // 5 px is the padding given to the canvas
}
}
drawRect(float left, float top, float right, float bottom, Paint paint)
It seems in your case the problem is that if right is less than left or bottom is less than top then the rectangle is not drawn. But it seems it happens only in some devices , as #David Medenjak commented
I also recommend you to use the View dimensions and not the Canvas dimensions, that is better to use getWidth() & getHeight() and not Canvas.getWidth() & Canvas.getHeight()
Use this method to draw rectangle at X & Y coordinate with width and height params
fun drawRectangle(left: Int, top: Int, right: Int, bottom: Int, canvas: Canvas, paint: Paint?) {
var right = right
var bottom = bottom
right = left + right // width is the distance from left to right
bottom = top + bottom // height is the distance from top to bottom
canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint!!)
}
Usage
//drawing rectangle
drawRectangle(posX, posY, 60, 40, canvas, paint)
I am creating a button class that will display a blue circle if it is not being pressed and a red circle if it is being pressed. I start by building a 4 vertex quad composed of two triangles. I generate my own bitmap and draw two circles on the bitmap next to each other, left to right. I then create a texture buffer with instead 4 uv points, I create it with 8, one that maps out the blue circle, one that maps out the red circle. I would then like to render the red circle when the button is pressed. Ideally I would like to call the
gl.glTexCoordPointer
method and pass in an offset, but that is not working. Here is my method that I used to generate the bitmap, draw on the bitmap using the canvas and paint objects and then attempt to map the textures. Note that I have to generate a texture that is a power of 2 so there is some math in there that allows me to generate a bigger bitmap than I need based of the width and height variables of the button that were specified in the constructor.
public void InitializeButton(GL10 gl, int upcolor, int downcolor, String symbol)
{
//Our variables for creating a bitmap and texture
Canvas canvas = null;
Bitmap bitmap = null;
Paint paint = null;
//Set up the bitmap type
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
/*
* We now want to calculate the size of the texture. Remember it is best to be a square
* texture or at least a power of 2. The below equation below will do this. For example
* if the width of the button was say 20, we need to find the smallest power of 2 that is
* greater than 20. In this case we know it is 32. But how do we calculate that? First we
* have to find out the exponent of what 2^x = 20. Then we find the ceiling of tha number.
* In order to make that calculation we have to take the log of that, but in order to use
* the log function which is base 10, we have to switch to base 2 so that means
* we have to take the log(width)/log(2) to switch to base 2, then get the ceiling of that
* number because it would be between 4 and 5 in this case. When we take the ceiling we get
* 5 and 2^5 is 32.
*
* Side note, we want to double the size to make sure there is room for the up and the down
* actions.
*/
widthTexture = (int) Math.pow(2,Math.ceil((Math.log(this.width*2)/Math.log(2))));
heightTexture = (int) Math.pow(2,Math.ceil((Math.log(this.height*2)/Math.log(2))));
/*
* Now we will create the bitmap for the creation of the button
*/
bitmap = Bitmap.createBitmap(widthTexture,heightTexture,conf);
//Now create a new canvas from that bitmap
canvas = new Canvas(bitmap);
//Create a new Paint
paint = new Paint();
/*
* Now we want to render the draw the up and down button on the texture. We are just going
* to use two different colors to represent up and down. So we will draw the up circle button
* starting at 0 0 and the down button off to the right.
*/
paint.setColor(upcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(0,0,width,height), paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width/2, height/2, paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width/2+3, height/2+3, paint);
//Draw the down color button
paint.setColor(downcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(width,0,width*2,height), paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width+(width/2), height/2, paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width+(width/2)+3, height/2+3, paint);
float widthpercent = ((float)width/(float)widthTexture);
float heightpercent = ((float)height/(float)heightTexture);
/*
* Now create two texture maps. One for the up button and one for the down button
* You can change the offset of the draw texture thing to change the animations now
*/
float uvTextures[] = {0f, heightpercent,
widthpercent, heightpercent,
widthpercent, 0f,
0f, 0f,
widthpercent, heightpercent,
widthpercent*2, heightpercent,
widthpercent*2, 0f,
widthpercent, 0f,
};
/*
* Allocate the byte buffer so it is a normal array of floats and not a java array.
* load the uvTexture values inside.
*/
ByteBuffer tbb = ByteBuffer.allocateDirect(uvTextures.length*4);
tbb.order(ByteOrder.nativeOrder());
textureBuffer = tbb.asFloatBuffer();
textureBuffer.put(uvTextures);
textureBuffer.position(0);
int [] textures = new int[1];
gl.glGenTextures(1, textures,0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
textureID = textures[0];
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,0,bitmap,0);
//Don't forget to deallocate the bitmap
bitmap.recycle();
}
So eventually in the render method, I want to be able to render different coordinates on the texture map to the same vertices. So I call glTexCoordPointer and change the offset to "counter" where counter should have been 6*4 (6 vertices times 4 bytes per float) but that doesn't work, so I tried counter at 0 and incremented it and never found the magical number that will map the red button. On a side note, when I tried that, I would get very weird patterns drawn, sometimes showing 10 to 15 mini blue and red circles.
if(isdown)
gl.glTexCoordPointer(2,GL10.GL_FLOAT,counter,textureBuffer);
else
gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,textureBuffer);
#Harism was correct for my situation. I hope I am still using this correctly. I was thinking the stride variable could be changed, but I had to change the position of the buffer. This worked for animating textures on 3D surfaces. I don't know if this is the "best" way to do it, but until then I'll be using this.
I have a SurfaceView canvas I'm drawing a scene into.
The canvas is transformed to accomodate the logical scene size which is 1024x768 (while the actual screen is 800x480), and also to support zoom/scroll navigation on it.
Since I did not want black stripes on the sides (fitting 4:3 to 16:9) I'm using a different background bitmap which has "stretched" sides to cover the ratio difference.
Another twist in the plot, is that I need to support both phones and tables. For tablets I'm loading a higher quality (size) image and for phones I'm scaling down the background bitmap when loading it from disk.
Below is code that does what I want (although probably not the best way), and in particular, I have a feeling the background calculations can be cached into a matrix to be used later in canvas.drawBitmap(Bitmap, Matrix, Paint);
(Also, I couldn't get the equivalent positioning of drawBitmap(Bitmap, left, top, Paint) by translating the canvas with these offsets, I'm also curious why).
This is the draw routine:
public void draw(Canvas canvas) {
canvas.save();
float midX = width/2;
float midY = height/2;
// zoom out to fit logical view, and translate for future
// sprite drawing with logical coordinates
// x,y and zoom are calculated in on init and updated in touch events
canvas.translate(x, y);
canvas.scale(zoom, zoom, midX, midY);
// background part
if(back != null && !back.isRecycled()) {
// todo: these can probably be pre-calculated for optimization
// todo: but so far I couldn't get it right..
if(bgMatrix != null) { // this is where I'm thinking of using the cached matrix
canvas.drawBitmap(back, bgMatrix, paint);
}
else {
float offsetW = (width-back.getWidth())/2;
float offsetH = (height-back.getHeight())/2;
canvas.save();
// bgScaleFactor is calculated upon setting the bg bitmap
// it says by how much we need to scale the image to fill the canvas
// taking into account the image (possible) downscale
canvas.scale(bgScaleFactor, bgScaleFactor, midX, midY);
// this doesn't work: canvas.postTranslate(offsetW, offsetH) and use 0,0 for next draw
canvas.drawBitmap(back, offsetW, offsetH, paint);
// todo: here I would like to save a matrix which represents
// how the back bitmap was drawn onto the canvas
// so that next time these calculations can be avoided
// this fails: bgMatrix = canvas.getMatrix();
canvas.restore();
}
// draw scene shapes on transformed canvas
if(shapes != null){
shapes.onDraw(canvas);
}
canvas.restore();
}
The mentioned class has got the following method:
protected void drawCompass(Canvas canvas, float bearing) {
int offset = Math.max(canvas.getHeight(), canvas.getWidth()) / 8;
Rect r = new Rect(0, 0, 2*offset, 2*offset);
canvas.drawBitmap(compassBase, null, r, paint);
canvas.rotate(-bearing, offset, offset);
canvas.drawBitmap(compassArrow, null, r, paint);
}
Link to the complete src: MyLocationOverlay
There they create a Rect r witch specifies where the both Bitmaps should be drawn on the canvas.
The java doc of drawBitmap says :"Draw the specified bitmap, scaling/translating automatically to fill the destination rectangle. If the source rectangle is not null, it specifies the subset of the bitmap to draw. "
Because both Bitmaps use the same Rect r and because they both are automatically scaled to fit the Rect, why is the result a perfect Compass when I activate the compass in the MapView.
To my mind the result should be crap, because the arrow of the compass is also scaled to fit the Rect.
So where is the error in reasoning?
They are scaled using the same proportion, not to absolute size. To answer your question, the canvas is drawn on using the bitmap as reference - think of it like a painter and a canvas painting one image on top of the other using two photo images as reference.
I have this sprite rotating algorithm (its poorly named and just used for testing). It is so close, sprites drawn with it do rotate. Everyframe I can add +5 degrees to it and see my nice little sprite rotate around. The problem is, the other stuff drawn to the canvas now flickers. If I don't do the rotation the regular drawn sprites work great. I think I am close but I just don't know what piece I am missing. Below is my two "Draw_Sprite" methods, one just draws the previously resource loaded bitmap to the canvas passed in. The other one, does some rotation the best I know how to rotate the sprite by so x many degrees..and then draw it. If I have a nice game loop that draws several objects, one type is the rotated kind. Then the non-rotated sprites flicker and yet the rotated sprite never does. Though if I draw the non-rotated sprites first, all is well, but then the Z-Ordering could be messed up (sprites on top of UI elements etc)... The method definitions:
/*************************************************
* rotated sprite, ignore the whatever, its for ease of use and testing to have this argument list
* #param c canvas to draw on.
* #param whatever ignore
* #param rot degrees to rotate
* #return
*/
public int Draw_Sprite(Canvas c, int whatever, int rot) {
//rotating sprite
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
Matrix orig = c.getMatrix();
mMatrix = orig;
orig.setTranslate(0, 0);
orig.postRotate(rot, x+width/2, y+height/2);
c.setMatrix(orig);
c.drawBitmap(images[curr_frame], src, dst, null);
c.setMatrix(mMatrix); //set it back so all things afterwards are displayed correctly.
isScaled=false;
return 1;
}
/********************************************************
* draw a regular sprite to canvas c
* #param c
* #return
*/
public int Draw_Sprite(Canvas c) {
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
c.drawBitmap(images[curr_frame], src, dst, null);
isScaled=false;
return 1;
}
And now the usage:
void onDraw(Canvas c)
{
canvas.drawRect( bgRect, bgPaint); //draw the background
//draw all game objects
// draw the normal items
for (GameEntity graphic : _graphics) {
graphic.toScreenCoords((int)player_x, (int)player_y);
if(graphic.getType().equals("planet")) //draw planets
graphic.Draw_Sprite(canvas); //before the rotation call draws fine
else
{
//rotate all space ships every frame so i see them spinning
//test rotation
mRot +=5;
if(mRot>=360)
mRot=0;
graphic.Draw_Sprite(canvas, 0, mRot); //yes function name will be better in future. this rotates spins draws fine
}
}
thePlayer.Draw_Sprite(canvas); //FLICKERS
drawUI(canvas);//all things here flickr
}
So it does do it, things after a call to a rotational draw are drawn correctly. But the problem is it flickrs. Now One could say I should just do all my non rotational stuff and save that last, but the zordering would be off.... suggestions as to how to tackle this issue of zordering or the flickering?
Just for the next guy who may read this. You can do this with only a few lines of code:
canvas.save();
canvas.rotate(rotation_angle, x + (widthofimage / 2), y + (heightofimage / 2));
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
Try using canvas.save() before the rotation and canvas.restore() after manipulation is complete.
When performing manipulations on the canvas in order to change the way an object is drawn you have to remember the manipulations set how the canvas handles origins etc... So if you translate or rotate the canvas, that will be set for the lifetime of that canvas. In order to avoid this you first call save, which saves a snapshot of the canvas matrix before you manipulate it, then you run all your changes, then call restore which will restore the canvas back to the last saved point. Otherwise all your changes build up and you get unintended results.