So, I have this test project where I try to circumnavigate issues that I find with bitmaps and I'm having more issues than usual with resizing bitmaps. The project, pre-resizing tests, allowed me to load a bitmap from a stream, show it, and dispose it. In a loop.
protected override void OnCreate (Bundle bundle){
...
int i = 0;
System.Timers.Timer t = new System.Timers.Timer (100);
t.Elapsed += delegate(object sender, System.Timers.ElapsedEventArgs e) {
RunOnUiThread(delegate() {
if(i++ % 2 == 0){
Console.WriteLine("Iteration no: " + i);
tmpView tView = new tmpView(this);
mainView.AddView(tView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent));
}else{
((tmpView)mainView.GetChildAt(0)).dispose();
mainView.RemoveAllViews();
}
});
};
t.AutoReset = true;
t.Start ();
}
and the tmpView code:
private class tmpView:RelativeLayout{
PTImage img;
ImageView img2;
PTImageObj imgObj;
Android.Graphics.Bitmap bmp;
Bitmap resized;
public tmpView(Context cntx):base(cntx){
SetBackgroundColor(new Android.Graphics.Color(200, 0, 0, 200));
System.IO.Stream imgStream = Application.Context.Assets.Open ("backgroundLeft.png");
bmp = Android.Graphics.BitmapFactory.DecodeStream (imgStream, new Android.Graphics.Rect(), new Android.Graphics.BitmapFactory.Options(){InPurgeable = true, InInputShareable = true});
img2 = new ImageView(cntx);
img2.SetImageBitmap(bmp);
imgStream.Close();
imgStream.Dispose();
GC.SuppressFinalize(imgStream);
imgStream = null;
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MatchParent, 500);
lp.TopMargin = 150;
this.AddView(img2, lp);
}
public void dispose(){
bmp.Recycle ();
img2.SetImageDrawable (null);
}
}
So far that works fine, I can leave it iterating for as long as I want without having outOfMemory issues. However, if I want to change the tmpView code to this:
public tmpView(Context cntx):base(cntx){
SetBackgroundColor(new Android.Graphics.Color(200, 0, 0, 200));
System.IO.Stream imgStream = Application.Context.Assets.Open ("backgroundLeft.png");
bmp = Android.Graphics.BitmapFactory.DecodeStream (imgStream, new Android.Graphics.Rect(), new Android.Graphics.BitmapFactory.Options(){InPurgeable = true, InInputShareable = true});
resized = Bitmap.CreateScaledBitmap(bmp, bmp.Width + 50, bmp.Height + 50, false);
bmp.Recycle();
img2 = new ImageView(cntx);
img2.SetImageBitmap(resized);
imgStream.Close();
imgStream.Dispose();
GC.SuppressFinalize(imgStream);
imgStream = null;
}
I end up with outOfMemory issues at about ~200 iterations. My guess is that the rescaling is creating temp bitmaps that I can't force recycle. Is there a better way of resizing the bitmap so I can continue the loop without crashing?
Note: if I don't actually resize, if I give the CreateScaledBitmap the same sizes the bitmap has, I don't run into the memory issues. But... that kind of defeats the purpose :P. Oh, and yes, I did add the resized.Recycle (); in the dispose
Note2: The code is in C# but I'm fine with answers in java
Thank you,
Axel
Related
I want to convert PinBitmap (SkiaSharp.SkBitmap) to Android.Graphics.Bitmap. I couldn't find online references, I only tried this in the Android project:
Android.Graphics.Bitmap bitmap = BitmapFactory.DecodeByteArray(myView.PinBitmap.Bytes, 0, myView.PinBitmap.Bytes.Length);
but the bitmap is null.
I'm creating the PinBitmap from a SKCanvasView:
private void SKCanvasView_PaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
var surface = e.Surface;
var canvas = surface.Canvas;
SKImageInfo info = e.Info;
canvas.DrawLine(10, 10, 10, 200, new SKPaint() { IsStroke = true, Color = SKColors.Green, StrokeWidth = 10 });
SKBitmap saveBitmap = new SKBitmap();
// Create bitmap the size of the display surface
if (saveBitmap == null)
{
saveBitmap = new SKBitmap(info.Width, info.Height);
}
// Or create new bitmap for a new size of display surface
else if (saveBitmap.Width < info.Width || saveBitmap.Height < info.Height)
{
SKBitmap newBitmap = new SKBitmap(Math.Max(saveBitmap.Width, info.Width),
Math.Max(saveBitmap.Height, info.Height));
using (SKCanvas newCanvas = new SKCanvas(newBitmap))
{
newCanvas.Clear();
newCanvas.DrawBitmap(saveBitmap, 0, 0);
}
saveBitmap = newBitmap;
}
// Render the bitmap
canvas.Clear();
canvas.DrawBitmap(saveBitmap, 0, 0);
var customPin = new CustomPin { PinBitmap = saveBitmap };
Content = customPin;
}
This is easy to do, you just need to have the SkiaSharp.Views NuGet
package installed. Then, there are extension methods:
skiaBitmap = androidBitmap.ToSKBitmap();
androidBitmap = skiaBitmap.ToBitmap();
There are also a few others, like: ToSKImage
and ToSKPixmap.
NOTE: these all make copies of the pixel data. To avoid memory issue,
you can dispose of the original as soon as the method returns.
Source: https://forums.xamarin.com/discussion/comment/294868/#Comment_294868
I want to convert PinBitmap (SkiaSharp.SkBitmap) to Android.Graphics.Bitmap
In Android MainActivity, you could use AndroidExtensions.ToBitmap Method to convert PinBitmap to Bitmap.
AndroidExtensions.ToBitmap Method: https://learn.microsoft.com/en-us/dotnet/api/skiasharp.views.android.androidextensions.tobitmap?view=skiasharp-views-1.68.1
Install SkiaSharp.Views.Forms from NuGet Package. https://www.nuget.org/packages/SkiaSharp.Views.Forms/
Use the reference.
using SkiaSharp.Views.Android;
Use the code below.
var bitmap = AndroidExtensions.ToBitmap(PinBitmap);
I am trying to show frame by frame animation by changing images in a imageview. I tried animation drawable in xml and also changing the bitmap of the imageview inside a Handler. I also tried to store only three bitmaps inside a arraylist(to avoid out of memory) as a caching mechanism but really low improvement. I need to iterate 36 images for a full animation. The problem i am facing is that in all the methods I used I cannot complete the animation in the given timeframe of 50ms. The images range from 250 kb smallest to 540 kb maximum. The fps of animation is really low. As the ios version of the app is ready I am constrained to show animation consistent to the ios version. I am a noob in renderscript and opengl. Is there any way to show a smooth animation for large images in 50-60ms. Any hints or suggestions is highly appreciated. Heres a snapshot of the animation:
Here's the link to my images for any one intrested.
I wrote a simple activity that does the most basic thing:
Loads all bitmaps in a Thread, then posts a change to an ImageView every 40ms.
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{
R.drawable.s0,
R.drawable.s1,
R.drawable.s2,
R.drawable.s3,
R.drawable.s4,
R.drawable.s5,
R.drawable.s6,
R.drawable.s7,
R.drawable.s8,
R.drawable.s9,
R.drawable.s10,
R.drawable.s11,
R.drawable.s12,
R.drawable.s13,
R.drawable.s14,
R.drawable.s15,
R.drawable.s16,
R.drawable.s17,
R.drawable.s18,
R.drawable.s19,
R.drawable.s20,
R.drawable.s21,
R.drawable.s22,
R.drawable.s23,
R.drawable.s24,
R.drawable.s25,
R.drawable.s26,
R.drawable.s27,
R.drawable.s28,
R.drawable.s29,
R.drawable.s30,
R.drawable.s31,
R.drawable.s32,
R.drawable.s33,
R.drawable.s34,
R.drawable.s35,
};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
#Override
public void run() {
long timestamp = System.currentTimeMillis();
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
Log.d("ANIM-TAG", "Loading all bitmaps took " + (System.currentTimeMillis() - timestamp) + "ms");
for (int i = 0; i < mBitmaps.size(); i++) {
final int idx = i;
handler.postDelayed(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(mBitmaps.get(idx));
}
}, i * 40);
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
}
}
This looked pretty decent on my Nexus 7, but it did take a little over 4s to load all the bitmaps.
Can you load the bitmaps in advance?
Also, it won't save a ton, but your pngs have a bunch of padding around the transparent space. You can crop them and reduce the memory a bit. Otherwise compressing the images will also help (like limiting the number of colors used).
Ideally, in the above solution, you'd recycle the bitmaps immediately after they're no longer used.
Also, if that's too memory-heavy, you could do as you mentioned, and have a Bitmap buffer, but I'm pretty sure it'll need to be more than 3 images large.
Good luck.
EDIT: Attempt 2.
First, I cropped all the images to 590x590. This shaved about 1mb off the images. Then I created a new class, which is a bit "busy" and doesn't have a fixed frame rate but renders the images as soon as they are ready:
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{R.drawable.s0, R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5, R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10, R.drawable.s11, R.drawable.s12, R.drawable.s13, R.drawable.s14, R.drawable.s15, R.drawable.s16, R.drawable.s17, R.drawable.s18, R.drawable.s19, R.drawable.s20, R.drawable.s21, R.drawable.s22, R.drawable.s23, R.drawable.s24, R.drawable.s25, R.drawable.s26, R.drawable.s27, R.drawable.s28, R.drawable.s29, R.drawable.s30, R.drawable.s31, R.drawable.s32, R.drawable.s33, R.drawable.s34, R.drawable.s35};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final long timestamp = System.currentTimeMillis();
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
#Override
public void run() {
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
Thread drawing = new Thread() {
#Override
public void run() {
int i = 0;
while (i < mImageRes.length) {
if (i >= mBitmaps.size()) {
Thread.yield();
} else {
final Bitmap bitmap = mBitmaps.get(i);
handler.post(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
i++;
}
}
Log.d("ANIM-TAG", "Time to render all frames:" + (System.currentTimeMillis() - timestamp) + "ms");
}
};
drawing.setPriority(Thread.MAX_PRIORITY);
drawing.start();
}
}
The above got the rendering to start nearly immediately, and it took less than 4s on my 2012 Nexus 7.
As a last effort, I converted all the images to 8-bit PNGs instead of 32-bit. This brought the rendering to under 2 seconds!
I'll bet any solution you end up with will benefit from making the images as small as possible.
Again -- Good Luck!
I'm facing the same problem and I have solved it by overriding an AnimationDrawable.
So, if the problem is you that can't load all images in array because it is too big for the memory to hold it, then load the image when you need it.
My AnimationDrawable is this:
public abstract class MyAnimationDrawable extends AnimationDrawable {
private Context context;
private int current;
private int reqWidth;
private int reqHeight;
private int totalTime;
public MyAnimationDrawable(Context context, int reqWidth, int reqHeight) {
this.context = context;
this.current = 0;
//In my case size of screen to scale Drawable
this.reqWidth = reqWidth;
this.reqHeight = reqHeight;
this.totalTime = 0;
}
#Override
public void addFrame(Drawable frame, int duration) {
super.addFrame(frame, duration);
totalTime += duration;
}
#Override
public void start() {
super.start();
new Handler().postDelayed(new Runnable() {
public void run() {
onAnimationFinish();
}
}, totalTime);
}
public int getTotalTime() {
return totalTime;
}
#Override
public void draw(Canvas canvas) {
try {
//Loading image from assets, you could make it from resources
Bitmap bmp = BitmapFactory.decodeStream(context.getAssets().open("presentation/intro_000"+(current < 10 ? "0"+current : current)+".jpg"));
//Scaling image to fitCenter
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, bmp.getWidth(), bmp.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
//Calculating the start 'x' and 'y' to paint the Bitmap
int x = (reqWidth - bmp.getWidth()) / 2;
int y = (reqHeight - bmp.getHeight()) / 2;
//Painting Bitmap in canvas
canvas.drawBitmap(bmp, x, y, null);
//Jump to next item
current++;
} catch (IOException e) {
e.printStackTrace();
}
}
abstract void onAnimationFinish();
}
Now to play animation you need to do what next
//Get your ImageView
View image = MainActivity.this.findViewById(R.id.presentation);
//Create AnimationDrawable
final AnimationDrawable animation = new MyAnimationDrawable(this, displayMetrics.widthPixels, displayMetrics.heightPixels) {
#Override
void onAnimationFinish() {
//Do something when finish animation
}
};
animation.setOneShot(true); //dont repeat animation
//This is just to say that my AnimationDrawable has 72 frames with 50 milliseconds interval
try {
//Always load same bitmap, anyway you load the right one in draw() method in MyAnimationDrawable
Bitmap bmp = BitmapFactory.decodeStream(MainActivity.this.getAssets().open("presentation/intro_00000.jpg"));
for (int i = 0; i < 72; i++) {
animation.addFrame(new BitmapDrawable(getResources(), bmp), 50);
}
} catch (IOException e) {
e.printStackTrace();
}
//Set AnimationDrawable to ImageView
if (Build.VERSION.SDK_INT < 16) {
image.setBackgroundDrawable(animation);
} else {
image.setBackground(animation);
}
//Start animation
image.post(new Runnable() {
#Override
public void run() {
animation.start();
}
});
That is all, and works OK form me!!!
Try using an AnimationDrawable for your ImageView. See my answer here for example.
The most efficient way to show fram by frame animation in android is using OpenGL with NDK.
Create an imageView using programming and then rotate it with an angle and then make another one and then rotate it.. do it for number of imageView you want to show. you only have to add only one image for it.
you can rotate an image like this..
Matrix matrix=new Matrix();
imageView.setScaleType(ScaleType.MATRIX); //required
matrix.postRotate((float) angle, pivX, pivY);
imageView.setImageMatrix(matrix);
Maybe stupid answer but it may help you. Make the animation using another tool and save it as a high quality video and then just play the video.
Try exporting your images to a GIF? Android does support decoding GIF files.
Have a look : Supported Media Formats.
Loading the bitmaps from assets instead of resources saved 40% decoding time for me. You might want to try that.
So this went from 5 seconds with resources to 3 seconds on your pictures on my 2012 Nexus 7:
long time = System.currentTimeMillis();
try {
for (int i = 0; i < 35; i++) {
InputStream assetStream = getAssets().open(
"_step" + (i + 1) + ".png");
try {
Bitmap bitmap = BitmapFactory.decodeStream(assetStream);
if (bitmap == null) {
throw new RuntimeException("Could not load bitmap");
}
mBitmaps.add(bitmap);
} finally {
assetStream.close();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
Log.d("ANIM", "Loading bitmaps elapsed "+(System.currentTimeMillis() - time)+"ms");
Lots been said, written, vented out ;) on this already, I know. But my question is a bit different. I realize that there is issue with using large bitmap objects in Android. In my Android App, I need to use large bitmaps in various places. cannot help it. I tried various things listed in stackoverflow. One that I want to try but not able to is to avoid using ARBG_8888. The App crashes exactly at that line many times. I have an ImageView where I load an initial large bitmap. Then the user can make some markings in the bitmap and then I need to save the whole image (i.e. the original bitmap + the user's markings). Am trying this with this code I got from stackoverflow:
public static Bitmap loadBitmapFromView(Context context, View view) {
Log.d(logtag, "loadBitmapFromView");
Bitmap bitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
// These help in saving/recycling the bitmap memory
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap dummy = null;
try {
dummy = BitmapFactory.decodeStream(context.getAssets().open("icon_add.png"), new Rect(-1,-1,-1,-1), options);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bitmap = Bitmap.createBitmap(view.getLayoutParams().width,
view.getLayoutParams().height, Bitmap.Config.ARGB_8888);
// bitmap = Bitmap.createScaledBitmap( dummy, view.getLayoutParams().width,
// view.getLayoutParams().height, false);
Canvas c = new Canvas(bitmap);
view.layout(0, 0, view.getLayoutParams().width, view.getLayoutParams().height);
view.draw(c);
Log.d(logtag, "loadBitmapFromView: width:" + view.getLayoutParams().width);
c = null;
return bitmap;
}
How do I avoid using ARGB_8888 in the above code? Can someone please help?
I removed it from many other places in the code, but, in the above snippet, am left in vain.
I tried using an initial dummy object (you can see the object 'dummy' in the above code) to create a scaled bitmap object ( with createScaledBitmap() which necessarily asks for a source bitmap ) and then tried to load the canvas, but the generated image is having only the dummy object (icon_add.png) and not the one from the imageview.
you can show image in device specific height width....then you don't get out of memory for imagesize.
this is code:
#SuppressLint("NewApi")
public static int getDeviceWidth(Activity activity) {
int deviceWidth = 0;
Point size = new Point();
WindowManager windowManager = activity.getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
windowManager.getDefaultDisplay().getSize(size);
deviceWidth = size.x;
} else {
Display display = windowManager.getDefaultDisplay();
deviceWidth = display.getWidth();
}
return deviceWidth;
}
#SuppressLint("NewApi")
public static int getDeviceHeight(Activity activity) {
int deviceHeight = 0;
Point size = new Point();
WindowManager windowManager = activity.getWindowManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
windowManager.getDefaultDisplay().getSize(size);
deviceHeight = size.y;
} else {
Display display = windowManager.getDefaultDisplay();
deviceHeight = display.getHeight();
}
return deviceHeight;
}
you can use device height and width..and set this image in imageview..
ImageView image = new ImageView(viewHomeScreen);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.FILL_PARENT,
FrameLayout.LayoutParams.FILL_PARENT);
params.height = getDeviceHeight(activity);
params.width = getDevicewidth(activity);
FrameLayout framelayout = new FrameLayout(viewHomeScreen);
framelayout.addView(image, 0, params);
i hope if you use device specific height and width then your image memory issue will be solved.
background
starting from API 11, you can re-use bitmaps when you decode new ones, so that the decoder won't need to re-create totally new large objects.
this is done using something like this code (taken from this tutorial) :
mCurrentBitmap = Bitmap.createBitmap(imageWidth,imageHeight,Bitmap.Config.ARGB_8888);
bitmapOptions.inJustDecodeBounds = false;
bitmapOptions.inBitmap = mCurrentBitmap;
bitmapOptions.inSampleSize = 1;
mCurrentBitmap = BitmapFactory.decodeResource(getResources(),imageResId, bitmapOptions);
the advantage is quite obvious: using less memory in some cases, getting less pressure on the GC, and having a better performance because you don't need to create a lot more large objects.
the only catch is that both images must be of the same size and config.
the problem
even though the code works perfectly fine with resources in the project itself (in the res folder), i seem to always get the next error when handling image files i've put into the internal storage :
java.lang.IllegalArgumentException: Problem decoding into existing bitmap
i've tried multiple different flags for the bitmap options:
bitmapOptions.inPurgeable = true;
bitmapOptions.inInputShareable = true;
bitmapOptions.inMutable = true;
bitmapOptions.inScaled = false;
bitmapOptions.inSampleSize = 1;
bitmapOptions.inPreferredConfig = Config.RGB_565; //i've set the created bitmap to be of this type too, of course
i've also tried both decodeFile and decodeStream for the BitmapFactory.
here's a sample code to show there is a problem (based on the sample i've written about) :
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bitmap_allocation);
final int[] imageIDs = { R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e, R.drawable.f };
final CheckBox checkbox = (CheckBox) findViewById(R.id.checkbox);
final TextView durationTextview = (TextView) findViewById(R.id.loadDuration);
final ImageView imageview = (ImageView) findViewById(R.id.imageview);
// Create bitmap to be re-used, based on the size of one of the bitmaps
mBitmapOptions = new BitmapFactory.Options();
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.a, mBitmapOptions);
mCurrentBitmap = Bitmap.createBitmap(mBitmapOptions.outWidth, mBitmapOptions.outHeight, Bitmap.Config.ARGB_8888);
mBitmapOptions.inJustDecodeBounds = false;
mBitmapOptions.inBitmap = mCurrentBitmap;
mBitmapOptions.inSampleSize = 1;
mBitmapOptions.inPreferredConfig = Config.ARGB_8888;
BitmapFactory.decodeResource(getResources(), R.drawable.a, mBitmapOptions);
imageview.setImageBitmap(mCurrentBitmap);
// When the user clicks on the image, load the next one in the list
imageview.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
mCurrentIndex = (mCurrentIndex + 1) % imageIDs.length;
Options bitmapOptions = new Options();
bitmapOptions.inPreferredConfig = Config.ARGB_8888;
if (checkbox.isChecked()) {
// Re-use the bitmap by using BitmapOptions.inBitmap
bitmapOptions = mBitmapOptions;
bitmapOptions.inBitmap = mCurrentBitmap;
}
final long startTime = System.currentTimeMillis();
//
File tempFile = null;
try {
tempFile = File.createTempFile("temp", ".webp", getApplicationContext().getCacheDir());
FileOutputStream fileOutputStream;
final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imageIDs[mCurrentIndex]);
bitmap.compress(CompressFormat.WEBP, 100, fileOutputStream = new FileOutputStream(tempFile));
fileOutputStream.flush();
fileOutputStream.close();
final InputStream inputStream = new FileInputStream(tempFile);
mCurrentBitmap = BitmapFactory.decodeStream(inputStream,null,bitmapOptions);
inputStream.close();
} catch (final IOException e1) {
e1.printStackTrace();
}
imageview.setImageBitmap(mCurrentBitmap);
// One way you can see the difference between reusing and not is through the
// timing reported here. But you can also see a huge impact in the garbage
// collector if you look at logcat with and without reuse. Avoiding garbage
// collection when possible, especially for large items like bitmaps,
// is always a good idea.
durationTextview.setText("Load took " + (System.currentTimeMillis() - startTime));
}
});
}
the question
why do i keep getting this error, and how can i fix it?
i've found some similar questions, but none had an answer.
it seems that the problem with this cool tip when using webP files.
i just need to use either jpg or png.
I have two png image files that I would like my android app to combine programmatically into one png image file and am wondering if it is possible to do so? if so, what I would like to do is just overlay them on each other to create one file.
the idea behind this is that I have a handful of png files, some with a portion of the image on the left with the rest transparent and the others with an image on the right and the rest transparent. and based on user input it will combine the two to make one file to display. (and i cant just display the two images side by side, they need to be one file)
is this possible to do programmatically in android and how so?
I've been trying to figure this out for a little while now.
Here's (essentially) the code I used to make it work.
// Get your images from their files
Bitmap bottomImage = BitmapFactory.decodeFile("myFirstPNG.png");
Bitmap topImage = BitmapFactory.decodeFile("myOtherPNG.png");
// As described by Steve Pomeroy in a previous comment,
// use the canvas to combine them.
// Start with the first in the constructor..
Canvas comboImage = new Canvas(bottomImage);
// Then draw the second on top of that
comboImage.drawBitmap(topImage, 0f, 0f, null);
// comboImage is now a composite of the two.
// To write the file out to the SDCard:
OutputStream os = null;
try {
os = new FileOutputStream("/sdcard/DCIM/Camera/" + "myNewFileName.png");
comboImage.compress(CompressFormat.PNG, 50, os)
} catch(IOException e) {
e.printStackTrace();
}
EDIT :
there was a typo,
So, I've changed
image.compress(CompressFormat.PNG, 50, os)
to
bottomImage.compress(CompressFormat.PNG, 50, os)
You can do blending. This is not particular to Android. It's just universal image processing.
EDIT:
You may find these articles & samples & code useful:
http://www.jhlabs.com/ip/
http://kfb-android.blogspot.com/2009/04/image-processing-in-android.html
http://code.google.com/p/jjil/
Image Processing on Android
I use this code
private class PhotoComposition extends AsyncTask<Object, Void, Boolean> {
private String pathSave;//path save combined images
#Override
protected Boolean doInBackground(Object... objects) {
List<String> images = (List<String>) objects[0]; //lsit of path iamges
pathSave = (String) objects[1];//path save combined images
if (images.size() == 0) {
return false;
}
List<Bitmap> bitmaps = new ArrayList<>();
for (int i = 0; i < images.size(); i++) {
bitmaps.add(BitmapFactory.decodeFile( images.get(i)));
}
int width = findWidth(bitmaps);//Find the width of the composite image
int height = findMaxHeight(bitmaps);//Find the height of the composite image
Bitmap combineBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//create bitmap of composite image
combineBitmap.eraseColor(Color.parseColor("#00000000")); //bcakgraound color of composite image
Bitmap mutableCombineBitmap = combineBitmap.copy(Bitmap.Config.ARGB_8888, true);//create mutable bitmap to create canvas
Canvas canvas = new Canvas(mutableCombineBitmap);// create canvas to add bitmaps
float left = 0f;
for (int i = 0; i < bitmaps.size(); i++) {
canvas.drawBitmap(bitmaps.get(i), left, 0f, null);//Taking photos horizontally
left += bitmaps.get(i).getWidth();//Take right to the size of the previous photo
}
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(pathSave);//path of save composite image
mutableCombineBitmap.compress(Bitmap.CompressFormat.PNG, 80, outputStream);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
#Override
protected void onPostExecute(Boolean isSave) {
if (isSave) {
//iamge save on pathSave
Log.i("PhotoComposition", "onPostExecute: " + pathSave);
}
super.onPostExecute(isSave);
}
private int findMaxHeight(List<Bitmap> bitmaps) {
int maxHeight = Integer.MIN_VALUE;
for (int i = 0; i < bitmaps.size(); i++) {
if (bitmaps.get(i).getHeight() > maxHeight) {
maxHeight = bitmaps.get(i).getHeight();
}
}
return maxHeight;
}
private int findWidth(List<Bitmap> bitmaps) {
int width = 0;
for (int i = 0; i < bitmaps.size(); i++) {
width += bitmaps.get(i).getWidth();
}
return width;
}
USAGE
List<String> images = new ArrayList<>();
images.add("/storage/emulated/0/imageOne.png");//path of image in storage
images.add("/storage/emulated/0/imageTwo.png");
// images.add("/storage/emulated/0/imageThree");
// ... //add more images
String pathSaveCombinedImage = "/storage/emulated/0/CombinedImage.png";//path save result image
new PhotoComposition().execute(images, pathSaveCombinedImage);
And the result of using the above code will be as follows
You may wish to look into the Canvas object, which would make it easy to do other drawing operations as well. You can just draw your bitmaps onto a canvas where you want them, then save the resulting bitmap.
If they have transparent sections, then if you draw one on top of the other, only the non-transparent portions will overlap. It will be up to you to arrange the bitmaps however you like.
For the separate issue of re-saving your image to a png, use bitmap.compress().
Try this .
public Bitmap mergeBitmap(Bitmap frame, Bitmap img){
Bitmap bmOverlay = Bitmap.createBitmap(frame.getWidth(), frame.getHeight(), frame.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(img, 0, 0, null);
canvas.drawBitmap(frame, new Matrix(), null);
return bmOverlay;
}
Returns a bitmap image
Pass two bitmap images to your function as shown below
Bitmap img= mergeBitmap(imgone, imagetwo);
See the entire post or also see merge multiple images in android programmatically