Rotated bitmap is losing quality on every rotate - android

I am using the following function to rotate my bitmap. I noticed that if I rotate this bitmap a few times, it loses quality at every rotate upto a point that it becomes just a image with solid colour. I tried with PNG instead of JPEG but the size goes up drastically. Is there any solution to rotate the image without losing quality and increasing size?
fun rotatedImage(path:String):Bitmap{
val bitmap = BitmapFactory.decodeFile(path)
val mat = Matrix()
if (rotateDegree==270f) rotateDegree = 0f
mat.postRotate(rotateDegree+90)
val bMapRotate=Bitmap.createBitmap(bitmap, 0,0,bitmap.getWidth(),bitmap.getHeight(), mat, true)
deleteImageFile(path)
val fileStream = FileOutputStream(path)
// Tried with PNG as well = Size increases
bMapRotate.compress(Bitmap.CompressFormat.JPEG, 100, fileStream)
return bMapRotate
}

Related

Android: BitmapFactory changes dimensions of image when decoding from ByteArray

I am converting an android Image captured in my application to a bitmap. I am doing this by getting the image buffer from the pixel plane of the image and then using BitMapFactory to decode it into a Bitmap. However, doing so seems to change the resolution of the Image from 1920 x 1440 to 1800 x 1600, cropping out the top and bottom of the image in the process. The code for the method is shown here.
`protected void getImageFromBuffer(ImageReader reader){
Image image = null;
image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
System.out.println("Getting Image Ready");
synchronized (this){
image_to_upload = new byte[buffer.capacity()];
buffer.get(image_to_upload);
Bitmap storedBitmap = BitmapFactory.decodeByteArray(image_to_upload, 0, image_to_upload.length, null);
Matrix mat = new Matrix();
mat.postRotate(jpegOrientation); // angle is the desired angle you wish to rotate
storedBitmap = Bitmap.createBitmap(storedBitmap, 0, 0, storedBitmap.getWidth(), storedBitmap.getHeight(), mat, true);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
storedBitmap.compress(Bitmap.CompressFormat.JPEG,70, byteArrayOutputStream);
image_to_upload = byteArrayOutputStream.toByteArray();
image_ready = true;
System.out.println("Image Ready");
}
}`
Debugging shows that the height and width of the Image are correct before the buffer is converted to a bitmap, but the bitmap dimensions are wrong immediately after decodeByteArray. Can anyone suggest why this may be? I have checked the dimensions before applying the matrix transformation.
EDIT: To add further details, I have tried using BitmapFactory.Options() to disable scaling or to set the target density and neither have any impact on the resulting Bitmap, it is always size 1800 x 1600.
You can change some options that affects the result resolution of your bitmap by using the Options param to the decodeByteArray:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = DisplayMetrics.DENSITY_XXHIGH;//for example
BitmapFactory.decodeByteArray(image_to_upload, 0, image_to_upload.length, options);

How to properly grayscale bitmap on android to make the file lighter

I'm trying to get the most out of compression and I need to make my image grayscale in order to make the file lighter.
I found a function that makes image grayscale:
fun toGrayscale(bmpOriginal: Bitmap): Bitmap {
val width = bmpOriginal.width
val height = bmpOriginal.height
val bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val c = Canvas(bmpGrayscale)
val paint = Paint()
val cm = ColorMatrix()
cm.setSaturation(0f)
val f = ColorMatrixColorFilter(cm)
paint.colorFilter = f
c.drawBitmap(bmpOriginal, 0f, 0f, paint)
return bmpGrayscale
}
Then I save it with
try {
FileOutputStream(filePathGrayScale).use { out ->
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
} catch (e: IOException) {
e.printStackTrace()
}
But the image size increase!
What exactly I'm doing wrong and how to make the thing correct?
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out)
You are saving the image with an compression quality of 100. This maximizes file size (and minimizes quality loss).
If you want to make the most of compression, pass a lower value here. There is no ideal value because it really depends on the image being compressed and the tolerable amount of artefacts. It's a trade-of between file size and image quality.
For some more reading about the quality level see:
What quality to choose when converting to JPG?
And some examples 1, 2

Display long bitmap

I need to load a very long (approx 15K px) black-and-white bitmap which would have screen width.
I tried several approaches and the best seems using Glide:
private fun loadGlideScreenWideCompress(context: Context, imageView: AppCompatImageView) {
imageView.adjustViewBounds = true
val params = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
imageView.layoutParams = params
GlideApp.with(context)
.load(imageRes)
.encodeFormat(Bitmap.CompressFormat.WEBP)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView)
}
The problem is - image quality is lost. The image is blurred and text is not readable.
I tried to use BitmapRegionDecoder. I did not see the quality loss or memory issues. However, it decodes just a portion of the image. I do not really understand how to use it: to decode next part on scroll event? This would be hard to implement. Measure the drawable height and passing full height to the BitmapRegionDecoder intuitively feels wrong because this is the decoder is just for regions.
Another issue is that the image is large and I need it to work for all screen sizes. If I take the largest possible size and then scale it, I would need to perform expensive bitmap creating operations and block the main thread potentially.
The conventional approach does not work and give OOM exceptions:
val bitmap = BitmapFactory.Options().run {
inJustDecodeBounds = true
inPreferredConfig = Bitmap.Config.ALPHA_8
inDensity = displayMetrics.densityDpi
BitmapFactory.decodeResource(context.resources, imageRes, this)
}
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
imageView.setImageBitmap(bitmap)
Code with scaling down:
val res = context.resources
val display = res.displayMetrics
val dr = res.getDrawable(imageRes!!)
val original = (dr as BitmapDrawable).bitmap
val scale = original.width / display.widthPixels
val scaledBitmap = BitmapDrawable(res, Bitmap.createScaledBitmap(
original,
display.widthPixels,
original.height / scale,
true
))
imageView.adjustViewBounds = true
val bos = ByteArrayOutputStream()
scaledBitmap.bitmap.compress(CompressFormat.WEBP, 100, bos)
val decoder = BitmapRegionDecoder.newInstance(
ByteArrayInputStream(bos.toByteArray()),
false
)
val rect = Rect(
0,
0,
scaledBitmap.intrinsicWidth,
scaledBitmap.intrinsicHeight
)
val bitmapFactoryOptions = BitmapFactory.Options()
bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.ALPHA_8;
bitmapFactoryOptions.inDensity = display.densityDpi;
val bmp = decoder.decodeRegion(rect, bitmapFactoryOptions);
imageView.setImageBitmap(bmp)
So the question is: What would be the best approach in this situation and how to use BitmapRegionDecoder correctly?

