268 lines
6.5 KiB
Kotlin
268 lines
6.5 KiB
Kotlin
package jp.juggler.util
|
|
|
|
import android.content.Context
|
|
import android.graphics.*
|
|
import androidx.exifinterface.media.ExifInterface
|
|
import android.net.Uri
|
|
//import it.sephiroth.android.library.exif2.ExifInterface
|
|
import java.io.FileNotFoundException
|
|
import java.io.InputStream
|
|
import kotlin.math.max
|
|
import kotlin.math.sqrt
|
|
|
|
private val log = LogCategory("BitmapUtils")
|
|
|
|
fun InputStream.imageOrientation() : Int? =
|
|
try {
|
|
ExifInterface(this)
|
|
// .readExif(
|
|
// this@imageOrientation,
|
|
// ExifInterface.Options.OPTION_IFD_0
|
|
// or ExifInterface.Options.OPTION_IFD_1
|
|
// or ExifInterface.Options.OPTION_IFD_EXIF
|
|
// )
|
|
.getAttributeInt(ExifInterface.TAG_ORIENTATION, - 1)
|
|
.takeIf { it >= 0 }
|
|
} catch(ex : Throwable) {
|
|
log.w(ex, "imageOrientation: exif parse failed.")
|
|
null
|
|
}
|
|
|
|
// 回転情報の値に合わせて、wとhを入れ替える
|
|
fun rotateSize(orientation : Int?, w : Float, h : Float) : PointF =
|
|
when(orientation) {
|
|
5, 6, 7, 8 -> PointF(h, w)
|
|
else -> PointF(w, h)
|
|
}
|
|
|
|
// 回転情報を解決するようにmatrixに回転を加える
|
|
fun Matrix.resolveOrientation(orientation : Int?) : Matrix {
|
|
when(orientation) {
|
|
2 -> postScale(1f, - 1f)
|
|
3 -> postRotate(180f)
|
|
4 -> postScale(- 1f, 1f)
|
|
|
|
5 -> {
|
|
postScale(1f, - 1f)
|
|
postRotate(- 90f)
|
|
}
|
|
|
|
6 -> postRotate(90f)
|
|
|
|
7 -> {
|
|
postScale(1f, - 1f)
|
|
postRotate(90f)
|
|
}
|
|
|
|
8 -> postRotate(- 90f)
|
|
}
|
|
return this
|
|
}
|
|
|
|
enum class ResizeType {
|
|
// リサイズなし
|
|
None,
|
|
|
|
// 長辺がsize以下になるようリサイズ
|
|
LongSide,
|
|
|
|
// 平方ピクセルが size*size 以下になるようリサイズ
|
|
SquarePixel,
|
|
}
|
|
|
|
class ResizeConfig(
|
|
val type : ResizeType,
|
|
val size : Int
|
|
)
|
|
|
|
fun createResizedBitmap(
|
|
context : Context,
|
|
uri : Uri,
|
|
sizeLongSide : Int,
|
|
skipIfNoNeedToResizeAndRotate : Boolean = false
|
|
) =
|
|
createResizedBitmap(
|
|
context,
|
|
uri,
|
|
if(sizeLongSide <= 0)
|
|
ResizeConfig(ResizeType.None, 0)
|
|
else
|
|
ResizeConfig(ResizeType.LongSide, sizeLongSide),
|
|
skipIfNoNeedToResizeAndRotate = skipIfNoNeedToResizeAndRotate
|
|
)
|
|
|
|
fun createResizedBitmap(
|
|
context : Context,
|
|
|
|
// contentResolver.openInputStream に渡すUri
|
|
uri : Uri,
|
|
|
|
// リサイズ指定
|
|
resizeConfig : ResizeConfig,
|
|
|
|
// 真の場合、リサイズも回転も必要ないならnullを返す
|
|
skipIfNoNeedToResizeAndRotate : Boolean = false
|
|
|
|
) : Bitmap? {
|
|
|
|
try {
|
|
|
|
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 {
|
|
BitmapFactory.decodeStream(it, 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
|
|
}
|
|
|
|
// 回転後のサイズ
|
|
val srcSize = rotateSize(orientation, src_width.toFloat(), src_height.toFloat())
|
|
val aspect = srcSize.x / srcSize.y
|
|
|
|
/// 出力サイズの計算
|
|
val sizeSpec = resizeConfig.size.toFloat()
|
|
val dstSize : PointF = when(resizeConfig.type) {
|
|
|
|
ResizeType.None ->
|
|
srcSize
|
|
|
|
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()
|
|
)
|
|
}
|
|
}
|
|
|
|
ResizeType.SquarePixel -> {
|
|
val maxPixels = sizeSpec * sizeSpec
|
|
val currentPixels = srcSize.x * srcSize.y
|
|
if(currentPixels <= maxPixels) {
|
|
srcSize
|
|
} else {
|
|
val y = sqrt(maxPixels / aspect)
|
|
val x = aspect * y
|
|
PointF(x, y)
|
|
}
|
|
}
|
|
}
|
|
|
|
val dstSizeInt = Point(
|
|
max(1, (dstSize.x + 0.5f).toInt()),
|
|
max(1, (dstSize.y + 0.5f).toInt())
|
|
)
|
|
|
|
val resizeRequired = dstSizeInt.x != srcSize.x.toInt() || dstSizeInt.y != srcSize.y.toInt()
|
|
|
|
// リサイズも回転も必要がない場合
|
|
if(skipIfNoNeedToResizeAndRotate
|
|
&& (orientation == null || orientation == 1)
|
|
&& ! resizeRequired
|
|
) {
|
|
log.d("createResizedBitmap: no need to resize or rotate")
|
|
return null
|
|
}
|
|
|
|
// 長辺
|
|
val dstMax = max(dstSize.x, dstSize.y).toInt()
|
|
|
|
// inSampleSizeを計算
|
|
var bits = 0
|
|
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 {
|
|
BitmapFactory.decodeStream(it, null, options)
|
|
}
|
|
|
|
if(sourceBitmap == null) {
|
|
showToast(context, false, "could not decode image.")
|
|
return null
|
|
}
|
|
try {
|
|
// サンプル数が変化している
|
|
src_width = options.outWidth
|
|
src_height = options.outHeight
|
|
val scale = dstMax.toFloat() / max(src_width, src_height)
|
|
|
|
val matrix = Matrix().apply {
|
|
reset()
|
|
|
|
// 画像の中心が原点に来るようにして
|
|
postTranslate(src_width * - 0.5f, src_height * - 0.5f)
|
|
|
|
// スケーリング
|
|
postScale(scale, scale)
|
|
|
|
// 回転情報があれば回転
|
|
resolveOrientation(orientation)
|
|
|
|
// 表示領域に埋まるように平行移動
|
|
postTranslate(dstSizeInt.x.toFloat() * 0.5f, dstSizeInt.y.toFloat() * 0.5f)
|
|
}
|
|
|
|
// 出力用Bitmap作成
|
|
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
|
|
}
|