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 ?
Related
I have a AnimationDrawable that i initialise before starting the animation and recycle it right as it finishes.. the problem is that when i want to start the animation again, i reinitialise everything but still gives an exception
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#2bbad018
here's my code
public ImageView radarImageView;
public AnimationDrawable animationDrawable;
public void animationStart() {
// animationStop();
radarImageView = (ImageView) findViewById(R.id.radarIV);
radarImageView.setBackgroundResource(R.drawable.sensor_animation);
animationDrawable = (AnimationDrawable) radarImageView.getBackground();
animationDrawable.start();
}
public void animationStop() {
animationDrawable.stop();
for (int i = 0; i < animationDrawable.getNumberOfFrames(); ++i){
Drawable frame = animationDrawable.getFrame(i);
if (frame instanceof BitmapDrawable) {
((BitmapDrawable)frame).getBitmap().recycle();
}
frame.setCallback(null);
}
animationDrawable.setCallback(null);
// animationDrawable = null;
// radarImageView.setBackgroundResource(0);
}
Why does it not reinitialise the whole thing again?
The problem is because when you Bitmap.recycle() is called on the bitmap you cannot reuse it.
Also as you are calling setBackgroundResources(R.drawable.sensor_animation) you are referring to the same object which its bitmaps were recycled previously.
As a solution, you either have to create a new drawable every time or do not recycle that instance.
If you are worries about the memory usage try to use smaller bitmaps. Also using the correct sizes for different screen densities would help.
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.
There is an ImageView and a GLSurfaceView setup with 'match_parent' parameters on the screen. The ImageView gets populated quickly and shows up on the screen. Then, the GLSurfaceView gets populated on top of it with modifications to the image. So far so good.
However, these views also live inside of a ViewPager with images to the left and right of them. Before introducing the OpenGL Surface View on top of the ImageView, the views would transition out to the left and right as expected. But after introducing the GLSurfaceView on top of it, as soon as the user starts to swipe their fingers to move to the next image, the SurfaceView becomes transparent causing the image to disappear. It even forces the image below it to disappear in the ImageView. Showing the background of the parent view.
I am unclear on how to approach this issue. It would be nice if the texture slid out to the side just like the ImageView or even be transparent but leave the ImageView behind it visible would be fine.
Even GLSurfaceView.RENDERMODE_CONTINUOSLY doesn't keep the texture around during the transition.
It would seem that the reason that I cannot get the effect that I am trying to achieve is because the GLSurfaceView is always placed behind other views causing transparency on the foreground views. Thus, the GLSurfaceView would have priority over being drawn. In order to achieve the effect that I would want, I probably need to be sure to use View.setVisibility( ) for invisible when swiping and set it back to visible after the ViewPager settles. That is the current conclusion that I have come across after seeing a couple other links as a by-product of different research.
z-order for GLSurfaceViews
scrolling in GLSurfaceViews
It doesn't seem as though the GLSurfaceView class really performs as similar to a view as I had been originally expecting. So a little extra care will need to be taken in order to progress further to keep the ImageView visible transitioning between images in the ViewPager.
Within a viewPager I managed to accomplish something along the lines of the following code. This is still in a rough state of course since it was in the middle of testing, but it accomplishes what I am looking for. Which is for a texture to start loading when we begin to settle on an image, and then disappear revealing the original image below it once the user begins to leave the image. And this seems to work without tearing or flickers which is how I would expect for it to behave.
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
Log.i("photoViewer:","dragging ViewPager");
if(photoSurfaceView_ != null && photoViewFrameLayout != null)
{
photoSurfaceView_.setAlpha(0);
photoSurfaceView_.invalidate();
}
break;
case ViewPager.SCROLL_STATE_IDLE:
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(1);
Log.i("photoViewer:","idle ViewPager");
break;
case ViewPager.SCROLL_STATE_SETTLING:
int childCount = photoViewFrameLayout.getChildCount();
if(childCount >= 2)
{
photoViewFrameLayout.removeViews(1, childCount-1);
}
new Thread(new Runnable() {
#Override
public void run()
{
Looper.prepare();
final Bitmap openGlBitmap = BitmapFactoryUtils.resizeAsNecessaryForOpenGLtexture(photoPaths[photoViewPager_.getCurrentItem()], new BitmapFactory.Options());
Rect bounds = BitmapFactoryUtils.calculateBoundsForBitmap(activityContext, openGlBitmap);
int scaledHeight = bounds.height();
int scaledWidth = bounds.width();
photoSurfaceView_ = new PhotoViewSurfaceView(activityContext, openGlBitmap);
photoSurfaceView_.setLayoutParams(new LayoutParams(scaledWidth, scaledHeight, Gravity.CENTER));
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(0);
runOnUiThread(new Runnable() {
#Override
public void run()
{
photoViewFrameLayout.addView(photoSurfaceView_);
}
});
}
}).start();
Log.i("photoViewer:","settling ViewPager");
break;
}
Edit: As requested I wanted to provide some additional input as to how to resize the texture for an OpenGL SurfaceView that will use a Bitmap for input.
The following is a quick snippet of some of the code available for the first step in the process in checking against whether or not the texture size of the current Bitmap will fit within a single texture. If you want to tile the texture to keep the image larger, then this gets more complicated and this was not what was needed for my resolution.
The texture size can be retrieved with the following code:
private void assignTextureSize()
{
/* The maximum texture size can be found within the OpenGL context and then shared amongst other applications. */
int[] maxTextureSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
maxTextureSize = maxTextureSize[0];
}
but only from within the Renderer class and for my case I waited until within the onDrawFrame of the Texture to do the assignment; before a Bitmap has been assigned to it.
Then, the goal of the Bitmap being loaded for me was to simply load what I could without running out of memory. For this, I simply modified the Options of the Bitmap that I was about to load and try and load it within a try catch block. If it ran out of memory, I would try it again, but with a smaller footprint.
try
{
final int sizeFactors[] = { 1, 2, 4, 8, 16, 32 };
if (new File(bitmapFilePath).exists())
{
BitmapFactory.Options options = new BitmapFactory.Options();
for (int i = 0; i < sizeFactors.length; ++i)
{
try
{
options.inSampleSize = sizeFactors[i];
Bitmap bmp = BitmapFactory.decodeFile(bitmapFilePath, options);
if(bmp.getHeight() > maximumTextureSize ||
bmp.getWidth() > maximumTextureSize)
{
continue;
}
/*
* #category Check against EXIF data if the image needs to be rotated or not for viewing.
*/
Matrix matrix = new Matrix ();
ExifInterface exif = new ExifInterface(bitmapFilePath);
int orientation = exif.getAttributeInt (ExifInterface.TAG_ORIENTATION, 1);
switch(orientation)
{
case ExifInterface.ORIENTATION_NORMAL:
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate (90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate (180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate (270);
break;
default:
}
bmp = Bitmap.createBitmap (bmp, 0, 0, bmp.getWidth (), bmp.getHeight (), matrix, true);
return bmp;
} catch (OutOfMemoryError outOfMemory)
{
//Do nothing, it will return when one that doesn't run out of memory is loaded. Or you could do additional checking to bail if needed, etc.
}
}
throw new Exception("Not enough memory for loading image");
}
} catch (Exception exception)
{
Log.e("CustomLogTag", "Exception for loading the image that was selected for extraction.");
}
There are many ways to accomplish this, but this is one approach to take and I wanted to add clarity and additional information to this previous post as requested since it did not contain as much information on my accepted answer as I would have like either.
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)