I am trying to rotate Img(Bitmap), by the fallowing code. its working fine up to 5-6 rotations, after that am getting OME?
private void rotateImg() {
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
matrix.postRotate(curRotate);
try {
temp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(),
temp.getHeight(), matrix, true);
setImage.setImageBitmap(temp);
} catch (OutOfMemoryError e) {
curRotate = curRotate - 45.0f;
Toast.makeText( this,"Out Of Memory",Toast.LENGTH_LONG).show();
}
}
here "test" is an static bitmap file loaded from SDCard.
Why are you creating the bitmap every time? Is there any specific reason?
If not use the following code:
private void rotateImg() {
int cx = temp.getWidth() / 2;
int cy = temp.getHeight() / 2;
matrix.preTranslate(-cx, -cy);
matrix.postRotate(curRotate);
matrix.postTranslate(cx, cy);
setImage.setImageMatrix(matrix);
}
The first answer is probably one potential solution. The issue here is that you're creating a lot of Bitmap objects, which are fairly big, and they're not getting gc'd for whatever reason.
A better solution might be to use a single bitmap, and apply the rotation / scaling when you draw it. For example, if you were drawing on a Canvas that's part of a View, rotateImg could simply rotate the matrix and call invalidate on the view, and then in the view's onDraw method you'd use the void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint) on the canvas to render the bitmap. Docs are here.
Maybe you have to use bitmap.recycle() method each time you call this method. Try something like this,
private void rotateImg() {
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
matrix.postRotate(curRotate);
try {
temp.recycle(); //removes the memory occupied by this bitmap object
temp=null;
temp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(),
temp.getHeight(), matrix, true);
setImage.setImageBitmap(temp);
} catch (OutOfMemoryError e) {
curRotate = curRotate - 45.0f;
Toast.makeText( this,"Out Of Memory",Toast.LENGTH_LONG).show();
}
}
Related
I am using Android's Fingerpaint demo[1] to experiment with Canvas and Bitmaps. I want to draw an object on a screen, and continue drawing the object after the screen has rotated. The Fingerpaint demo erases the screen after a screen rotation - I want to preserve the contents of the screen and just rotate it along with the screen.
With my code, I can rotate the screen and the image I've drawn. But I'm no longer able to add any additional path markings to the bitmap. It becomes like a read-only Bitmap. Does anyone know what I am I doing wrong?
Here's the code where I save the image and restore it after a rotation. Notice that I am saving it as a PNG in a byte array (in onSaveInstanceState()) and then creating a new Bitmap from that byte array in onCreate() (I think this is okay?):
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.d("TAG", "Saving state...");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
canvasView.mBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
imageByteArray = stream.toByteArray();
savedInstanceState.putSerializable("ByteArray", imageByteArray);
super.onSaveInstanceState(savedInstanceState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
canvasView = new MyView(this);
setContentView(canvasView);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFF00FF00);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
if (savedInstanceState != null) {
Log.d("TAG", "Restoring any bitmaps...");
byte[] imageByteArray = (byte[]) savedInstanceState.getSerializable("ByteArray");
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap savedImage = BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length, opt);
canvasView.mBitmap = savedImage;
}
}
In my custom view, MyView, here's the code where I rotate the bitmap when the screen changes:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int orientation = display.getRotation();
Log.d("CANVAS", "Rotation: " + orientation);
if (mBitmap == null) {
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
} else {
Matrix rotator = new Matrix();
rotator.postRotate(orientation * 3);
Bitmap rotatedBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), rotator, true);
mCanvas = new Canvas(rotatedBitmap);
mCanvas.drawBitmap(rotatedBitmap, 0, 0, mPaint);
}
}
Just about everything else is the same as in the Fingerpaint demo. I can push down to make markings on the screen, but when I lift my finger the path I've created is not applied to the bitmap.
Here's a duplication of the onTouchEvent() to illustrate (I did not modify it):
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
Thanks in advance for any insight. I suspect my understanding of how Canvas should work is not correct, and hence my confusion!
[1] The full Fingerpaint demo is here: https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/graphics/FingerPaint.java
BitmapFactory.decodeByteArray() ignores the option where you asked for a mutable bitmap and gives you one that isn't mutable because that's how it rolls.
https://developer.android.com/reference/android/graphics/BitmapFactory.html#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)
decodeByteArray
Bitmap decodeByteArray (byte[] data,
int offset,
int length,
BitmapFactory.Options opts)
Decode an immutable bitmap from the specified byte array.
You need to create a new bitmap object, using one of the ways to create a mutable bitmap. Then paint the immutable bitmap into the mutable one. Then toss the one you loaded with that function.
The reason for the immutability is that the bitmap was created from those bytes you gave it. So for speed they are backed by those bytes. They are the same memory. And the way actual bitmap objects work is different than that, and you can't just magically make a mutable bitmap by forcing those bytes from normal memory into the GPU. You can just make a second bitmap object and paint the data into that second bitmap.
You must control your configuration change for the activity.
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="#string/app_name">
For more details just go for https://developer.android.com/guide/topics/resources/runtime-changes.html
Hope it helps..
When I use following code, it ends up with outofmemory exception. After doing researh Render script looks like a good candidate. Where can I find sample code for similar operation and how can integrate it to my project.
public Bitmap rotateBitmap(Bitmap image, int angle) {
if (image != null) {
Matrix matrix = new Matrix();
matrix.postRotate(angle, (image.getWidth()) / 2,
(image.getHeight()) / 2);
return Bitmap.createBitmap(image, 0, 0, image.getWidth(),
image.getHeight(), matrix, true);
}
return null;
}
Basically rotating bitmap is a task of rotating 2D array without using additional memory. And this is the correct implementation with RenderScript: Android: rotate image without loading it to memory .
But this is not necessary if all you want is just to display rotated Bitmap. You can simply extend ImageView and rotate the Canvas while drawing on it:
canvas.save();
canvas.rotate(angle, X + (imageW / 2), Y + (imageH / 2));
canvas.drawBitmap(imageBmp, X, Y, null);
canvas.restore();
What about ScriptIntrinsic, since it's just a built-in RenderScript kernels for common operations you cannot do nothing above the already implemented functions: ScriptIntrinsic3DLUT, ScriptIntrinsicBLAS, ScriptIntrinsicBlend, ScriptIntrinsicBlur, ScriptIntrinsicColorMatrix, ScriptIntrinsicConvolve3x3, ScriptIntrinsicConvolve5x5, ScriptIntrinsicHistogram, ScriptIntrinsicLUT, ScriptIntrinsicResize, ScriptIntrinsicYuvToRGB. They do not include functionality to rotate bitmap at the moment so you should create your own ScriptC script.
Try this code..
private Bitmap RotateImage(Bitmap _bitmap, int angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
_bitmap = Bitmap.createBitmap(_bitmap, 0, 0, _bitmap.getWidth(), _bitmap.getHeight(), matrix, true);
return _bitmap;
}
Use this code when select image from gallery.
like this..
File _file = new File(file_name);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap bitmap = BitmapFactory.decodeFile(file_name, options);
try {
ExifInterface exif = new ExifInterface(file_name);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
bitmap = RotateImage(bitmap, 90);
} else if (orientation ==ExifInterface.ORIENTATION_ROTATE_270) {
bitmap = RotateImage(bitmap, 270);
}
} catch (Exception e) {
e.printStackTrace();
}
image_holder.setImageBitmap(bitmap);
I am using janmuller android library to perform rotation of an image . On rotating it 90 degrees to the left or right the image is rotated , however it gets de centred on the screen . This is the code I have used from janmuller library
public void onRotateRight(View v) {
mBitmap = Util.rotateImage(mBitmap, 90);
RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
mRunFaceDetection.run();
}
This is the rotateImage function
public static Bitmap rotateImage(Bitmap src, float degree) {
// create new matrix
Matrix matrix = new Matrix();
// setup rotation degree
matrix.postRotate(degree);
Bitmap bmp = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
return bmp;
}
public void onRotateRight(View v) {
mBitmap = Util.rotateImage(mBitmap, 90);
RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
mRunFaceDetection.run();
}
I wonder why you have so much code. Especially i wonder that after having used rotateImage() on the bitmap you dont put it back in the imageview. There is so much code with rotate (why is that needed?) and reset base (what does that?) that it is difficult to say who is to blame when things go wrong. Better try with a normal ImageView first before you blame janmuller. More like this:
public void onRotateRight(View v) {
mBitmap = Util.rotateImage(mBitmap, 90);
mImageView.setImageBitMap(mBitmap);
}
That should be enough to test;
The function rotateImage() looks ok.
I need a bit of help. I have an ImageView with a touch listener, and I am able to capture all touch inputs into a matrix, and apply that matrix to the ImageView and VOILA! the image pans and zooms appropriately.
Here is the trouble: I'd now like to CROP the image in such a way that it ALWAYS ends up the same size; eg a 300x300 image.
In other words, suppose I have a 300x300 square in the middle of my screen, a user pans and zooms an image until an item of interest fits into that square, and hits "next". I would like to have a resulting image that has cropped the photo to only be the 300x300 portion that was contained in the box.
Make sense?? Please help! Thanks comrades! See a bit of code below for what I have tried thus far.
float[] matrixVals = new float[9];
ImageTouchListener.persistedMatrix.getValues(matrixVals);
model.setCurrentBitmap(Bitmap.createBitmap(model.getOriginalBitmap(), 0, 0, model.getTargetWidth(), model.getTargetHeight(), ImageTouchListener.persistedMatrix, true));
model.setCurrentBitmap(Bitmap.createBitmap(model.getCurrentBitmap(), Math.round(matrixVals[Matrix.MTRANS_X]), Math.round(matrixVals[Matrix.MTRANS_Y]), model.getTargetWidth(), model.getTargetHeight(), null, false));
Finally, I would also like to be able to SHRINK the image into the box, where the edges may actually need to be filled in with black or white or some kind of border... So far, everything I do other than no pan or zoom at all crashes when I hit next.
Thanks again!
see this custom ImageView, the most important part is onTouchEvent where cropped Bitmap is created and saved to /sdcard for verification:
class IV extends ImageView {
Paint paint = new Paint();
Rect crop = new Rect();
public IV(Context context) {
super(context);
paint.setColor(0x660000ff);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
crop.set(w / 2, h / 2, w / 2, h / 2);
crop.inset(-75, -75);
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setScaleType(ScaleType.MATRIX);
Matrix m = getImageMatrix();
m.postScale(2, 2);
m.postTranslate(40, 30);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Bitmap croppedBitmap = Bitmap.createBitmap(crop.width(), crop.height(), Config.ARGB_8888);
Canvas c = new Canvas(croppedBitmap);
c.translate(-crop.left, -crop.top);
c.concat(getImageMatrix());
getDrawable().draw(c);
// just save it for test verification
try {
OutputStream stream = new FileOutputStream("/sdcard/test.png");
croppedBitmap.compress(CompressFormat.PNG, 100, stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(crop, paint);
}
}
It's not really clear for me what your problem is, a calculation or a drawing problem ... if it is a drawing problem I might have the solution ...
Create a new bitmap, get the canvas and draw the correct rect of the big image into the new smaller one ....
Bitmap bigPicture; //your big picture
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Rect source = ...
Rect dest = ...
c.drawBitmap(bigPicture, source, dest, paint);
Now if your problem is a calculation problem I might have a solution too ...
I have an app that displays quite a few images for the user, and we've been seeing a lot of error reports with OutOfMemoryError exception.
What we currently do is this:
// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
// Rotate it to show as a landscape
Matrix m = image.getImageMatrix();
m.postRotate(90);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
image.setImageBitmap(bmp);
The obvious problem with this is that we have to recreate the bitmap from the image on memory and rotate the matrix, this is quite expensive for the memory.
My question is simple:
Is there a better way to rotate images without causing OutOfMemoryError?
2 methods of rotating a large image:
using JNI , like on this post.
using a file : it's a very slow way (depending on the input and the device , but still very slow) , which puts the decoded rotated image into the disk first , instead of putting it into the memory .
code of using a file is below:
private void rotateCw90Degrees()
{
Bitmap bitmap=BitmapFactory.decodeResource(getResources(),INPUT_IMAGE_RES_ID);
// 12 => 7531
// 34 => 8642
// 56 =>
// 78 =>
final int height=bitmap.getHeight();
final int width=bitmap.getWidth();
try
{
final DataOutputStream outputStream=new DataOutputStream(new BufferedOutputStream(openFileOutput(ROTATED_IMAGE_FILENAME,Context.MODE_PRIVATE)));
for(int x=0;x<width;++x)
for(int y=height-1;y>=0;--y)
{
final int pixel=bitmap.getPixel(x,y);
outputStream.writeInt(pixel);
}
outputStream.flush();
outputStream.close();
bitmap.recycle();
final int newWidth=height;
final int newHeight=width;
bitmap=Bitmap.createBitmap(newWidth,newHeight,bitmap.getConfig());
final DataInputStream inputStream=new DataInputStream(new BufferedInputStream(openFileInput(ROTATED_IMAGE_FILENAME)));
for(int y=0;y<newHeight;++y)
for(int x=0;x<newWidth;++x)
{
final int pixel=inputStream.readInt();
bitmap.setPixel(x,y,pixel);
}
inputStream.close();
new File(getFilesDir(),ROTATED_IMAGE_FILENAME).delete();
saveBitmapToFile(bitmap); //for checking the output
}
catch(final IOException e)
{
e.printStackTrace();
}
}
you can try:
image.setImageBitmap(null);
// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
// Rotate it to show as a landscape
Matrix m = image.getImageMatrix();
m.postRotate(90);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
}
BitmapDrawable bd = new BitmapDrawable(mContext.getResources(), bmp);
bmp.recycle();
bmp = null;
setImageDrawable(bd);
bd = null;
When working with lots of Bitmaps be sure to call recycle() on them as soon as they are not needed. This call will instantly free memory associated with a particular bitmap.
In your case if you do not need the original bitmap after rotation, then recycle it. Something along the lines of:
Bitmap result = bmp;
// Check if image is a landscape image
if (bmp.getWidth() > bmp.getHeight()) {
// Rotate it to show as a landscape
Matrix m = image.getImageMatrix();
m.postRotate(90);
result = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
// rotating done, original not needed => recycle()
bmp.recycle();
}
image.setImageBitmap(result);