Created
June 25, 2018 10:02
-
-
Save ozodrukh/1985f5b7b73f9749dcee20554b11351e to your computer and use it in GitHub Desktop.
Very simple Bitmap loading from Disk & Assets, it's taken from Picasso (c)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.ozodrukh.meet | |
import android.app.ActivityManager | |
import android.content.Context | |
import android.content.pm.ApplicationInfo | |
import android.graphics.Bitmap | |
import android.graphics.BitmapFactory | |
import android.graphics.BitmapFactory.Options | |
import android.graphics.BitmapShader | |
import android.graphics.Matrix | |
import android.graphics.Paint | |
import android.graphics.Picture | |
import android.graphics.Shader.TileMode.CLAMP | |
import android.media.ExifInterface.ORIENTATION_FLIP_HORIZONTAL | |
import android.media.ExifInterface.ORIENTATION_FLIP_VERTICAL | |
import android.media.ExifInterface.ORIENTATION_ROTATE_180 | |
import android.media.ExifInterface.ORIENTATION_ROTATE_270 | |
import android.media.ExifInterface.ORIENTATION_ROTATE_90 | |
import android.media.ExifInterface.ORIENTATION_TRANSPOSE | |
import android.media.ExifInterface.ORIENTATION_TRANSVERSE | |
import android.net.Uri | |
import android.util.LruCache | |
import android.view.Gravity | |
import android.webkit.URLUtil | |
import io.reactivex.Single | |
import java.io.IOException | |
class BitmapHunter(context: Context) { | |
companion object { | |
const val ASSET_LEADING_PATH = "/android_asset/" | |
fun calculateMemoryCacheSize(context: Context): Int { | |
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager | |
val largeHeap = context.getApplicationInfo().flags and ApplicationInfo.FLAG_LARGE_HEAP != 0 | |
val memoryClass = if (largeHeap) am.getLargeMemoryClass() else am.getMemoryClass() | |
// Target ~15% of the available heap. | |
return (1024L * 1024L * memoryClass.toLong() / 7).toInt() | |
} | |
fun calculateInSampleSize(reqWidth: Int, reqHeight: Int, width: Int, height: Int, | |
options: BitmapFactory.Options, centerInside: Boolean) { | |
var sampleSize = 1 | |
if (height > reqHeight || width > reqWidth) { | |
val heightRatio: Int | |
val widthRatio: Int | |
if (reqHeight == 0) { | |
sampleSize = Math.floor((width.toFloat() / reqWidth.toFloat()).toDouble()).toInt() | |
} else if (reqWidth == 0) { | |
sampleSize = Math.floor((height.toFloat() / reqHeight.toFloat()).toDouble()).toInt() | |
} else { | |
heightRatio = Math.floor((height.toFloat() / reqHeight.toFloat()).toDouble()).toInt() | |
widthRatio = Math.floor((width.toFloat() / reqWidth.toFloat()).toDouble()).toInt() | |
sampleSize = if (centerInside) | |
Math.max(heightRatio, widthRatio) | |
else | |
Math.min(heightRatio, widthRatio) | |
} | |
} | |
options.inSampleSize = sampleSize | |
options.inJustDecodeBounds = false | |
} | |
fun transformResult(data: BitmapRequestOptions, result: Bitmap, exifOrientation: Int): Bitmap { | |
var result = result | |
val inWidth = result.width | |
val inHeight = result.height | |
val onlyScaleDown = data.onlyScaleDown | |
var drawX = 0 | |
var drawY = 0 | |
var drawWidth = inWidth | |
var drawHeight = inHeight | |
val matrix = Matrix() | |
if (data.hasSize() || exifOrientation != 0) { | |
var targetWidth = data.targetWidth | |
var targetHeight = data.targetHeight | |
// EXIf interpretation should be done before cropping in case the dimensions need to | |
// be recalculated | |
if (exifOrientation != 0) { | |
val exifRotation = getExifRotation(exifOrientation) | |
val exifTranslation = getExifTranslation(exifOrientation) | |
if (exifRotation != 0) { | |
matrix.preRotate(exifRotation * 1f) | |
if (exifRotation == 90 || exifRotation == 270) { | |
// Recalculate dimensions after exif rotation | |
val tmpHeight = targetHeight | |
targetHeight = targetWidth | |
targetWidth = tmpHeight | |
} | |
} | |
if (exifTranslation != 1) { | |
matrix.postScale(exifTranslation * 1f, 1f) | |
} | |
} | |
if (data.centerCrop) { | |
// Keep aspect ratio if one dimension is set to 0 | |
val widthRatio = | |
if (targetWidth != 0) targetWidth / inWidth.toFloat() else targetHeight / inHeight.toFloat() | |
val heightRatio = | |
if (targetHeight != 0) targetHeight / inHeight.toFloat() else targetWidth / inWidth.toFloat() | |
val scaleX: Float | |
val scaleY: Float | |
if (widthRatio > heightRatio) { | |
val newSize = Math.ceil((inHeight * (heightRatio / widthRatio)).toDouble()).toInt() | |
if (data.centerCropGravity and Gravity.TOP == Gravity.TOP) { | |
drawY = 0 | |
} else if (data.centerCropGravity and Gravity.BOTTOM == Gravity.BOTTOM) { | |
drawY = inHeight - newSize | |
} else { | |
drawY = (inHeight - newSize) / 2 | |
} | |
drawHeight = newSize | |
scaleX = widthRatio | |
scaleY = targetHeight / drawHeight.toFloat() | |
} else if (widthRatio < heightRatio) { | |
val newSize = Math.ceil((inWidth * (widthRatio / heightRatio)).toDouble()).toInt() | |
if (data.centerCropGravity and Gravity.LEFT == Gravity.LEFT) { | |
drawX = 0 | |
} else if (data.centerCropGravity and Gravity.RIGHT == Gravity.RIGHT) { | |
drawX = inWidth - newSize | |
} else { | |
drawX = (inWidth - newSize) / 2 | |
} | |
drawWidth = newSize | |
scaleX = targetWidth / drawWidth.toFloat() | |
scaleY = heightRatio | |
} else { | |
drawX = 0 | |
drawWidth = inWidth | |
scaleY = heightRatio | |
scaleX = scaleY | |
} | |
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) { | |
matrix.preScale(scaleX, scaleY) | |
} | |
} else if (data.centerInside) { | |
// Keep aspect ratio if one dimension is set to 0 | |
val widthRatio = | |
if (targetWidth != 0) targetWidth / inWidth.toFloat() else targetHeight / inHeight.toFloat() | |
val heightRatio = | |
if (targetHeight != 0) targetHeight / inHeight.toFloat() else targetWidth / inWidth.toFloat() | |
val scale = if (widthRatio < heightRatio) widthRatio else heightRatio | |
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) { | |
matrix.preScale(scale, scale) | |
} | |
} else if ((targetWidth != 0 || targetHeight != 0) // | |
&& (targetWidth != inWidth || targetHeight != inHeight)) { | |
// If an explicit target size has been specified and they do not match the results bounds, | |
// pre-scale the existing matrix appropriately. | |
// Keep aspect ratio if one dimension is set to 0. | |
val sx = | |
if (targetWidth != 0) targetWidth / inWidth.toFloat() else targetHeight / inHeight.toFloat() | |
val sy = | |
if (targetHeight != 0) targetHeight / inHeight.toFloat() else targetWidth / inWidth.toFloat() | |
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) { | |
matrix.preScale(sx, sy) | |
} | |
} | |
} | |
val newResult = Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true) | |
if (newResult != result) { | |
result.recycle() | |
result = newResult | |
} | |
return result | |
} | |
private fun shouldResize(onlyScaleDown: Boolean, inWidth: Int, inHeight: Int, | |
targetWidth: Int, targetHeight: Int): Boolean { | |
return (!onlyScaleDown || targetWidth != 0 && inWidth > targetWidth | |
|| targetHeight != 0 && inHeight > targetHeight) | |
} | |
fun getExifRotation(orientation: Int): Int { | |
val rotation: Int | |
when (orientation) { | |
ORIENTATION_ROTATE_90, ORIENTATION_TRANSPOSE -> rotation = 90 | |
ORIENTATION_ROTATE_180, ORIENTATION_FLIP_VERTICAL -> rotation = 180 | |
ORIENTATION_ROTATE_270, ORIENTATION_TRANSVERSE -> rotation = 270 | |
else -> rotation = 0 | |
} | |
return rotation | |
} | |
fun getExifTranslation(orientation: Int): Int { | |
val translation: Int | |
when (orientation) { | |
ORIENTATION_FLIP_HORIZONTAL, ORIENTATION_FLIP_VERTICAL, ORIENTATION_TRANSPOSE, ORIENTATION_TRANSVERSE -> translation = | |
-1 | |
else -> translation = 1 | |
} | |
return translation | |
} | |
} | |
private val context = context.applicationContext | |
private val bitmapsCache = PlatformLruCache<String>(calculateMemoryCacheSize(context)) | |
private val picturesCache = LruCache<String, Picture>(100) | |
fun getCachedBitmap(file: Uri, options: BitmapRequestOptions): Bitmap? { | |
return bitmapsCache[options.getKey(file)] | |
} | |
fun getCachedPicture(file: Uri, options: PictureRequest): Picture? { | |
return picturesCache[options.getKey(file)] | |
} | |
fun clearCache() { | |
bitmapsCache.clear() | |
picturesCache.evictAll() | |
} | |
fun getPicture(file: Uri, bitmap: Bitmap, options: PictureRequest): Picture { | |
val cachedPicture = Picture() | |
val width = if (options.size == 0) bitmap.width else options.size | |
val height = if (options.size == 0) bitmap.height else options.size | |
val canvas = cachedPicture.beginRecording(width, height) | |
if (options.circle) { | |
val paint = Paint(Paint.ANTI_ALIAS_FLAG) | |
paint.shader = BitmapShader(bitmap, CLAMP, CLAMP) | |
val size = if (options.size == 0) bitmap.width else options.size | |
canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint) | |
} else { | |
canvas.drawBitmap(bitmap, 0f, 0f, null) | |
} | |
cachedPicture.endRecording() | |
picturesCache.put(options.getKey(file), cachedPicture) | |
return cachedPicture | |
} | |
private fun getBitmapByOptions(file: Uri, options: Options): Bitmap? { | |
if (URLUtil.isAssetUrl(file.toString())) { | |
val path = file.path.removePrefix(ASSET_LEADING_PATH) | |
return BitmapFactory.decodeStream(context.assets.open(path), null, options) | |
} else if (URLUtil.isFileUrl(file.toString())) { | |
return BitmapFactory.decodeFile(file.path, options) | |
} else { | |
throw RuntimeException("Unsupported uri=$file") | |
} | |
} | |
fun getBitmap(file: Uri, options: BitmapRequestOptions): Single<Bitmap> { | |
return Single | |
.fromCallable { | |
val opts = BitmapFactory.Options() | |
opts.inPreferredConfig = options.config | |
opts.inJustDecodeBounds = true | |
getBitmapByOptions(file, opts) | |
return@fromCallable opts | |
} | |
.flatMap { opts -> | |
calculateInSampleSize(options.targetWidth, options.targetHeight, | |
opts.outWidth, opts.outHeight, | |
opts, | |
options.centerInside) | |
val decodedImage = getBitmapByOptions(file, opts) | |
if (decodedImage == null) { | |
Single.error(IOException("Couldn't decode stream")) | |
} else { | |
Single.just(transformResult(options, decodedImage, 0)) | |
} | |
} | |
.doOnSuccess { | |
bitmapsCache[options.getKey(file)] = it | |
} | |
} | |
class PictureRequest( | |
val circle: Boolean = false, | |
val size: Int = 0, | |
val bitmapOptions: BitmapRequestOptions = BitmapRequestOptions(size, size)) { | |
fun getKey(file: Uri): String { | |
return "$file;circle:$circle;size:$size;" | |
} | |
} | |
class BitmapRequestOptions( | |
val targetWidth: Int, | |
val targetHeight: Int, | |
val config: Bitmap.Config = Bitmap.Config.RGB_565, | |
val centerInside: Boolean = false, | |
val centerCrop: Boolean = false, | |
val centerCropGravity: Int = Gravity.CENTER, | |
val onlyScaleDown: Boolean = false) { | |
constructor(bitmapOptions: BitmapRequestOptions, | |
targetWidth: Int = bitmapOptions.targetHeight, | |
targetHeight: Int = bitmapOptions.targetWidth, | |
config: Bitmap.Config = bitmapOptions.config, | |
centerInside: Boolean = bitmapOptions.centerInside, | |
centerCrop: Boolean = bitmapOptions.centerCrop, | |
centerCropGravity: Int = bitmapOptions.centerCropGravity, | |
onlyScaleDown: Boolean = bitmapOptions.onlyScaleDown) | |
: this(targetWidth, targetHeight, | |
config, centerInside, centerCrop, centerCropGravity, onlyScaleDown) | |
init { | |
if (centerCrop && centerInside) { | |
throw IllegalArgumentException("Both centerCrop & centerInside features couldn't be turned on") | |
} | |
} | |
fun hasSize(): Boolean { | |
return targetHeight != 0 || targetWidth != 0 | |
} | |
fun getKey(file: Uri): String { | |
val builder = StringBuilder() | |
builder.append(file).append(";") | |
if (hasSize()) { | |
builder.append("resize:").append(targetWidth).append('x').append(targetHeight) | |
.append(";") | |
} | |
if (centerCrop) { | |
builder.append("centerCrop:").append(centerCropGravity).append(";") | |
} else if (centerInside) { | |
builder.append("centerInside:").append(";") | |
} | |
return builder.toString() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment