I have a really simple task to do which is to draw a background image in a custom View. I create a bitmap and scale it to fit the width and height of the view. This makes the app way slower, like half as fast (I print out the value of time every 10 milliseconds to measure the speed of performance).
This is the code:
public class GView extends View {
int w, h;
Bitmap bg;
int time = 0;
boolean created = false;
public GView(Context context) {
super(context);
bg = BitmapFactory.decodeResource(getResources(), R.drawable.my_image);
new Timer();
}
#Override
public void onDraw(Canvas c) {
Paint p = new Paint();
if(!created) {
w=getWidth();
h=getHeight();
p.setColor(Color.RED);
p.setTextSize(40);
bg = Bitmap.createScaledBitmap(bg, w, h, false);
created = true;
}
if(created) {
time++;
c.drawBitmap(bg, 0,0,null);
c.drawText(time+"", (int)(w/4), (int)(h/4), p);
}
}
class Timer extends Handler {
private Timer() {
handleMessage(obtainMessage(0));
}
#Override
public void handleMessage(Message m) {
invalidate();
sendMessageDelayed(obtainMessage(0), 10);
}
}
}
FYI, the original image is 300x225. The screen res of my tablet that I scale the image to is 1280x800.
The thing is if I scale the background image to sth like (int)(.8*w), (int)(.8*h) or sth smaller or not scale the image at all, then it runs fast as expected.
I tried using ImageView and use setImageResource(R.drawable.my_image) but it was as slow though.
I thought drawing an image to fit the background should be very simple for any programming languages, but I've had this problem for a really long time even after a lot of searching. I hope somebody can give me a proper answer. I would really appreciate that.
createScaledBitmap() should not be done inside your onDraw() routine. It is quite slow because it uses a pixel averaging routine to "smooth" out the stretched bitmap. Create your stretched bitmap outside of the gui thread and then just draw it (canvas.drawBitmap) in your onDraw() method. The filter parameter can be used to create a "nearest neighbor" scaling if set to false. This will look "blocky", but the processing is much faster.
public static Bitmap createScaledBitmap
(Bitmap src, int dstWidth, int dstHeight, boolean filter)
Related
currently I am trying to make an animation where some fish move around. I have successfully add one fish and made it animate using canvas and Bitmap. But currently I am trying to add a background that I made in Photoshop and whenever I add it in as a bitmap and draw it to the canvas no background shows up and the fish starts to lag across the screen. I was wondering if I needed to make a new View class and draw on a different canvas or if I could use the same one? Thank you for the help!
Here is the code in case you guys are interested:
public class Fish extends View {
Bitmap bitmap;
float x, y;
public Fish(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fish1);
x = 0;
y = 0;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, x, y, null);
if (x < canvas.getWidth())
{
x += 7;
}else{
x = 0;
}
invalidate();
}
}
You can draw as many bitmaps as you like. Each will overlay the prior. Thus, draw your background first, then draw your other images. Be sure that in your main images, you use transparent pixels where you want the background to show through.
In your code, don't call Invalidate() - that's what causes Android to call onDraw() and should only be called from somewhere else when some data has changed and needs to be redrawn.
You can do something like this, where theView is the view containing your animation:
In your activity, put this code in onCreate()
myAnimation();
Then
private void myAnimation()
{
int millis = 50; // milliseconds between displaying frames
theView.postDelayed (new Runnable ()
{
#Override public void run()
{
theView.invalidate();
myAnimation(); // you can add a conditional here to stop the animation
}
}, millis);
}
I am rendering some very simple HTML (just some text and a small image) in a WebView off screen (not set as content view of an activity) so I can create a Bitmap from the content.
The way to know when the content is fully rendered I have based on this answer:
final AtomicBoolean rendered = new AtomicBoolean(false);
final WebView view = new WebView(this) {
#Override
public void invalidate() {
if (getProgress() == 100 && getContentHeight() > 0) {
if (! rendered.get()) {
rendered.set(true);
// Content should be fully rendered
}
}
super.invalidate();
}
};
// Load and lay out content
view.loadUrl(url);
view.setInitialScale(100);
view.layout(0, 0, 240, 420);
I've tested this successfully on 4.1.2, 4.2.2, 4.4.2 and 5.0. But with 4.0.3, it seems invalidate() is never called.
While trying all kinds of things I found out that showing a Toast and doing a delayed (1 sec.) call to invalidate() solves the problem:
#Override
public void onPageFinished(final WebView view, final String url) {
Toast.makeText(MainActivity.this, "Page loaded", Toast.LENGTH_SHORT).show();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (! rendered.get()) {
view.invalidate();
}
}
}, 1000);
}
Of course I don't consider this a valid solution, but while the delayed call to invalidate() somehow makes sense to me, I really wonder what side effect of the Toast does the trick here. Does it do some damage causing a redraw or something like that?
Sorry can not help you very much since I would like to make same operation of you (get screenshot from offline webview control) but in my case I'm not able to receive invalidate() event until the webview is offline. If I set as visible by inserting into the main window the invalidate() start to be generated but in case of offline (invisible) no invalidate() call is generated. The code you developed for get such result is only the snippet you posted here or there is some additional function to call for have the webview "active" in offline mode also?
About your problem what I can suggest is to override the other invalidate() definitions like:
public void invalidate(Rect dirty)
public void invalidate(int l, int t, int r, int b)
since in my tests I noted also these last was called.
Thank you
After a lot more testing on different (virtual) devices, this is the way that works reliable for me so far. Note that the "hack" for 4.0.3 mentioned in my original question still applies but is not included here.
Overriding invalidate() did not prove to be always reliable on all tested versions, so I am using PictureListener.onNewPicture(WebView view, Picture picture) even though it is deprecated.
In my activity:
final WebView view = new WebView(this);
// Zooming in can improve font quality
final float scale = 2.0;
// Unfortunately, there is no method view.getContentWidth()
final int contentWidth = 240;
view.setPictureListener(new PictureListener() {
#Override
public void onNewPicture(final WebView view, final Picture picture) {
if (view.getProgress() == 100 && view.getContentHeight() > 0) {
view.setPictureListener(null);
// Content is now fully rendered
final int width = Math.round(contentWidth * scale);
final int height = Math.round(view.getContentHeight() * scale);
final Bitmap bitmap = getBitmap(view, width, height);
// Display or print bitmap...
}
}
});
view.loadUrl(url);
view.setInitialScale(Math.round(scale * 100));
// Width and height must be at least 1
view.layout(0, 0, 1, 1);
And the getBitmap() method:
private Bitmap getBitmap(
final WebView view, final int width, final int height) {
final Bitmap bitmap = Bitmap.createBitmap(
width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
This is the best I can offer. The app has not been tested extensively yet, but so far it never failed to correctly render a small document including a small GIF image.
I have got a Image (Bitmap) on a ImageView, without flickering. When I change something with setPixel(x, y, COLOR_VALUE), so some Pixels are changed on the ImageView, it begins to flicker, where I changed the Pixels.
public class Drawer extends ImageView {
private Bitmap someBitmap;
public void doSomeDrawing() {
for (int i = 0; i < 100; i = i + 2) {
someBitmap.setPixel(x, y, COLOR_VALUE);
}
setOnDraw();
}
public void setOnDraw() {
this.setImageBitmap(someBitmap);
}
Try getting a copy of your bitmap and draw on it. Then recycle your old bitmap.
The problem here might also be that setting the pixel takes time and if you do this on the UI Thread, it will slow down your app and may cause flickering too. How much time does doSomething take ?
I'm writing a custom view that displays signals. In order to shorten my onDraw() time I cache everything I've drawn so far in a Bitmap and just append to that in every onDraw() call. By doing this I can save huge amounts of time since I only need to draw a few fixels at a time instead of redoing the whole thing.
There is on thing bothering me though - it appears as drawing directly to the provided canvas provides a more "accurate" drawing than drawing on the bitmap first and then drawing the bitmap on the canvas. By looking at the lower part of the following picture you can see the difference:
I uploaded a demo project displaying the discrepancy at https://github.com/gardarh/android-uglybitmapdrawing/ but the relevant code is as follows:
#Override
public void onDraw(Canvas canvas) {
if(cachedBitmap == null) {
cachedBitmap = Bitmap.createBitmap(getWidth(), 200, Config.ARGB_8888);
cachedCanvas = new Canvas(cachedBitmap);
}
for(int i = 0; i < COORDS.length; i++) {
float[] curCoords = COORDS[i];
canvas.drawLine(curCoords[0], curCoords[1], curCoords[2], curCoords[3], linePaint);
cachedCanvas.drawLine(curCoords[0], curCoords[1], curCoords[2], curCoords[3], linePaint);
}
canvas.drawBitmap(cachedBitmap, 0, 120, null);
}
Why are the two traces not the same and more importantly, how can I make the lower trace look like the upper one?
The reason for the differences is that the canvas drawing is done by hardware acceleration (GPU), and the bitmap drawing is done by software (CPU). If you disable hardware acceleration, they become the exact same.
If you multiply the X coordinates by 10, you will see that the difference is in the way lines are joined. These are minor one pixel difference and I wouldn't bother with them. I am not sure which one is the more accurate, they seem like just slightly different implementations.
Android framework API provides 2D drawing APIs for simple animation that does not require major dynamic changes.
There are two ways of implementation using these API.
1. Drawing to a View
2. Drawing on a Canvas
1.Drawing a circle to View
Drawing to view is a better option when your UI does not require dynamic changes in the application.
This can be achieved simply by extending the View class and define an onDraw() callback method.
Use the Canvas given to you for all your drawing,
using various Canvas.draw...() methods (Ex: canvas.drawCircle(x / 2, y / 2, radius, paint);). onDraw() is a callback method invoked when the view is initially drawn.
Below is a simple example code to draw a circle:-
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
public class MyView extends View {
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
int x = getWidth();
int y = getHeight();
int radius;
radius = 100;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
canvas.drawPaint(paint);
// Use Color.parseColor to define HTML colors
paint.setColor(Color.parseColor("#FB9J2F"));
canvas.drawCircle(x / 2, y / 2, radius, paint);
}
} }
2. Drawing rectangle on a canvas
To draw dynamic 2D graphics where in your application needs to regularly re draw itself, drawing on a canvas is a better option. A Canvas works for you as an interface, to the actual surface upon which your graphics will be drawn.
If you need to create a new Canvas, then you must define the bitmap upon which drawing will actually be performed. The Bitmap is always required for a Canvas.
The below example explains to draw a rectangle:-
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mylayout"> </LinearLayout>
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Paint paint = new Paint();
paint.setColor(Color.parseColor("#DD4N5C"));
Bitmap bitmap = Bitmap.createBitmap(512, 800, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawRect(150, 150, 250, 250, paint);
LinearLayout layout = (LinearLayout) findViewById(R.id.mylayout);
layout.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
}
I disagree that you are saving much by going the bitmap route. You might consider a data structure that stores the drawn signal and converting this to a JSON object that can be serialized/deserialized.
As for your question, this is an educated guess but it has more to do with rescaling of the drawn signal, since you are not using getHeight() when you create the bitmap.
It is possible to render a view at a low resolution, and then scale it up to fit the actual size of your view using the setFixedSize() method of a SurfaceHolder. However, the scaling is done with some kind of interpolation, causing everything to blur.
Is there any method for changing the method of interpolation to nearest neighbour or just turning it off?
Here is an example of what I mean, Made with a 4x4 surface in a fullscreen-view:
Left image: This is how I want the result to look (here achieved by drawing a nonfiltered bitmap)
Right image: This is what happens when the 4x4 canvas is scaled to fullscreen.
Sample code for generating the right image if anyone's interested:
public class ScaleView extends SurfaceView implements SurfaceHolder.Callback {
private final static float[] points = {0,0, 2,0, 4,0, 1,1, 3,1, 0,2, 2,2, 4,2, 1,3, 3,3};
private Paint white;
public ScaleView(Context context) {
super(context);
white = new Paint();
white.setColor(0xFFFFFFFF);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
Canvas c = holder.lockCanvas();
try{
c.drawPoints(points, white);
}finally{
holder.unlockCanvasAndPost(c);
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
holder.setFixedSize(4, 4);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){}
}
Note: The square pattern above is just an example of the effect I want to avoid. I am looking for a general solution that can be applied to any view (with a surfaceholder) with any content.
Sorry, you can't control this. It is not defined what kind of scaling will be done on these surfaces -- it depends on the hardware and how it is configured. Also, this kind of extreme scaling really should be avoided, since in some cases hardware can't do it so you will end up in slower paths. (For example if surfaces are being put into overlays, many hardware overlay engines can't do that kind of extreme scaling.)