I've made a simple whiteboard on my Android app, where the user can draw something. I want to add an Undo feature so that he will be able to go back to his previous action.
On my Touch Start event, I've added the following code which basically adds the current canvas to an ArrayList and create a new one to avoid same reference.
previousDrawing.add(this.canvas);
this.canvas = new Canvas(this.bitmap);
Then, in my undo method, I've added the following code:
if (previousDrawing.size() > 0)
{
// Remove last
this.canvas = previousDrawing.remove(previousDrawing.size() - 1);
this.canvas.setBitmap(this.bitmap);
}
It doesn't work at all. I mean, I'm able to draw on my canvas using this.canvas.drawPath(this.path, this.paint); but not to save and restore a previous canvas.
Could you help me to do that ?
Thanks in advance.
EDIT :
I've also tryied to use the saveLayer method. But when I save, I'm no longer able to draw on the canvas. Is this a normal behaviour ?
Try drawBitmap instead of setBitmap
this.canvas.drawBitmap(this.bitmap, 0, 0, null);
Edit
if (previousDrawing.size() > 0)
{
// Remove last
this.bitmap = previousDrawing.get(previousDrawing.size() - 1);
this.canvas.drawBitmap(this.bitmap, 0, 0, null);
previousDrawing.remove(previousDrawing.size() - 1);
}
Reference link
canvas doesnt hold pixels. Bitmap does. So it is useless to save and restore canvas. You can save and restore bitmap, but it is very memory consuming operation.
You better log user drawing actions. To go back in history you should redraw all actions from the beggining except the last one. To make it faster you can keep several key history bitmaps. Because redrawing all from the beggining is slow...
Related
I'm trying to create my first android app, so I'm really new to android development. I'm trying to make a nice background image for my app. I made an image that is the exact dimensions of the device, but when I try to load it, i get the error "Failed to allocate a 218748 byte allocation with 217220 free bytes and 212KB until OOM" Here's my code:
#Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
protected void drawBackground(Canvas canvas) {
Bitmap background = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.background2));
canvas.drawBitmap(background, 0, 0, null);
I've tried scaling it down, which works, but then it doesn't fill the entire screen. How do I resolve this issue?
You should recycle bitmap after canvas.drawBitmap(background, 0, 0, null);, or create bitmap once, in the constructor, and then use it, it is very costly to create bitmap each time in the onDraw method, it even costly to create simple objects in the onDraw, you shuold avoid from it, android:largeHeap="true" and System.gc() can't help in this case. Read this
Custom Drawing
Creating objects ahead of time is an important optimization. Views are redrawn very frequently, and many drawing objects require expensive initialization. Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish.
There is blog that will help you to detect and prevent Out of memory issue. I wrote this based on a real problem I faced and here is the link
If you got stuck anywhere in the tutorial please let me know we can discuss and if you want to add something please suggest.
I am drawing multiple text on the canvas in my app. For two text its working fine but drawing multiple text/bitmap at the same times it's a big headache for everyone.it takes all GPU process and it hangs at some point of time especially when you draw large bitmap/text.
Is there any way to save previous drawn things to single bitmap?
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (CustomTextview.GetDB().size() != 0) {
for (CustomTextview textview : CustomTextview.GetDB()) {
Bitmap b= drawasbitmap(textview.text,textview.X,textview,Y) //creating a bitmap for view
canvas.drawBitmap(b, textview.X, textview.Y, textview.paint);
b.recycle();
}
canvas.restore();
}
}
Lets say i have 5 bitmaps/text in my app.it should draw 5 text at the same time.
If it's 5 bitmap the memory will go away,is there any way to save all previous view (Bitmap-which is drawn before adding the new one )to one Bitmap ,or can i use drawing cache?
P.S- i have multiple views in my app- i want to draw the recent one for editing purpose .everthing should be saved to temp bitmap and shown to use that views are out there.when ever user clicks that particular region that view get selected called in onDraw
My code here receives a value for bmpX and bmpY and uses them to draw a bitmap at that location on the screen. This works exactly as I want it to, but I would rather use the canvas.translate() function to handle the movement of the image, rather than just canvas.drawBitmap(), because I will be applying other draw elements to the canvas and I want them all to move the same amount and direction.
My question is: where can I move the canvas.drawBitmap() in the code so that it just draws onto the canvas at the start, but it is moved any time after that using canvas.translate() instead? Anywhere I place the code still freezes the image at that point where I draw it, regardless of how much I change the position of canvas.translate(). I'm imaging its possible to "stick" the image to the canvas, so to speak, and then move the canvas, which also moves the image with it.
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
if(!surfaceHolder.getSurface().isValid()) {
continue;
}
canvas = surfaceHolder.lockCanvas();
canvas.drawRGB(255, 255, 255);
canvas.drawBitmap(bmp, bmpX-(bmp.getWidth()/2),
bmpY-(bmp.getHeight()/2), null);
//canvas.translate(bmpX, bmpY);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
I hope I explained my problem clearly enough. Thanks!
Answering my own question here for clarity, afetr info I got offline. UI cannot be done on a thread like this, I needed to be using an android UI thread instead. runOnUIUpdatethread and async tasks
Need a little help here : I'm out of ideas now...
Here's what I need to do :
Render "base" image [ it is created from an ARGB.8888 byte array ]
user clicks 2 points on the screen; I need to perform the the pixel manipulation on a region of pixels around the path taken from one point to another... [ I need to calculate a squre block of pixel for each pixel on the whole path ].
display the modification of the image as the code progresses in animation form.
I am able to display the whole path; I am able to calculate & manipulate the pixels properly .. But what I am unable to do is show the animation as my code is progressing on the path... with the current implementation I am able to display the whole calculated path at the end ...
public void onDraw(Canvas canvas){
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
canvas.drawBitmap(base,0,0,p);
traverse clickEvent1.x -> clickEvent2.x
traverse for clickEvent1.Y -> clickEvent2.Y
{
newBitMap = calculateNewBitMap(base)
// I nee to redraw Canvas with (newBitMap)
// canvas.drawBitMap(newBitMap);
//Doesn't work
//postInvalidate();
//invalidate()
//AnimationDrawable.addFrame(newBitMap)
// I am not calling start here
//but just wanted to let you know that I do call animation start to display the frames stored in it
// animation.start();
}
// obviously wouldn't work here As it is already out of the loop
//invalidate();
}
Please NOTE :
The newBitMap image is generated at runtime, it would not be available to me beforehand...
I tried invalidate() in the loop as well ; but it would only draw the cumulative result after the whole loop has traversed and not the intermediate states of the newBitMaps.
Performance is of critical importance + I am dealing with HUGE image sizes .. so please keep that in mind as well ... if I create multiple bitmaps for temporary storing the JVM crashes due to "OutofMemory" ..
I tried storing the new Images in "AnimationDrawable" form as well; but tht's not solving the problem as well....
AnimationDrawable animDrawable = new AnimationDrawable();
Drawable frame1 = new BitmapDrawable(newCaclBitMap);
animDrawable.addFrame(frame1, 250);
Thanks for any pointers / suggestions ..
It seems like what you are doing is running all of your drawing logic in a single call to onDraw() when I think what you want is to have onDraw() called once for each frame of your animation.
So, instead of something like this in your trace:
onDraw()
drawFrame()
drawFrame()
drawFrame()
...
You would have this:
onDraw()
drawFrame()
onDraw()
drawFrame()
onDraw()
drawFrame()
...
The CubeLiveWallpaper example has an example of this type of thing.
Animation objects are used to animate View objects. What you want to do is animate a canvas. This is more complicated but potentially more powerful. Essentially you need to derive an equation that will govern the movement of you drawing as a function of time. When the animation starts you get the current time stamp and then in you onDraw method you draw what the canvas should look like at that point in time. Basically you have to draw every step.
I'm new to Android.
I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.
The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.
A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.
Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?
I should welcome any advice!
Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?
I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.
The entire onDraw() method now reads as follows:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}
The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.
That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?
I had this problem too,
I did the drawing like this:
Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);
int ScrollPosX , ScrollPosY // (calculate these with the onScrollEvent handler)
void onCreate()
{
BigCanvas.SetBitmap(BigBitmap);
}
onDraw(Canvas TargetCanvas)
{
// do drawing stuff
// ie. BigCanvas.Draw.... line/bitmap/anything
//draw to the screen with the scrolloffset
//drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
}
for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).
I Hope this gives some more insight.
Continuation of reply to Viktor ...
In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");`
doDrawing();
}
and to call this method from other places in my program instead of doDrawing().
However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on.
reDrawBuffer() now reads:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
}`
and the Handler code (which is nested within my View class) is:
private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();
class timeCheckHandler extends Handler {
#Override
public void handleMessage(Message msg) {
doDrawing();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}`
No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.
<activity android:name=".ANote"
android:label="#string/app_name"
android:configChanges="orientation|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This method can be used to get a notification when the orienation is changed:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
prt("onConfigurationChanged: "+newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
prt(" PORTRAIT");
} else {
prt(" LANDSCAPE");
}
} // end of onConfigurationChanged
prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.
Correct me if I am wrong!