On android I am drawing into a android.graphics.Picture then save the Picture to a file. Later I reload the picture into memory and draw it to the canvas. I noticed that Bitmaps were never drawing. And after much debugging I managed to narrow down the problem to Picture.writeToStream and Picture.createFromStream. It seems that Bitmaps drawn into the picture don't get reloaded properly. Below is sample code I wrote to show the problem. In this sample my canvas is not hardware accelerated.
So my questions are as follows:
Am I doing something wrong?
Is this an Android bug? I filed the bug report https://code.google.com/p/android/issues/detail?id=54896 because I think this is.
Any known workaround?
#Override
protected void onDraw(Canvas canvas)
{
try
{
Picture picture = new Picture();
// Create a bitmap
Bitmap bitmap = Bitmap.createBitmap( 100, 100, Config.ARGB_8888);
Canvas bitmapCanvas = new Canvas(bitmap);
bitmapCanvas.drawARGB(255, 0, 255, 0);
// Draw the bitmap to the picture's canvas.
Canvas pictureCanvas = picture.beginRecording(canvas.getWidth(), canvas.getHeight());
RectF dstRect = new RectF(0, 0, 200, 200);
pictureCanvas.drawBitmap(bitmap, null, dstRect, null);
picture.endRecording();
// Save the Picture to a file.
File file = File.createTempFile("cache", ".pic");
FileOutputStream os = new FileOutputStream(file);
picture.writeToStream(os);
os.close();
// Read the picture back in
FileInputStream in = new FileInputStream(file);
Picture cachedPicture = Picture.createFromStream(in);
// Draw the cached picture to the view's canvas. This won't draw the bitmap!
canvas.drawPicture(cachedPicture);
// Uncomment the following line to see that Drawing the Picture without reloading
// it from disk works fine.
//canvas.drawPicture(picture);
}
catch (Exception e)
{
}
}
I did find an answer to this question after looking at the native android code that backs the Bitmap. Android only can only save certain types of bitmaps to the picture. This is because the SkBitmap class only supports certain types of inputs that result in a bitmap that can be saved to a Picture. So in this can I can workaround the problem by providing those magical inputs. Use a bitmap that is saved to disk and call BitmapFactory.decodeFileDescriptor to create it.
private Bitmap createReusableBitmap(Bitmap inBitmap)
{
Bitmap reuseableBitmap = null;
if (inBitmap== null)
return null;
try
{
// The caller is responsible for deleting the file.
File tmpBitmapFile = File.createTempFile("bitmap", ".png");
setBitmapPath(tmpBitmapFile.getAbsolutePath());
FileOutputStream out = new FileOutputStream(tmpBitmapFile);
boolean compressed = inBitmap.compress(CompressFormat.PNG, 100, out);
out.close();
if (compressed)
{
// Have to create a purgeable bitmap b/c that is the only kind that works right when drawing into a
// Picture. After digging through the android source I found decodeFileDescriptor will create the one we need.
// See https://github.com/android/platform_frameworks_base/blob/master/core/jni/android/graphics/BitmapFactory.cpp
// In short we have to give the options inPurgeable=true inInputShareable=true and call decodeFileDescriptor
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inInputShareable = true;
options.inPurgeable = true;
options.inSampleSize = 1;
options.inScaled = false;
options.inMutable = false;
options.inTempStorage = DraftRenderer.tempStorage;
FileInputStream inStream = new FileInputStream(tmpBitmapFile);
FileDescriptor fd = inStream.getFD();
reuseableBitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
inStream.close();
}
} catch (Exception e) {
}
return reuseableBitmap;
}
Note: a picture created from an input stream cannot be replayed on a hardware accelerated canvas.
Picture.createFromStream(InputStream stream)
you can use canvas.isHardwareAccelerated() to detect hardware accelerated or not.
Related
I need to create .jpeg/.png file on my Android application programmatically. I have simple image (black background), and it need to write some text on it programmatically. How can I do it? Is it possible?
It's definately possible.
To write text on an image you have to load the image in to a Bitmap object. Then draw on that bitmap with the Canvas and Paint functions. When you're done drawing you simply output the Bitmap to a file.
If you're just using a black background, it's probably better for you to simply create a blank bitmap on a canvas, fill it black, draw text and then dump to a Bitmap.
I used this tutorial to learn the basics of the canvas and paint.
This is the code that you'll be looking for to turn the canvas in to an image file:
OutputStream os = null;
try {
File file = new File(dir, "image" + System.currentTimeMillis() + ".png");
os = new FileOutputStream(file);
finalBMP.compress(CompressFormat.PNG, 100, os);
finalBMP.recycle(); // this is very important. make sure you always recycle your bitmap when you're done with it.
screenGrabFilePath = file.getPath();
} catch(IOException e) {
finalBMP.recycle(); // this is very important. make sure you always recycle your bitmap when you're done with it.
Log.e("combineImages", "problem combining images", e);
}
Yes, see here
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
You can also use awt's Graphics2D with this compatibility project
Using Graphics2d you can create a PNG image as well:
public class Imagetest {
public static void main(String[] args) throws IOException {
File path = new File("image/base/path");
BufferedImage img = new BufferedImage(100, 100,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.YELLOW);
g2d.drawLine(0, 0, 50, 50);
g2d.setColor(Color.BLACK);
g2d.drawLine(50, 50, 0, 100);
g2d.setColor(Color.RED);
g2d.drawLine(50, 50, 100, 0);
g2d.setColor(Color.GREEN);
g2d.drawLine(50, 50, 100, 100);
ImageIO.write(img, "PNG", new File(path, "1.png"));
}
}
Am drawing a graph using AChartEngine where I'm having text (i.e. annotations, as they call in AChartEngine). They appear fine when seen on the screen, as you can see below :
but when the screen is saved as bitmap, the text appears with only the outline of each letter displayed as you can see below :
and this is how I get the bitmap from the view (ie. I am taking a screenshot of the graph)
Bitmap bitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
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) {
e.printStackTrace();
}
bitmap = Bitmap.createBitmap(deviceWidth,
deviceHeight, Bitmap.Config.ARGB_8888); // use ARGB_4444 if outofmemory
Canvas c = new Canvas(bitmap);
c.drawColor(Color.WHITE);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight() + 15);
view.draw(c);
can someone please help?
This code works for me:
Bitmap cache;
view.buildDrawingCache();
cache = Bitmap.createBitmap(view.getDrawingCache());
view.destroyDrawingCache();
I'm writing a custom printing app in Android and I'm looking for ways to save on memory. I have three basic rectangles I need to print on a full page. Currently I'm creating a base Bitmap the size of the page:
_baseBitmap = Bitmap.createBitmap(width/_scale, height/_scale, Bitmap.Config.ARGB_8888);
The print process requests a Rect portion of that page. I cannot predetermine the dimensions of this Rect.
newBitmap = Bitmap.createBitmap(fullPageBitmap, rect.left/_scale, rect.top/_scale, rect.width()/_scale, rect.height()/_scale);
return Bitmap.createScaledBitmap(newBitmap, rect.width(), rect.height(), true);
Using bitmap config ARGB_8888 _baseBitmap is about 28MB (8.5"x11" # 300dpi = 2250*3300*4bytes). Even at 50% scaling (used above), my image is over 7MB. Scaling any smaller than this and image quality is too poor.
I've attempted to create _baseBitmap using Bitmap.Config.RGB_565, which does greatly reduce the full image size, but then when I overlay an image (jpegs) I get funny results. The image is compressed in width, duplicated next to itself, and all the color is green.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap myBitmap = BitmapFactory.decodeStream(input, null, options);
input.close();
return myBitmap;
....
private static Bitmap overlay(Bitmap bmp1, Bitmap bmp2, float left, float top) {
Canvas canvas = new Canvas(bmp1);
canvas.drawBitmap(bmp2, left, top, null);
return bmp1;
}
I know I can compress an image of these dimensions down to a reasonable size. I've looked into Bitmap.compress, but for some reason beyond my understanding I'm getting the same size image back:
ByteArrayOutputStream os = new ByteArrayOutputStream();
_baseBitmap.compress(Bitmap.CompressFormat.JPEG, 3, os);
byte[] array = os.toByteArray();
Bitmap newBitmap = BitmapFactory.decodeByteArray(array, 0, array.length);
_baseBitmap.getAllocationByteCount() == newBitmap.getAllocationByteCount()
It would be better to create it compressed than to create a large one and then compress it. Is there any way to create a compressed Bitmap? Any advice is greatly appreciated.
note: Not an Android expert. I'm not necessarily familiar with the platform specific terms you may use to respond. Please be gentle.
Try something like this, if you have a target size in mind.
private static final int MAX_BYTES_IMAGE = 4194304; // 4MB
//...
ByteArrayOutputStream out;
int quality = 90;
do
{
out = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
quality -= 10;
} while (out.size() > MAX_BYTES_IMAGE_FILESIZE);
out.close();
I trying to achieve good quality image resize in Android. I'm trying all methods listed here on SO and that I could find on Google, but I still can't find a good solution.
To exemplify what I'm trying to achieve and what problems I'm having, I'm posting 3 images with the different results. Basically I'm just getting a big image from SD card, resizing and cropping it.
Here is the desired result, achieved on Photoshop:
And this is when I use the tradicional method of drawing on canvas
And this result is when I use ImageMagick. It's better, but in some devices it takes minutes to resize (not cool for a mobile app)
So, here is my code using the canvas method:
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(params[0].path_source, o);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDither = false;
options.inPurgeable = true;
options.inScaled = false;
options.inPreferQualityOverSpeed = true;
options.inSampleSize = Utils.calculateInSampleSize(o, 640, 640);
Bitmap image = BitmapFactory.decodeFile(params[0].path_source, options);
... calculate right x, y for cropping ..
Matrix m = new Matrix();
m.postScale(new_width/(float)width, new_width/(float)width);
m.postRotate(rotation);
Bitmap result = Bitmap.createBitmap(640, 640, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
canvas.setMatrix(m);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
canvas.drawBitmap(image, -x, -y, paint);
FileOutputStream fos = activity.openFileOutput(Utils.NOME_ARQUIVO_FOTO, Context.MODE_PRIVATE);
result.compress(CompressFormat.JPEG, 100, fos);
fos.close();
Now here is the code using ImageMagick:
ImageInfo info = new ImageInfo(params[0].path_source);
MagickImage image = new MagickImage(info);
//I can optionally use sampleImage for better performance, but worse quality (still better then android canvas)
//image = image.sampleImage(new_sampled_width, new_sampled_height);
image = image.scaleImage(new_width, new_height);
image = image.cropImage(new Rectangle(x, y, 640, 640));
image = image.rotateImage(rotation);
byte blob[] = image.imageToBlob(info);
FileOutputStream fos = activity.openFileOutput(Utils.NOME_ARQUIVO_FOTO, Context.MODE_PRIVATE);
fos.write(blob);
fos.close();
Edit:
I'm using Android 2.3.4 on a Xperia Play for testing
Edit 2:
Saving with CompressFormat.PNG achieves near perfect result! Thanks FunkTheMonk for the tip! Only problem is that I use ImageMagick for blending this image with another later in the code, and I couldn't manage to build ImageMagick with PNG support
In onPictureTaken, I want to do the following:
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
My only requirement is that I'd like to save a high-quality photo... Seems I might have to sacrifice that.
On my Nexus 4 and newer devices, this code runs fine and as expected. But on older devices that have less memory, I'm running out of RAM! :(
How do I do the same image manipulation without running up against the memory limit?? I'm not trying to display these images on screen, so the solutions that have to do with a scaled down image don't really apply here...
you need to read the bitmap in with an increased sample size. the trick is finding the correct sample size that won't result in reduced resolution when you ultimately scale the image. i wrote a blog entry about it here that includes a nice utility class for scaling,
http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/
you could probably simplify that class quite a bit depending on your specific needs.
the jist is to read just the size of the bitmap. calculate the optimal sample size based on your desired scaled size, read the bitmap in using that sample size, then fine-scale it to exactly the size you want.
You have so many Bitmap object lying around. try recycling/reusing some of this.
Not exactly sure what is your requirement is but i can see you can save some memory by simply doing this.
Bitmap decodedPicture = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
matrix.preScale(-1.0f, 1.0f);
Bitmap picture = Bitmap.createBitmap(decodedPicture, 0, 0, decodedPicture.getWidth(), decodedPicture.getHeight(), matrix, false);
decodedPicture.recycle();
decodedPicture=null;
View v1 = mainLayout.getRootView();
v1.setDrawingCacheEnabled(true);
Bitmap screenshot = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
Bitmap scaledPicture = Bitmap.createScaledBitmap(picture, screenshot.getWidth(), screenshot.getHeight(), true);
picture.recycle();
picture=null;
Bitmap compos = Bitmap.createBitmap(scaledPicture.getWidth(), scaledPicture.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(compos);
canvas.drawBitmap(scaledPicture, new Matrix(), null);
canvas.drawBitmap(screenshot, new Matrix(), null);
MediaStore.Images.Media.insertImage(getContentResolver(), compos, "name" , "description");
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory())));
Also look into your memory footprint, make sure device wise memory you are using are is not too big.
FYI, on post honycomb devices bitmap pixel image allocated on native layer. You need recycle() or finalizer() to restore memory
Considering you don't want to resize your bitmap and don't want to display it, I'd do something like this:
Load the Bitmap with inJustDecodeBounds to see its original height and width (code from here)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Depending on the size and memory you have, you can directly process it from there (i.e. load the Bitmap) or proceed to load a number of chunks of said Bitmap with the Bitmap.createBitmap method that allows you to only load a chunk of data. Optionally: consider converting it into a byte array (see code below) and null+ recycle() before you process the chunk.
code
ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
bitmapPicture.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY, byteArrayBitmapStream);
byte[] b = byteArrayBitmapStream.toByteArray();
I use for work with Bitmap class WeakReference and after I always call recycle on the instance object WeakReference, the snippet code for rotate image:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPurgeable = true;
options.inInputShareable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
WeakReference<Bitmap> imageBitmapReference = new WeakReference<Bitmap>(BitmapFactory.decodeByteArray(params[0], 0, params[0].length, options));
Matrix mat = new Matrix();
mat.postRotate(90.0f);
imageBitmapReference = new WeakReference<Bitmap (Bitmap.createBitmap(imageBitmapReference.get(), 0, 0, resolution[0], resolution[1], mat, true));
FileOutputStream fos = new FileOutputStream(filename);
imageBitmapReference.get().compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
imageBitmapReference.get().recycle();
And second solution how work with Bitmap and don't get OutOfMemory Exception is use library Universal Image Loader
(Of course is so the third solution set in your AndroidManifest property android:largeHeap="true" and really DON'T USE THIS property).
The perfect material is on the http://developer.android.com/training/displaying-bitmaps/index.html and video https://www.youtube.com/watch?v=_CruQY55HOk