Android - Rotated text on a large bitmap

I have a picture (bitmap) and I want to draw some shapes and rotated
text on it.
This works fine as long as the picture doesn't get too large. However,
when using a picture (2560 x 1920 pixels)taken with the build-in
camera of my android 2.1 phone, the result is distorted.
It looks like the rotation back, after drawing the rotated text, has
not been completed. Also, the distortion point is not always the same,
like it depends on the cpu usage.
You can see some resulting pictures here:
http://dl.dropbox.com/u/4751612/Result1.png
http://dl.dropbox.com/u/4751612/Result2.png
The code is executed inside a AsyncTask. The strange this is that this code works fine in one Activity, but not in another. In both activities the AsyncTask is executed when a button is clicked.
These are some excerpts of the code I'm using.
// Load the image from the MediaStore
c = MediaStore.Images.Media.query(context.getContentResolver(),
Uri.parse(drawing.fullImage), new String[] {MediaColumns.DATA});
if (c != null && c.moveToFirst()) {
imageFilePath = c.getString(0);
bitmap = ImageUtil.getBitmap(new File(imageFilePath), 10000);
}
c.close();
// Create a canvas to draw on
drawingBitmap = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(drawingBitmap);
// Draw image
canvas.drawBitmap(bitmap, 0, 0,
MeasureFactory.getMeasurePaint(context));
// calculate text width
rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
// Draw rotated text
canvas.save();
canvas.rotate(-angle, centerPoint.x, centerPoint.y);
canvas.drawText(text, centerPoint.x-Math.abs(rect.exactCenterX()),
Math.abs(centerPoint.y-rect.exactCenterY()), paint);
canvas.restore();
// Upload the bitmap to the Media Library
Uri uri =
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values);
OutputStream outStream = getContentResolver().openOutputStream(uri);
drawingBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outStream);
outStream.flush();
outStream.close();
Thanks in advance for any help.
Since it works as long as the resolution isn't too high, I would just rescale all images to something that works.
You can accomplish this using
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 800 /* width */, 600 /* height */, true);
This turned out to be a memory problem although no OutOfMemoryException was visible in the log.
So, I "solved" it by scaling the image if the resolution is too high, as suggested by ingo. The problem is that I don't know how to determine the limits of a device. I suppose they are different for every device and depends on the current memory usage.

Bitmap resizing and rotating: linear noise

I am resizing image and rotating it using Matrix:
Matrix mtx = new Matrix();
if(orientation>0) {
mtx.postRotate(orientation);
Log.d(TAG,"image rotated: "+orientation);
}
if(scale<1) {
mtx.postScale(scale,scale);
Log.d(TAG,"image scaled: "+scale);
}
bmp = Bitmap.createBitmap(bm_orig, 0, 0, width, height, mtx, true);
bm_orig.recycle();
bmp.compress(Bitmap.CompressFormat.JPEG,95,output);
bmp.recycle();
When bmp_orig is taken, used 3.2 Mpx Camera, image resized and rotated looks normal.
But when source is 4 Mpx or bigger, result after resizing has barely-noticeable linear noise
I dont know, why does this noise appear, and how to remove it.
Any idea?
May be another way to resize and rotate?
Found that this problem is related with source and resulting image size.
Solved it, when check image size before loading it, and then load halfsized image, if source image size is more than 2 times bigger than resulting size is needed.
BitmapFactory.Options options_to_get_size = new BitmapFactory.Options();
options_to_get_size.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, options_to_get_size);
int load_scale = 1; // load 100% sized image
int width_tmp=options_to_get_size.outWidth
int height_tmp=options_to_get_size.outHeight;
while(width_tmp/2>maxW && height_tmp/2>maxH){
width_tmp/=2;//load half sized image
height_tmp/=2;
load_scale*=2;
}
Log.d(TAG,"load inSampleSize: "+ load_scale);
//Now load image with precalculated scale. scale must be power of 2 (1,2,4,8,16...)
BitmapFactory.Options option_to_load = new BitmapFactory.Options();
option_to_load.inSampleSize = load_scale;
((FileInputStream)input).getChannel().position(0); # reset input stream to read again
Bitmap bm_orig = BitmapFactory.decodeStream(input,null,option_to_load);
input.close();
//now you can resize and rotate image using matrix as stated in question

Categories

Resources