I am trying to implement my own map overlay for osmdroid (but I assume it is fairly similar to Google map overlays).
What I am trying to do is draw a plane, rotate it according to bearing and draw a speed vector (line ahead of the plane in the flight direction that shows where it will soon be).
The idea is that I draw the plane (et all) on a canvas "facing North", then rotate it according to flight direction and "merge" it with the overlay canvas (I tried drawing directly to the overlay canvas, but on rotate, it was rotating the map as well).
I have created a subclass of Overlay and overiden the onDraw method as follows:
#Override
protected void draw(Canvas c, MapView mapView, boolean shadow) {
if (location != null) {
Point locPoint = new Point();
GeoPoint locGeoPoint = new GeoPoint(location);
final Projection pj = mapView.getProjection();
pj.toMapPixels(locGeoPoint, locPoint);
this.drawPlane(c, locPoint, location.getBearing());
}
}
private void drawPlane(Canvas cs, Point ctr, float bearing) {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
Bitmap planeBM = Bitmap.createBitmap(cs.getWidth(), cs.getHeight(), Bitmap.Config.ARGB_8888);
planeBM.setDensity(cs.getDensity());
Canvas c = new Canvas(planeBM);
Rect r = new Rect();
//Point center = new Point(cs.getWidth() / 2, cs.getHeight() /2);
Point center = new Point(0, 0);
// Draw fuselage
r.left = center.x - PLANE_WIDTH / 2;
r.right = r.left + PLANE_WIDTH;
r.top = center.y - PLANE_SIZE / 3;
r.bottom = r.top + PLANE_SIZE;
c.drawRect(r, paint);
// Draw wing (REMOVED)
// Draw stabilizer (REMOVED)
// TODO Draw Speed vector
// "Merging" canvas
Matrix merge = new Matrix(cs.getMatrix());
//merge.setTranslate(0, 0);
//merge.setRotate(bearing, center.x, center.y);
cs.drawBitmap(planeBM, merge, paint);
cs.save();
}
Basically my plane never shows.
I assume this has to do with the matrix in the initial canvas which has large values (I assume these are sort of geographical coordinates).
It all seems to be consistent though (the plane location has large values as well consistent with the matrix).
I have tried a number of things :
drawing from the actual plane location (large values) : did not help;
setting the matrix of my new canvas with the overlay canvas matrix : did not help;
merging with a new "empty" matrix : did not help;
-...
I know that my image contains the plane (at least if I draw from 0,0 or the new canvas center as I saved it to the SD to check...
In case this is usefull to someone, I finally found a solution.
Basicaly I was trying to do too much at the same time.
Using setPostRotate instead of setRotate on the merge matrix did solve the issue (that and goig bac to the drawing board for the correct translation parameters).
Using the below as a merge matrix worked:
// "Merging" canvas
Matrix merge = new Matrix();
merge.setTranslate(loc.x - pCenter.x, loc.y - pCenter.y);
merge.postRotate(bearing, loc.x, loc.y);
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.
I am using the Projection class of Google maps to obtain the screen location for points in a polygon with the aim of creating a GroundOverlay and drawing a custom line style as a Path. The problem is when the camera is rotated the toScreenLocation method is returning incorrect results.
GroundOverlayOptions overlayOptions = new GroundOverlay();
Projection projection = map.getProjection();
LatLngBounds screenBounds = projection.getVisibleRegion().latLngBounds;
imageWidth = projection.toScreenLocation(screenBounds.northeast).x;
imageHeight = projection.toScreenLocation(screenBounds.southwest).y;
_overlayOptions.positionFromBounds(screenBounds);
Bitmap bmp = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
Path path = new Path();
List<LatLng> points = polygon.getPoints();
Point startPos = projection.toScreenLocation(points.get(0));
path.moveTo(startPos.x, startPos.y);
for (int i = 1; i < points.size(); i++) {
Point nextPos = projection.toScreenLocation(points.get(i));
path.lineTo(nextPos.x, nextPos.y);
}
path.lineTo(startX, startY);
canvas.drawPath(path, paint);
BitmapDescriptor bmpDesc = BitmapDescriptorFactory.fromBitmap(bmp);
overlayOptions.image(bmpDesc);
map.addGroundOverlay(overlayOptions);
The image below illustrates the problem. When the map is orientated North the blue dashed line renders where it is supposed to but when the bearing is changed the result of toScreenLocation gives me warped coordinates.
I've attempted to apply a matrix rotate transformation on the bitmap
Matrix rotationMatrix = new Matrix();
rotationMatrix.postRotate(map.getCameraPosition().bearing, imageWidth / 2, imageHeight / 2);
and I have also tried using trigonometry to rotate the points. Neither approach was successful.
So my question is how do you get the screen location of a LatLng that is independent of the camera orientation?
I think the screen locations are correct. But you do not use them to paint directly into the screen, but into a bitmap, which is by default oriented north up. When you apply this bitmap as GroundOverlay it is rotated together with the map and thus the locations do no longer fit.
See also the Documentation:
Bearing
The amount that the image should be rotated in a clockwise direction. The center of the rotation will be the image's anchor. This is optional and the default bearing is 0, i.e., the image is aligned so that up is north.
I am not sure, but I think you have to use the current rotation of the map as negative bearing for your GroundOverlay to compensate the effect. And I am also not sure, how a tilt of the map can or must be compensated. I would just try. The anchor point should most probably be the center of the image.
But in any case, I think it's important to realize that the screen locations are not really wrong. They just have the real screen as reference system, which is different from the bitmaps system after it is rotated.
I have been battling with trying to draw a bitmap and then highlighting a region on it with a rectangle. Originally, I was drawing a bitmap with alpha black in paint to make image darker and then on top drawing original bitmap in a region creating effect of highlight. I discovered that largest slowdown was because of alpha in Paint. So I have reworked the code and ended up with following in my draw thread:
private synchronized void drawSquare(int xStart, int yStart, int xEnd, int yEnd) {
Canvas c = holder.lockCanvas();
if(c != null) {
// Draw the background picture on top with some changed alpha channel to blend
Paint paint = new Paint();
paint.setAntiAlias(true);
if(bg != null && cWidth > 0 && cHeight > 0) {
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.DIFFERENCE);
c.drawBitmap(bg, gTransform, blackSqr); // Draw derker background
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.REPLACE);
c.drawBitmap(bg, gTransform, paint); ///draw original in selection
c.clipRect(0, 0, cWidth, cHeight,Region.Op.REPLACE);
}
Matrix RTcorner = new Matrix();
RTcorner.setRotate(90);
RTcorner.postTranslate(xEnd + 13, yStart - 13);
Matrix RBcorner = new Matrix();
RBcorner.setRotate(180);
RBcorner.postTranslate(xEnd + 13, yEnd + 13);
Matrix LBcorner = new Matrix();
LBcorner.setRotate(270);
LBcorner.postTranslate(xStart - 13, yEnd + 13);
// Draw the fancy bounding box
c.drawRect(xStart, yStart, xEnd, yEnd, linePaintB);
// Draw corners for the fancy box
c.drawBitmap(corner, xStart - 13, yStart - 13, new Paint());
c.drawBitmap(corner, RBcorner, new Paint());
c.drawBitmap(corner, LBcorner, new Paint());
c.drawBitmap(corner, RTcorner, new Paint());
}
holder.unlockCanvasAndPost(c);
}
So this clips out my selection area, I draw with paint that has this code to make it darker.
blackSqr.setColorFilter(new LightingColorFilter(Color.rgb(100,100,100),0));
And in the area inside the clip I draw my original bitmap. It works. But I am not happy with response time. After profiling Bitmap is what takes the longest. I have scaled the bitmap to the size of the screen already so it's drawing 300x800-ish image. The biggest resource hog seems to be the Lighting effect. Because when I turn it off I get decent response time.
So I was wondering if I have missed anything to improve how quickly bitmap is drawn, maybe caching? Or am I just stuck with this because I want darker image and actually should rethink the "highlighting/selection" altogether? Why is is so expensive to draw a bitmap with alpha colour in 2D image?
if I understand what you want, you want a rectangle (with rounded corners) to highlight a part from another image.
if it is that, then I would use an image with the square wit draw9patch and use it as a floating view over the image view
RelativeLaoyut (Image container)
+- ImageView (your actual image)
+- view (it has the square as a background, and you only have to move it to the area you want to highlight)
I'm sorry, I'm not good explaining myself.
For anyone that is interested, perhaps facing similar problem. This solution applies to my particular situation, but I have a separate background bitmap with darkened pixels manually set using:
for(int i = 0; i < cWidth; i++){
for(int j = 0; j < cHeight; j++){
int c = bg2.getPixel(i, j);
float mult = 0.15f;
int r = (int) (Color.red(c) * mult);
int g = (int) (Color.green(c) * mult);
int b = (int) (Color.blue(c) * mult);
bg2.setPixel(i, j, Color.rgb(r, g, b));
}
}
Then use the bg2 to draw main part and the original (not darkened) for the clip rectangle of the selection. There is a bit of overhead for creating and maintaining the second bitmap but the draw speed and response time is quick and smooth in comparison to bitmaps with alpha.
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 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();
}