I have a task to convert all pages of a PDF file to images and save it to cache. But when i create bitmap then recycle it, it seem not instantly be recycled. I think the problem is speed of create new bitmap is faster than recycle and then my app is crashed because OutOfMemoryError. Here is my example code to create image from a PDF:
for (index in 0 until pageCount) {
val pathImage = context?.cacheDir.toString() + index + TYPE_IMAGE
pdfiumCore.openPage(pdfDocument, index)
val width = pdfiumCore.getPageWidthPoint(pdfDocument, index)
var height = pdfiumCore.getPageHeightPoint(pdfDocument, index)
height = WIDTH_IMAGE_THUMB * height / width
val bitmap = Bitmap.createBitmap(WIDTH_IMAGE_THUMB, height, Bitmap.Config.ARGB_8888)
pdfiumCore.renderPageBitmap(pdfDocument, bitmap, index, 0, 0, WIDTH_IMAGE_THUMB, height)
images.add(ImageSelectPage(pathImage, false, index))
val fileOutputStream = FileOutputStream(pathImage)
bitmap.compress(Bitmap.CompressFormat.JPEG, QUALITY_CREATE_BITMAP, fileOutputStream)
bitmap.recycle() // here is recycle command
fileOutputStream.close()
index++
}
Don't know if there is a way to deal with this problem. I would like to hear any of your opinion. Thanks in advance!
Related
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
I have an imageView and several textViews
My app allows user to drag textViews on evey coordinates of imageView (imageView is not full screen) that user wants .
In other words this app allows user to add several captions to user image
and convert that image and captions to a single image and store it on user device.
According to one of stackOverFlow responses I can just convert one textView text to a bitamp
id there any way to screenshot from final image which user have created with its captions in kotlin??
This is my code:
#Throws(IOException::class)
fun foo(text: String) {
val textPaint = object : Paint() {
init {
setColor(Color.WHITE)
setTextAlign(Align.CENTER)
setTextSize(20f)
setAntiAlias(true)
}
}
val bounds = Rect()
textPaint.getTextBounds(text, 0, text.length, bounds)
val bmp = Bitmap.createBitmap(mImgBanner.getWidth(), mImgBanner.getHeight(), Bitmap.Config.RGB_565) //use ARGB_8888 for better quality
val canvas = Canvas(bmp)
canvas.drawText(text, 0, 20f, textPaint)
val path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/image.png"
val stream = FileOutputStream(path)
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
bmp.recycle()
stream.close()
}
Add desired views in xml layout inflate it and take screenshot of parent layout that is containing your views.
Code for taking screenshoot:
fun takeScreenshotOfView(view: View, height: Int, width: Int): Bitmap {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val bgDrawable = view.background
if (bgDrawable != null) {
bgDrawable.draw(canvas)
} else {
canvas.drawColor(Color.WHITE)
}
view.draw(canvas)
return bitmap
}
you can also use the extension View.drawToBitmap(). It will return a Bitmap
/**
* Return a [Bitmap] representation of this [View].
*
* The resulting bitmap will be the same width and height as this view's current layout
* dimensions. This does not take into account any transformations such as scale or translation.
*
* Note, this will use the software rendering pipeline to draw the view to the bitmap. This may
* result with different drawing to what is rendered on a hardware accelerated canvas (such as
* the device screen).
*
* If this view has not been laid out this method will throw a [IllegalStateException].
*
* #param config Bitmap config of the desired bitmap. Defaults to [Bitmap.Config.ARGB_8888].
*/
fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap {
if (!ViewCompat.isLaidOut(this)) {
throw IllegalStateException("View needs to be laid out before calling drawToBitmap()")
}
return Bitmap.createBitmap(width, height, config).applyCanvas {
translate(-scrollX.toFloat(), -scrollY.toFloat())
draw(this)
}
}
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?
I'm trying to optimise my app memory usage by following the recommended practice of using BitmapFactory.Options.inBitmap. What I want to do is slice a bitmap into stripes of height stripeHeight like this:
val stripeHeight = 10
val largeBitmap = // the original bitmap
val placeholder = // the bitmap object I want to re purpose for each stripe
val options = BitmapFactory.Options()
options.inBitmap = placeholder
val rows = largeBitmap.height / stripeHeight
for (i in 0 until rows) {
//how can I set the options object into createBitmap?
val stripe = Bitmap.createBitmap(largeBitmap, 0, i * stripeHeight, largeBitmap.width, stripeHeight)
it.onNext(stripe)
}
The problem is Bitmap.createBitmap won't take a BitmapFactory.Options in its method signature. So I'm unsure how to tell android to allocate the new bitmap into the memory space of the previous one.
One approach would be the following:
fun doBitmapProcessing() {
val STRIPE_HEIGHT = 10
//assuming your largeBitmap is a 320x240 ARGB8888 image
val largeBitmap: Bitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888)
//then allocate a buffer of size 320 * 240 * 4 (4 channels in an ARGB image) bytes
val byteBuffer: ByteBuffer = ByteBuffer.allocate(320 * 240 * 4)
//create a bitmap of desired dimensions for the placeholder
val placeholder: Bitmap = Bitmap.createBitmap(320, STRIPE_HEIGHT, Bitmap.Config.ARGB_8888)
//copy the pixels from your large bitmap to a pre-allocated byte buffer
largeBitmap.copyPixelsToBuffer(byteBuffer)
//create an input stream from the pre-allocated buffer
val inputStream: InputStream = ByteArrayInputStream(byteBuffer.array())
//create the bitmap region decoder
val bitmapRegionDecoder: BitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, true)
//create options, as before
val options = BitmapFactory.Options()
options.inBitmap = placeholder
val rows = largeBitmap.height / STRIPE_HEIGHT
for (i in 0 until rows) {
//call decode stream on the input stream containing your data from *largeImage*, and the options
//in which you specify that you want the memory of *placeholder* to be overwritten.
val region: Rect = Rect(0, i * STRIPE_HEIGHT, largeBitmap.width, i * STRIPE_HEIGHT + STRIPE_HEIGHT)
val stripe: Bitmap = bitmapRegionDecoder.decodeRegion(region, options)
it.onNext(stripe)
}
}
Be sure to read the documentation of BitmapFactory.Options#inBitmap and use the result of BitmapRegionDecoder.decodeRegion(...) instead of placeholder directly, since it is not guaranteed that the same memory location will be used.
There might be another solution through Bitmap.getPixels(...) & Bitmap.setPixels:
fun doBitmapProcessing2() {
val STRIPE_HEIGHT = 10
//assuming your largeBitmap is in ARGB 8888 format
val largeBitmap: Bitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888)
//create a bitmap of desired dimensions for the placeholder
val placeholder: Bitmap = Bitmap.createBitmap(320, STRIPE_HEIGHT, Bitmap.Config.ARGB_8888)
//create int pixel array in which the colors will be saved
val pixels = IntArray(320 * STRIPE_HEIGHT * 4)
val rows = largeBitmap.height / STRIPE_HEIGHT
for (i in 0 until rows) {
//save colors from desired location into the pixels array
largeBitmap.getPixels(pixels, 0, 320, 0, i * STRIPE_HEIGHT, 320, STRIPE_HEIGHT)
//write the colors saved into the array to the placeholder bitmap
placeholder.setPixels(pixels, 0, 320, 0, 0, 320, STRIPE_HEIGHT)
it.onNext(placeholder)
}
}
Although I'm not so sure about the second one. ( haven't used getPixels/setPixels before)
Hope this helps
I'm using a viewpager to load around 50 webviews... All the webviews are loaded into assests and each weview has an HTML page that is accessing around 70 images each... As i swipe, my app is crashing after around 30 pages, might be cos of the webviews still keep their reference to the images in the assests folder... Is there any way to release the webviews that the Viewpager is not using at that particular time?
awesomePager.setAdapter(new AwesomePagerAdapter(this, webviewdata));
Details:
Android WebView Memory Leak when loading html file from Assets
Failed adding to JNI local ref table (has 512 entries)
"Thread-375" prio=5 tid=15 RUNNABLE
while dynamically loading webview on viewpager
Logcat:
Try to scale down bitmap.Most of the time Bitmap is a main reason we get Memory issue.
Also learn about how to recycle the bitmaps.
Following snippet will help you.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile( filename, options );
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeFile( filename, options );
if ( bitmap != null && exact ) {
bitmap = Bitmap.createScaledBitmap( bitmap, width, height, false );
}
Also make sure you did override following method.
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((TextView) view);
}
Or you can create a function to Scale Down Bitmap
private byte[] resizeImage( byte[] input ) {
if ( input == null ) {
return null;
}
Bitmap bitmapOrg = BitmapFactory.decodeByteArray(input, 0, input.length);
if ( bitmapOrg == null ) {
return null;
}
int height = bitmapOrg.getHeight();
int width = bitmapOrg.getWidth();
int newHeight = 250;
float scaleHeight = ((float) newHeight) / height;
// creates matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleHeight, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,
width, height, matrix, true);
bitmapOrg.recycle();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
resizedBitmap.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
resizedBitmap.recycle();
return bos.toByteArray();
}
The WebView works with the JNI and this can only hold 512 local references, try loading your content directly from web and the problem shouldn't occur.
I get this problem when I override shouldInterceptRequest(WebView view, String url) in webviewclient and hand local references from my own caching mechanism.
This might be a bug in the webview itself. At least it isn't how it should behave if you ask me.
Setting layer type of webview to software works.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
But now you lose hardware acceleration and your webview may slow down.