SubwayTooter-Android-App/app/src/main/java/jp/juggler/util/BitmapUtils.kt

242 lines
6.1 KiB
Kotlin
Raw Normal View History

2018-12-01 00:02:18 +01:00
package jp.juggler.util
import android.content.Context
import android.graphics.*
import android.net.Uri
import it.sephiroth.android.library.exif2.ExifInterface
import java.io.FileNotFoundException
2019-10-06 22:46:56 +02:00
import java.io.InputStream
import kotlin.math.max
import kotlin.math.sqrt
private val log = LogCategory("BitmapUtils")
2019-10-06 22:46:56 +02:00
val InputStream.imageOrientation : Int?
get() = try {
ExifInterface().apply {
readExif(
this@imageOrientation,
ExifInterface.Options.OPTION_IFD_0
or ExifInterface.Options.OPTION_IFD_1
or ExifInterface.Options.OPTION_IFD_EXIF
)
}.getTagIntValue(ExifInterface.TAG_ORIENTATION)
} catch(ex : Throwable) {
log.w(ex, "imageOrientation: exif parse failed.")
null
}
// EXIFのorientationが特定の値ならwとhを入れ替える
2019-10-06 22:46:56 +02:00
private fun rotateSize(orientation : Int?, w : Float, h : Float) : PointF =
when(orientation) {
5, 6, 7, 8 -> PointF(h, w)
else -> PointF(w, h)
}
2019-10-06 22:46:56 +02:00
enum class ResizeType {
None,
LongSide,
SquarePixel,
}
class ResizeConfig(
2019-10-06 22:46:56 +02:00
val type : ResizeType,
val size : Int
)
fun createResizedBitmap(
context : Context,
uri : Uri,
sizeLongSide : Int,
skipIfNoNeedToResizeAndRotate : Boolean = false
2019-10-06 22:46:56 +02:00
) =
createResizedBitmap(
context,
uri,
if(sizeLongSide <= 0)
ResizeConfig(ResizeType.None, 0)
else
ResizeConfig(ResizeType.LongSide, sizeLongSide),
skipIfNoNeedToResizeAndRotate = skipIfNoNeedToResizeAndRotate
)
fun createResizedBitmap(
context : Context,
uri : Uri,
resizeConfig : ResizeConfig,
skipIfNoNeedToResizeAndRotate : Boolean = false
) : Bitmap? {
try {
2019-10-06 22:46:56 +02:00
val orientation : Int? = context.contentResolver
.openInputStream(uri)?.use { it.imageOrientation }
// 画像のサイズを調べる
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
options.inScaled = false
options.outWidth = 0
options.outHeight = 0
context.contentResolver.openInputStream(uri)?.use { inStream ->
BitmapFactory.decodeStream(inStream, null, options)
}
var src_width = options.outWidth
var src_height = options.outHeight
if(src_width <= 0 || src_height <= 0) {
showToast(context, false, "could not get image bounds.")
return null
}
2019-10-06 22:46:56 +02:00
// 回転後のサイズ
2019-10-06 22:46:56 +02:00
val srcSize = rotateSize(orientation, src_width.toFloat(), src_height.toFloat())
val aspect = srcSize.x / srcSize.y
/// 出力サイズの計算
val sizeSpec = resizeConfig.size.toFloat()
2019-10-06 22:46:56 +02:00
val dstSize : PointF = when(resizeConfig.type) {
ResizeType.None ->
srcSize
2019-10-06 22:46:56 +02:00
ResizeType.LongSide ->
if(max(srcSize.x, srcSize.y) <= resizeConfig.size) {
srcSize
} else {
if(aspect >= 1f) {
PointF(
resizeConfig.size.toFloat(),
sizeSpec / aspect
)
} else {
PointF(
sizeSpec * aspect,
resizeConfig.size.toFloat()
)
}
}
2019-10-06 22:46:56 +02:00
ResizeType.SquarePixel -> {
val maxPixels = sizeSpec * sizeSpec
val currentPixels = srcSize.x * srcSize.y
2019-10-06 22:46:56 +02:00
if(currentPixels <= maxPixels) {
srcSize
2019-10-06 22:46:56 +02:00
} else {
val y = sqrt(maxPixels / aspect)
val x = aspect * y
2019-10-06 22:46:56 +02:00
PointF(x, y)
}
}
}
val dstSizeInt = Point(
2019-10-06 22:46:56 +02:00
max(1, (dstSize.x + 0.5f).toInt()),
max(1, (dstSize.y + 0.5f).toInt())
)
2019-10-06 22:46:56 +02:00
val reSizeRequired = dstSizeInt.x != srcSize.x.toInt() || dstSizeInt.y != srcSize.y.toInt()
// リサイズも回転も必要がない場合
if(skipIfNoNeedToResizeAndRotate
&& (orientation == null || orientation == 1)
2019-10-06 22:46:56 +02:00
&& ! reSizeRequired
) {
log.d("createOpener: no need to resize & rotate")
return null
}
// 長辺
2019-10-06 22:46:56 +02:00
val dstMax = max(dstSize.x, dstSize.y).toInt()
// inSampleSizeを計算
var bits = 0
2019-10-06 22:46:56 +02:00
var x = max(srcSize.x, srcSize.y).toInt()
while(x > 512 && x > dstMax * 2) {
++ bits
x = x shr 1
}
options.inJustDecodeBounds = false
options.inSampleSize = 1 shl bits
val sourceBitmap : Bitmap? =
context.contentResolver.openInputStream(uri)?.use { inStream ->
BitmapFactory.decodeStream(inStream, null, options)
}
if(sourceBitmap == null) {
showToast(context, false, "could not decode image.")
return null
}
try {
// サンプル数が変化している
src_width = options.outWidth
src_height = options.outHeight
2019-10-06 22:46:56 +02:00
val scale = dstMax.toFloat() / max(src_width, src_height)
val matrix = Matrix()
matrix.reset()
// 画像の中心が原点に来るようにして
matrix.postTranslate(src_width * - 0.5f, src_height * - 0.5f)
// スケーリング
matrix.postScale(scale, scale)
2019-10-06 22:46:56 +02:00
// 回転情報があれば回転
2019-10-06 22:46:56 +02:00
when(orientation) {
2 -> matrix.postScale(1f, - 1f) // 上下反転
3 -> matrix.postRotate(180f) // 180度回転
4 -> matrix.postScale(- 1f, 1f) // 左右反転
5 -> {
matrix.postScale(1f, - 1f)
matrix.postRotate(- 90f)
}
2019-10-06 22:46:56 +02:00
6 -> matrix.postRotate(90f)
7 -> {
matrix.postScale(1f, - 1f)
matrix.postRotate(90f)
}
8 -> matrix.postRotate(- 90f)
}
// 表示領域に埋まるように平行移動
matrix.postTranslate(dstSizeInt.x.toFloat() * 0.5f, dstSizeInt.y.toFloat() * 0.5f)
// 出力用Bitmap作成
2019-10-06 22:46:56 +02:00
var dst : Bitmap? =
Bitmap.createBitmap(dstSizeInt.x, dstSizeInt.y, Bitmap.Config.ARGB_8888)
try {
return if(dst == null) {
showToast(context, false, "bitmap creation failed.")
null
} else {
val canvas = Canvas(dst)
val paint = Paint()
paint.isFilterBitmap = true
canvas.drawBitmap(sourceBitmap, matrix, paint)
log.d(
"createResizedBitmap: resized to %sx%s",
dstSizeInt.x,
dstSizeInt.y
)
val tmp = dst
dst = null
tmp
}
} finally {
dst?.recycle()
}
} finally {
sourceBitmap.recycle()
}
} catch(ex : FileNotFoundException) {
log.w(ex, "not found. $uri")
} catch(ex : SecurityException) {
log.w(ex, "maybe we need pick up image again.")
} catch(ex : Throwable) {
log.trace(ex, "createResizedBitmap")
}
return null
}