I am trying to understand the basics of moving objects on the screen etc. I have a bitmap and I am moving that bitmap on the screen. How can I keep the bitmap inside the rectangle and still move it. I would like to put the bitmap in a rectangle as that will help in collision detection with other objects. Below is my code so far.
Thanks
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.yellow_ball);
x1 = 0;
y1 = 100;
#Override
protected void onDraw(Canvas canvas) {
if(x1 < canvas.getWidth()){
x1 += 5;
}
else{
x1 = 0;
}
canvas.drawBitmap(bitmap1, x1, y1, null);
}
You have the basic idea.
First, decide where you want to move the object to.
Then compare the new X coordinate to the rectangle's left and right bounds,
and if the new X coordinate is outside of the rectangle's bounds, reset the coordinate to the bound that it is exceeding.
Then, do the same for the Y coordinate.
Finally, move your bitmap to the adjusted X and Y coordinates.
I wrote code to do this a while back, see https://github.com/rfreedman/android-constrained-drag-and-drop-view for an example.
Related
I draw a path and marker through certain coordinates on a canvas that shows an image on the screen.At a certain coordinate point on the picture.
Path path = new Path();
if (!isReady()) {
return;
}
else if (routeList != null && nodeList != null) {
if (!routeList.isEmpty() && !nodeList.isEmpty()) {
// draw route
for (int i = 0; i < routeList.size() - 1; i++) {
PointF goal3 = new PointF(nodeList.get(routeList.get(i)).x,
nodeList.get(routeList.get(i)).y);
PointF goal4 = new PointF(nodeList.get(routeList.get(i + 1)).x,
nodeList.get(routeList.get(i + 1)).y);
sourceToViewCoord(goal3, vPin5);
sourceToViewCoord(goal4, vPin6);
path.moveTo(vPin5.x, vPin5.y);
path.lineTo(vPin6.x, vPin6.y);
//Draw path on canvas certain coordinate points
canvas.drawPath(path, paint);
}
PointF goal1 = new PointF(nodeList.get(routeList.get(0)).x,
nodeList.get(routeList.get(0)).y);
sourceToViewCoord(goal1, vPin3);
float vX1 = vPin3.x - (location_img.getWidth() / 2);
float vY1 = vPin3.y - location_img.getHeight() / 2;
//Draw marker certain coordinate point
canvas.drawBitmap(location_img, vX1, vY1, paint);
PointF goal2 = new PointF(nodeList.get(
routeList.get(routeList.size() - 1)).x,
nodeList.get(routeList.get(routeList.size() - 1)).y
);
sourceToViewCoord(goal2, vPin4);
float vX2 = vPin4.x - (pin_red.getWidth() / 2);
float vY2 = vPin4.y - pin_red.getHeight();
// Draw bitmap on canvas certain coordinate points
canvas.drawBitmap(pin_red, vX2, vY2, paint);
}
}
You can draw your path on another canvas (Bitmap) and only draw your path Bitmap according to your wish on the image canvas. As stated by Basile Perrenoud, the important thing is not to allocate your path Bitmap during the drawing. You could e.g. create it during the class instanciation and reuse it with a clean at every redraw.
The save and restore method only store the transformation matrix and the clip area of the canvas (See the doc). You can't remove pixels from a canvas, just draw on top of it. If you added your path on top of the image, you should make a call that just draws the image.
Note that it isn't a big deal performance-wise as long as you don't reload or recreate the bitmap.
My application takes an image from the camera, saves it and then displays it on an ImageView, but the next step is to place a circle on top of the displayed image when the user touches the screen, and then save the "modified image".
Kinda like a image editor if you wish, problem is I do not know where to begin with the image editing. I tried this
#Override
public boolean onTouch(View v, MotionEvent event) {
circleView.setVisibility(View.VISIBLE);
circleView.setX(event.getX()-125);
circleView.setY(event.getY()-125);
try{
Bitmap bitmap = Bitmap.createBitmap(relativeLayout.getWidth(),relativeLayout.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
mImageView.setImageBitmap(bitmap);
FileOutputStream output = new FileOutputStream(Environment.getExternalStorageDirectory());
bitmap.compress(Bitmap.CompressFormat.PNG,100,output);
output.close();
}catch(FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
return true;
}//ENDOF onTouch
What can I do to save the image?
It'd be helpful if you included a bit more information about what libraries and language you're using. From the #override I'll assume this is java on android?
As for how to create a circle - there are many techniques you could use and there are probably more than a few libraries that you can use to do this. However, we can keep it pretty simple by using functions on the Bitmap object's interface, namely getPixels and setPixels.
What you need to do is grab a rectangle of pixels in to pre-allocated buffer (using getPixels), then draw your circle in to this buffer and then write the buffer back using 'setPixels'.
Here's a simple (although not exactly efficient) method for drawing a circle in the buffer you'd get from 'getPixels' in javaish pseudocode (untested):
//Return the distance between the point 'x1, y1' and 'x2, y2'
float distance(float x1, float y1, float x2, float y2)
{
float dx = x2 - x1;
float dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
//draw a circle in the buffer of pixels contained in 'int [] pixels'
//at position 'cx, cy' with the given radius and colour.
void drawCircle(int [] pixels, int stride, int height, float cx, float cy, float radius, int colour)
{
for (int y = 0; y < height; ++y)
for (int x = 0; x < stride; ++x)
{
if (distance((float)x, (float)y, cx, cy) < radius)
pixels[x + y * stride] = colour;
}
}
This just asks the question, for each pixel, 'is the point 'x,y' inside the circle given by 'cx, cy, radius'?' and if it is, it draw a pixel.
More efficient approaches might include a scanline rasteriser that steps through the left and right sides of the circle, removing the need to do a costly 'distance' calculation for each pixel.
However, this 'implicit surface' approach is quite flexible and you can achieve a lot of effects with it. Other options might be to copy a pre made circle bitmap instead of creating your own on the fly.
You could also blend the 'colour' based on the fractional value of 'distance - radius' to achieve anti-aliasing.
I am developing an application for technical drawing and I need to add a few tools to draw lines, circles, rectangles and corners. Now I can draw lines and free hand drawing but I cannot draw circles, rectangles and corners. I found in lot of websites how to draw it but static, I mean, draw the shape in the position you pre-set or the position where you touch but I need to know how to draw, for example, a circle in the position I touch and make it bigger than I separate my fingers. I hope you understand what I mean.
You can have two variables x and y, then every time you touch the screen set x and y to that value, while drawing draw the circle with coordinates x and y.
If you are drawing and you just want to keep a painted circle, you can paint the circle and add it inside your canvas on x and y, then the next time you touch the screen a new circle will be painted on x and y and the old one will remain painted.
Are you using Canvas ? if so you can find out how to do this here (Canvas documentation) and here (Bitmap documentation). Depending on your situation you can create a new Bitmap and assign it to Canvas then draw on the Canvasand inside your bitmap you will have your desired circles and other shapes, on the next drawing frame, draw new shapes and the changes will remain.
Edit: In order to have dynamic radius follow this logic, when you touch the screen, set x and y to that point (the center of the circle), while moving the finger on the screen, calculate the radius comparing to x and y, when lifting your finger apply the drawing on the bitmap as told above.
Some code:
public boolean onTouchEvent(MotionEvent e)
{
switch (e.getAction())
{
case MotionEvent.ACTION_DOWN:
x = (int) event.getX();
y = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
//If I'm not wrong this is how to calculate radius,
//I'm at work and can't test this now, you can use your way
int distanceX = (int) event.getX() -x;
int distanceY = (int) event.getY() -y;
radius = sqrt(distanceX *distanceX + distanceY *distanceY);
break;
case MotionEvent.ACTION_UP:
//Draw circle inside your bitmap here
//This is like a flag to notify the program that no new shape is being drawn
x = -1;
break;
}
public void draw(Canvas canvas)
{
canvas.drawBitmap(myBitmap, 0, 0, null);
//A new shape is being drawn
if (x != -1)
//In here you place a switch to decide which shape you are drawing
//for this example i assume circle
canvas.drawCircle(radius, x, y, paint);
}
When you are lifting your finger the new circle should be painted on your bitmap so you don't have to add extra code for each new circle.
Edit2: I will add more code with the Bitmap and Canvas method i described.
Bitmap myBitmap;
Canvas myCanvas;
//Constructor
public myActivity(Bundle bundle) //or whatever your constructor is
{
myBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas(myBitmap);
}
Now anything you draw on "myCanvas" will be applied to "myBitmap", when ACTION_UP activates draw circle on "myCanvas" which is drawn on the draw function.
case MotionEvent.ACTION_UP:
myCanvas.drawCircle(radius, x, y, paint);
x = -1;
break;
I am trying to write an android app that lets me draw graphics on top of an image then scale and zoom the image with the graphics staying over the same place on the image that have been drawn on top of it while changing the graphics in real time.
However I have been having a lot of issues actually getting it to zoom in while maintaining the center of the image. I have written code where I have a thread that updates the image. Updates are passed in using a class that I created called "PendingUpdate" through an ArrayBlockingQueue. This update contains a desired zoom level which is supposed to be the ratio of the image pixels to the canvas pixels and an image center. However the following code makes it pan while I am zooming which confuses me.
//Scale the image
canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());
//Translate the image
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());
Thank you for the help!
Edit: here is the full function, I can also post PendingUpdate if this helps, however its just a data class.
private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
int iWidth = pendingUpdate.getImage().getWidth();
int iHeight = pendingUpdate.getImage().getHeight();
Matrix matrix = new Matrix();
canvas.drawColor(Color.BLACK);
//TODO: add scrolling functionality to this
if(pendingUpdate.getZoom()>0) {
//Scale the image
canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());
//Translate the image
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());
}else {
//matrix.postTranslate(canvas.getWidth()-iWidth/2, canvas.getWidth()-iHeight/2);
canvas.drawBitmap(pendingUpdate.getImage(),
(canvas.getWidth()-iWidth)/2,
(canvas.getHeight()-iHeight)/2, null);
}
//TODO: draw other stuff on canvas here such as current location
}
edit 2: This is how I finally got it to work, it was simply a matter of scaling it before translating it.
private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
int iWidth = pendingUpdate.getImage().getWidth();
int iHeight = pendingUpdate.getImage().getHeight();
canvas.drawColor(Color.BLACK);
//TODO: add scrolling functionality to this
if(pendingUpdate.getZoom()>0) {
//Scale the image
canvas.save();
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = (canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = (canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
//canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom(), (float)pendingUpdate.getCenter().getX(), (float)pendingUpdate.getCenter().getY());
canvas.scale(pendingUpdate.getZoom(),
pendingUpdate.getZoom(),
canvas.getWidth()/2,
canvas.getHeight()/2);
canvas.translate(-(float)imageTranslateX,
-(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), 0, 0, null);
canvas.restore();
}else {
//TODO: update this so it displays image scaled to screen and updates current zoom somehow
canvas.drawBitmap(pendingUpdate.getImage(),
(canvas.getWidth()-iWidth)/2,
(canvas.getHeight()-iHeight)/2, null);
}
//TODO: draw other stuff on canvas here such as current location
}
}
If I were you, I'd use the Canvas.scale(float sx, float sy, float px, float py) method which does exactly what you want.
However looking at your code I think you might be messing with too many transformations at once, which is harder to debug.
Always (and I mean always) call Canvas.save() and Canvas.restore() on the initial matrix you're getting in Canvas if you plan to alter it. This is because the Canvas that you get to draw on may be the canvas for e.g. the whole window with just clipping set to the boundaries of the control that is currently drawing itself.
Use matrix transformation method provided by the Canvas method and draw bitmap using the simplest invocation.
Following these two advices look at the whole View I have just made up, that scales the bitmap by a factor of 3 with point (16,16) set as the pivot (unchanged point - center of scaling). Tested - working.
public class DrawingView extends View {
Bitmap bitmap;
public DrawingView(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
}
#Override
protected void onDraw(Canvas canvas) {
float sx = 3;
float sy = 3;
float px = 16;
float py = 16;
canvas.save();
canvas.scale(sx, sy, px, py);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
}
i want my compass to spin like this
but my result is that:
the compass is going everywhere in my screen...
where is my problem please?this is my compass.java code:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GRAY);
int w = canvas.getWidth();
int h = canvas.getHeight();
int cw = w / 2;
int ch = h / 2;
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.compass);
canvas.translate(cw, ch);
if (mValues != null) {
canvas.rotate(-mValues[0]);
}
int cx = (mWidth - myBitmap.getWidth()) / 2;
int cy = (mHeight - myBitmap.getHeight()) / 2;
canvas.drawBitmap(myBitmap, cx, cy, null);
}
p.s.: i m sorry for the bad pictures but i really dont know how to explain my problem in english!Thanks
Since you have already translated to the center of the canvas, you may only need to offset the compass with its half width/height to center it. Try:
int cx = -myBitmap.getWidth() / 2;
int cy = -myBitmap.getHeight() / 2;
canvas.drawBitmap(myBitmap, cx, cy, null);
Also to get a good hang of transformations (translate, rotate), read The OpenGL Red book chapter 3, specifically the part Thinking about Transformations. While this is about OpenGL, you can use the knowledge for non-OpenGL transforms too.
EDIT:
Think in turtle logic. Your first translation takes your pencil to the center of your canvas. The rotation rotates your pencil. So now you could draw the compass exactly where the pencil is (no offsets), except that drawing the compass image is done starting from its top-left corner instead of its center. Therefore you need a last translation of (-compassWidth/2, -compassHeight/2). Note that this translation already occurs on the rotated x & y axes. Also note that you may pass 0/0 for cx/cy in drawBitmap if you manually apply that translation to the canvas itself.
So the full sequence is: translate to canvas center, rotate, translate negated to image center.
Don't decode the Bitmap in onDraw - do it when the view is created and reuse the Bitmap.
Make a Matrix and matrix.postRotate(mValues[0], half_width_of_bitmap, half_height_of_bitmap); and matrix.postTranslate(cw, ch);
Draw the bitmap with canvas.drawBitmap(bitmap, matrix, null);