For my live wallpaper I use the following code (called by a Runnable) to draw each frame. Each time it is called, I fill the current canvas with a solid color and draw a background bitmap (bg_image has been resized to perfectly fit the screen). I then call drawParticles(c), which simply uses c.drawCircle(...) a bunch of times drawing particles all over the canvas.
In the live wallpaper preview mode, this code works great. However, when I actually set this as my live wallpaper it flickers and seems to not clear the canvas before drawing. Let me 'splain:
Frame 1: The bitmap is drawn and circles are overlaid.
Frame 2: The bitmap is drawn and circles are overlaid (based on my rough understanding, there are two canvases that are drawn on and posted alternately for efficiency).
Frame 3: The canvas is not being cleared! This frame includes the new positions of each drawn circle as well as the circles from Frame 1!.
Frame 4: Includes the new positions of each drawn circle as well as the circles from Frame 2!
The end effect is that the circles leave "trails" all over the screen that flicker between (I believe) the two alternating canvases. Why, based on my code below, isn't my canvas being cleared each frame? Again, this works fine during preview mode but not when it is actually set as my live wallpaper. It's also worth noting that this flickering problem only occurs if I am drawing a bitmap; if the background is just a solid color, this problem never arises.
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
c.drawColor(Color.BLACK);
c.drawBitmap(bg_image, 0, 0, null);
fluid.drawParticles(c);
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(mDrawRunnable);
mHandler.postDelayed(mDrawRunnable, 1000/targetFramerate -(System.currentTimeMillis() - mLastTime));
}
My guess is that your animation is too complex to keep up with your frame rate. If you slow the frame rate way down, do you still have the problem? Also, if you are drawing an opaque background bitmap at each frame, you do not need to paint the screen black first--that just wastes time, and limits your maximum frame rate.
I'm guessing that the problem does not happen in preview mode because more of the phone's resources are focused on you, whereas once the wallpaper is set, more stuff is going on in the background.
Related
I have a set of small images. If I draw these images individually on canvas, the draw quality is significantly low, compared to the case where I draw them on a screen size large bitmap and draw that bitmap on the canvas. Specially the lines get distorted. See the below (right side).
From the code below, the canvas also supports zooming (scaling). This issue occurs on small scale factors.
Question is how to improve the draw quantity of multiple small images to the standard of large image.
This is a code of multiple bitmaps drawn on canvas
canvas.scale(game.mScaleFactor, game.mScaleFactor);
canvas.translate(game.mPosX, game.mPosY);
for (int i = 0; i < game.clusters.size(); i++) {
Cluster cluster = game.clusters.get(i);
canvas.drawBitmap(cluster.Picture, cluster.left,
cluster.top, canvasPaint);
}
This is the code for single bitmap, game.board is a screen size image which has all the small bitmaps drawn on.
canvas.scale(game.mScaleFactor, game.mScaleFactor);
canvas.translate(game.mPosX, game.mPosY);
canvas.drawBitmap(game.board, matrix, canvasPaint)
The paint brush has following properties set.` All bitmaps are Bitmap.Config.ARGB_8888.
canvasPaint.setAntiAlias(true);
canvasPaint.setFilterBitmap(true);
canvasPaint.setDither(true);`
I can think of a couple, depending on you you are drawing the borders of the puzzle pieces.
The problem you are having is that when the single image is scaled, the lines are filtered with the rest of the image and it looks smooth (the blending is correct). When the puzzle is draw per-piece, the filtering reads adjacent pixels on the puzzle piece and blends them with the piece.
Approach 1
The first approach (one that is easy to do) is to render to FBO (RTT) at the logical size of the game and then scale the whole texture to the canvas with a fullscreen quad. This will get you the same result as single because the pixel blending involves neighboring pieces.
Approach B
Use bleeding to solve the issue. When you cut your puzzle piece, include the overlapping section of the adjacent pieces. Instead of setting the discarded pixels to zero, only set the alpha to zero. This will cause your blending function to pickup the same values as if it were placed on a single image. Also, double the lines for the border, but set the outside border alpha to zero.
Approach the Final
This last one is the most complicated, but will be smooth (AF) for any scaling.
Turn the alpha channel of your puzzle piece into a Signed Distance Field and render using a specialized shader that will smooth the output at any distance. Also, SDF allows you to draw the outline with a shader during rendering, and the outline will be smooth.
In fact, your SDF can be a separate texture and you can load it into the second texture stage. Bind the source image as tex unit 0, the sdf puzzle piece cutout(s) on tex unit 1 and use the SDF shader to determine the alpha from the SDF and the color from tex0, then mix in the outline as calculated from the SDF.
http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
https://github.com/Chlumsky/msdfgen
http://catlikecoding.com/sdf-toolkit/docs/texture-generator/
SDF is generated from a Boolean map. Your puzzle piece cutouts will need to start as monochrome cutout and then turned into SDF (offline) using a tool or similar as listed above. Valve and LibGDX have example SDF shaders, as well as the tools listed above.
im working on an app, that displays large(around 2000x2000px) bitmap in imageview. This image has to be that large since user can pinch to zoom it in order to see some details. App has to be able to draw circles on that image, and also to display image alone, without circles on it. I was using 2 layers but the problem is memory since 2k x 2k px is around 16mb of memory, and creating another bitmap(another 16mb), just to draw a few circles, is pointless in my opinion. Is there any way, that you can draw simple primitives on image, and also be able to display it without primitives(circles in my case)?
Maybe somehow to store only modified pixels or sth?
Thanks!
You don't need to make another 2000x2000 Bitmap to draw those circles on. Just 'prerender' a circle, and then choose where you draw it.
I'm working under the assumption that you're drawing your 'big' image on a Canvas, since you have zooming features etc.
If you're not, you'll need to override your SurfaceView's onDraw(Canvas canvas) method so that you can access the SurfaceView Canvas. I won't go into depth about that part since again I'm assuming you have it, but if not the implementation of that function would look like this:
//Overriding SurfaceView onDraw(Canvas canvas)
#Override
protected void onDraw(Canvas surfaceCanvas) {
if(canvas == null) return; //No Canvas? No point in drawing then.
surfaceCanvas.drawColor(Color.BLACK);
//Draw your 'big' image on the SurfaceView Canvas
insertYourBigImageDrawingFunctionHere(surfaceCanvas);
//Now draw your circles at their correct positions...
insertCircleDrawingFunctionHere(surfaceCanvas);
}
Now that you have access to the SurfaceView Canvas, you can choose precisely how things are drawn on it. Like circles for example...
I want to draw your attention to the multiple Canvas' being used below (surfaceCanvas vs. circleCanvas). I once thought that Canvas was a kind-of 'one Canvas for the whole app/activity' implementation, but it isn't. You are free to create Canvas' as you please. It is merely an instance of a tool to draw onto Bitmaps. This was a HUGE revelation for me, and gave me much more robust control over how Bitmaps are composed.
public void myCircleDrawingFunction(Canvas surfaceCanvas){
//Make a new Bitmap for your circle
Bitmap.Config conf = Bitmap.Config.ARGB_4444;
tinyCircleBMP = Bitmap.createBitmap(10,10, conf);
//Make a new canvas using that Bitmap as the source...
Canvas circleCanvas = new Canvas(cacheBmp);
//Now, perform your drawing on the `Canvas`...
Paint p = new Paint();
circleCanvas.drawCircle(5, 5, 5, p);
//Now the `Bitmap` has a circle on it, draw the `Bitmap` on the `SufaceView Canvas`
surfaceCanvas.drawBitmap(tinyCircleBMP, 10, 10, p);
//Replace the '10's in the above function with relevant coordinates.
}
Now obviously, your circles will zoom/pan differently to your 'big' image, since they are no longer being drawn at the same size/position of the 'big' image. You will need to consider how to translate the positions of each circle taking into account the current scale and position of the 'big' image.
For example, if your image is zoomed in to 200%, and a circle is supposed to appear 100px from the left of the big image, then you should multiply the pixel values to take into account the zoom, like this
(PsuedoCode):
drawCircleAtX = Bitmap.left * BitmapZoomFactor
If you are using the canvas API (if not I would suggest to)? if so you are just draw your image on the canvas and then the primitive shapes on top of the same canvas before display. This way you just keep a reference of the circles position in some basic data types and scale them as the user moves around and zooms, so you know where to draw them each frame.
I'm trying to 'take a photo' of both the camera preview, and an overlayed GLSurfaceView.
I have the camera preview element working, via camera.takePicture() and PictureCallback(), but now need to either include the GLSurfaceView elements, or capture the current screen seperately and merge the two bitmaps into one file.
I have tried to grab an image from the surfaceView using the code below, but this just results in a null bitmap.
public Bitmap grabImage() {
this.setDrawingCacheEnabled(true);
Bitmap b = null;
try {
b = this.getDrawingCache(true);
if (b==null) {
b = this.getDrawingCache(true);
}
} catch (Exception e) {
e.printStackTrace();
}
this.setDrawingCacheEnabled(false);
return b;
}
I would appreciate any thoughts/ snippets on this. Many thanks in advance.
I've done something similar and it was a bit convoluted but not too terrible.
In my case I'm using camera preview frame which I decode to a bitmap. Then get a canvas from that bitmap pass it to call to draw() on the views (surfaceview or otherwise) that I want drawn over top of the picture.
Bitmap bm;
MySurfaceViewImpl sv;
Canvas c = new Canvas(bm);
sv.draw(c);
You will need to use your own View implementation to handle the fact that the canvas size is going to change and you'll need to rescale things between the calls to draw() that happen in the normal running of your app and the when you call it manually as the canvas from the picture size is almost certainly going to be different than what's being drawn to the screen.
Also, the primary reason I'm using preview frames rather than captured pictures is due to memory limits. Very few phones support smallish sized pictures but all support reasonable sizes for preview frames. Getting a full size camera picture into a bitmap is probably too much memory. On devices with less than 24MB heap, I'm ok with about a 600 x 480 image and about 4 views that get drawn on top of that but it gets tight.
In your case, you'll probably need to scale the bitmap down to be able to pass a canvas from it to a view.
Good luck!
I'm not sure I'm doing this the "right" way, so I'm open to other options as well. Here's what I'm trying to accomplish:
I want a view which contains a graph. The graph should be dynamically created by the app itself. The graph should be zoom-able, and will probably start out larger than the screen (800x600 or so)
I'm planning on starting out simple, just a scatter plot. Eventually, I want a scatter plot with a fit line and error bars with axis that stay on the screen while the graph is zoomed ... so that probably means three images overlaid with zoom functions tied together.
I've already built a view that can take a drawable, can use focused pinch-zoom and drag, can auto-scale images, can switch images dynamically, and takes images larger than the screen. Tying the images together shouldn't be an issue.
I can't, however, figure out how to dynamically draw simple images.
For instance: Do I get a BitMap object and draw on it pixel by pixel? I wanted to work with some of the ShapeDrawables, but it seems they can only draw a shape onto a canvas ... how then do I get a bitmap of all those shapes into my view? Or alternately, do I have to dynamically redraw /all/ of the image I want to portray in the "onDraw" routine of my view every time it moves or zooms?
I think the "perfect" solution would be to use the ShapeDrawable (or something like it to draw lines and label them) to draw the axis with the onDraw method of the view ... keep them current and at the right level ... then overlay a pre-produced image of the data points / fit curve / etc that can be zoomed and moved. That should be possible with white set to an alpha on the graph image.
PS: The graph image shouldn't actually /change/ while on the view. It's just zooming and being dragged. The axis will probably actually change with movement. So pre-producing the graph before (or immediately upon) entering the view would be optimal. But I've also noticed that scaling works really well with vector images ... which also sounds appropriate (rather than a bitmap?).
So I'm looking for some general guidance. Tried reading up on the BitMap, ShapeDrawable, Drawable, etc classes and just can't seem to find the right fit. That makes me think I'm barking up the wrong tree and someone with some more experience can point me in the right direction. Hopefully I didn't waste my time building the zoom-able view I put together yesterday :).
First off, it is never a waste of time writing code if you learned something from it. :-)
There is unfortunately still no support for drawing vector images in Android. So bitmap is what you get.
I think the bit you are missing is that you can create a Canvas any time you want to draw on a bitmap. You don't have to wait for onDraw to give you one.
So at some point (from onCreate, when data changes etc), create your own Bitmap of whatever size you want.
Here is some psuedo code (not tested)
Bitmap mGraph;
void init() {
// look at Bitmap.Config to determine config type
mGraph = new Bitmap(width, height, config);
Canvas c = new Canvas(mybits);
// use Canvas draw routines to draw your graph
}
// Then in onDraw you can draw to the on screen Canvas from your bitmap.
protected void onDraw(Canvas canvas) {
Rect dstRect = new Rect(0,0,viewWidth, viewHeight);
Rect sourceRect = new Rect();
// do something creative here to pick the source rect from your graph bitmap
// based on zoom and pan
sourceRect.set(10,10,100,100);
// draw to the screen
canvas.drawBitmap(mGraph, sourceRect, dstRect, graphPaint);
}
Hope that helps a bit.
I would like to animate movement on a SurfaceView . Ideally I would like to also be notified once the animation had finished.
For example:
I might have a car facing North. If I wanted to animate it so that it faces South for a duration of 500ms, how could I do that?
I am using a SurfaceView so all animation must be handled manually, I don't think I can use XML or the android Animator classes.
Also, I would like to know the best way to animate something continuously inside a SurfaceView (ie. a walk cycle)
Rotating images manually can be a bit of a pain, but here's how I've done it.
private void animateRotation(int degrees, float durationOfAnimation){
long startTime = SystemClock.elapsedRealtime();
long currentTime;
float elapsedRatio = 0;
Bitmap bufferBitmap = carBitmap;
Matrix matrix = new Matrix();
while (elapsedRatio < 1){
matrix.setRotate(elapsedRatio * degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
//draw your canvas here using whatever method you've defined
currentTime = SystemClock.elapsedRealtime();
elapsedRatio = (currentTime - startTime) / durationOfAnimation;
}
// As elapsed ratio will never exactly equal 1, you have to manually draw the last frame
matrix = new Matrix();
matrix.setRotate(degrees);
carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
// draw the canvas again here as before
// And you can now set whatever other notification or action you wanted to do at the end of your animation
}
This will rotate your carBitmap to whatever angle you specify in the time specified + the time to draw the last frame. However, there is a catch. This rotates your carBitmap without adjusting its position on screen properly. Depending on how you're drawing your bitmaps, you could end up with your carBitmap rotating while the top-left corner of the bitmap stays in place. As the car rotates, the bitmap will stretch and adjust to fit the new car size, filling the gaps around it with transparent pixels. It's hard to describe how this would look, so here's an example rotating a square:
The grey area represents the full size of the bitmap, and is filled with transparent pixels. To solve this problem, you need to use trigonometry. It's a bit complicated... if this ends up being a problem for you (I don't know how you're drawing your bitmaps to the canvas so it might not be), and you can't work out the solution, let me know and I'll post up how I did it.
(I don't know if this is the most efficient way of doing it, but it works smoothly for me as long as the bitmap is less than 300x300 or so. Perhaps if someone knows of a better way, they could tell us!)
Do you want multiple independent animated object? If so, then you should use a game loop. (One master while loop that incrementally updates all game objects.) Here's a good discussion on various loop implementations. (I'm currently using "FPS dependent on Constant Game Speed" for my Android game project.)
So then your Car will look something like this (lots of code missing):
class Car {
final Matrix transform = new Matrix();
final Bitmap image;
Car(Bitmap sprite) {
image = sprite; // Created by BitmapFactory.decodeResource in SurfaceView
}
void update() {
this.transform.preRotate(turnDegrees, width, height);
}
void display(Canvas canvas) {
canvas.drawBitmap(this.image, this.transform, null);
}
}
You only need to load your bitmap once. So if you have multiple Cars, you may want to give them each the same Bitmap object (cache the Bitmap in your SurfaceView).
I haven't got into walk animations yet, but the simplest solution is to have multiple Bitmaps and just draw a different bitmap each time display is called.
Have a look at lunarlander.LunarView in the Android docs if you haven't already.
If you want to be notified when the animation is complete, you should make a callback.
interface CompletedTurnCallback {
void turnCompleted(Car turningCar);
}
Have your logic class implement the callback and have your Car call it when the turn's complete (in update()). Note that you'll get a ConcurrentModificationException if you are iterating over a list of Cars in update_game() and you try to remove a Car from that list in your callback. (You can solve this with a command queue.)