リサイズ不要なPNGもWebPに変換する

This commit is contained in:
tateisu 2023-05-14 10:52:34 +09:00
parent bdbbe5583c
commit f45ebd780f
2 changed files with 96 additions and 66 deletions

View File

@ -17,6 +17,7 @@ import jp.juggler.subwaytooter.util.AttachmentUploader.Companion.MIME_TYPE_WEBP
import jp.juggler.util.data.JsonObject
import jp.juggler.util.data.getStreamSize
import jp.juggler.util.log.LogCategory
import jp.juggler.util.log.errorEx
import jp.juggler.util.media.ResizeConfig
import jp.juggler.util.media.VideoInfo.Companion.videoInfo
import jp.juggler.util.media.createResizedBitmap
@ -141,55 +142,28 @@ class AttachmentRequest(
private fun createResizedImageOpener(): InputStreamOpener {
try {
pa.progress = context.getString(R.string.attachment_handling_compress)
val canUseWebP = try {
hasServerSupport(MIME_TYPE_WEBP) && PrefB.bpUseWebP.value
} catch (ex: Throwable) {
log.w(ex, "can't check canUseWebP")
false
}
// サーバが読めない形式の画像なら強制的に再圧縮をかける
// もしくは、PNG画像も可能ならWebPに変換したい
val canUseOriginal = hasServerSupport(mimeType) &&
!(mimeType == MIME_TYPE_PNG && canUseWebP)
createResizedBitmap(
context,
uri,
imageResizeConfig,
skipIfNoNeedToResizeAndRotate = true,
skipIfNoNeedToResizeAndRotate = canUseOriginal,
serverMaxSqPixel = serverMaxSqPixel
)?.let { bitmap ->
try {
val canUseWebP = hasServerSupport(MIME_TYPE_WEBP) &&
PrefB.bpUseWebP.value
if (canUseWebP) {
try {
val format = when {
Build.VERSION.SDK_INT >= 30 ->
Bitmap.CompressFormat.WEBP_LOSSY
else ->
@Suppress("DEPRECATION")
Bitmap.CompressFormat.WEBP
}
return bitmap.compressToTempFileOpener(MIME_TYPE_WEBP, format, 90)
} catch (ex: Throwable) {
log.w(ex, "compress to WebP lossy failed.")
// 失敗したらJPEG or PNG にフォールバック
}
}
try {
// check bitmap has translucent pixels
val hasAlpha = when {
mimeType == MIME_TYPE_JPEG -> false
!bitmap.hasAlpha() -> false
else -> bitmap.scanAlpha()
}
return when (hasAlpha) {
true -> bitmap.compressToTempFileOpener(
MIME_TYPE_PNG,
Bitmap.CompressFormat.PNG,
100
)
else -> bitmap.compressToTempFileOpener(
MIME_TYPE_JPEG,
Bitmap.CompressFormat.JPEG,
95
)
}
} catch (ex: Throwable) {
log.w(ex, "compress to JPEG/PNG failed.")
}
return bitmap.compressAutoType(canUseWebP)
} finally {
bitmap.recycle()
}
@ -203,6 +177,48 @@ class AttachmentRequest(
return contentUriOpener(context.contentResolver, uri, mimeType, isImage = true)
}
private fun Bitmap.compressAutoType(canUseWebP: Boolean): InputStreamOpener {
if (canUseWebP) {
try {
val format = when {
Build.VERSION.SDK_INT >= 30 ->
Bitmap.CompressFormat.WEBP_LOSSY
else ->
@Suppress("DEPRECATION")
Bitmap.CompressFormat.WEBP
}
return compressToTempFileOpener(MIME_TYPE_WEBP, format, 90)
} catch (ex: Throwable) {
log.w(ex, "compress to WebP lossy failed.")
// 失敗したらJPEG or PNG にフォールバック
}
}
try {
// check bitmap has translucent pixels
val hasAlpha = when {
mimeType == MIME_TYPE_JPEG -> false
!hasAlpha() -> false
else -> scanAlpha()
}
return when (hasAlpha) {
true -> compressToTempFileOpener(
MIME_TYPE_PNG,
Bitmap.CompressFormat.PNG,
100
)
else -> compressToTempFileOpener(
MIME_TYPE_JPEG,
Bitmap.CompressFormat.JPEG,
95
)
}
} catch (ex: Throwable) {
errorEx(ex, "compress to JPEG/PNG failed.")
}
}
/**
* Bitmapを指定フォーマットで圧縮して tempFileOpener を返す
* 失敗したら例外を投げる

View File

@ -2,7 +2,13 @@ package jp.juggler.util.media
//import it.sephiroth.android.library.exif2.ExifInterface
import android.content.Context
import android.graphics.*
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Point
import android.graphics.PointF
import android.net.Uri
import androidx.annotation.StringRes
import androidx.exifinterface.media.ExifInterface
@ -11,6 +17,7 @@ import jp.juggler.util.log.showToast
import java.io.FileNotFoundException
import java.io.InputStream
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
private val log = LogCategory("BitmapUtils")
@ -131,11 +138,9 @@ fun createResizedBitmap(
// 真の場合、リサイズも回転も必要ないならnullを返す
skipIfNoNeedToResizeAndRotate: Boolean = false,
): Bitmap? {
): Bitmap? {
try {
val orientation: Int? = context.contentResolver.openInputStream(uri)?.use {
it.imageOrientation()
}
@ -149,7 +154,6 @@ fun createResizedBitmap(
context.contentResolver.openInputStream(uri)?.use {
BitmapFactory.decodeStream(it, null, options)
}
var srcWidth = options.outWidth
var srcHeight = options.outHeight
if (srcWidth <= 0 || srcHeight <= 0) {
@ -167,22 +171,19 @@ fun createResizedBitmap(
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.LongSide -> when {
max(srcSize.x, srcSize.y) <= resizeConfig.size -> srcSize
aspect >= 1f -> PointF(
resizeConfig.size.toFloat(),
sizeSpec / aspect
)
else -> PointF(
sizeSpec * aspect,
resizeConfig.size.toFloat()
)
}
ResizeType.SquarePixel -> srcSize.limitBySqPixel(aspect, sizeSpec * sizeSpec)
}
@ -191,7 +192,7 @@ fun createResizedBitmap(
dstSize = dstSize.limitBySqPixel(aspect, serverMaxSqPixel.toFloat())
}
val dstSizeInt = Point(
var dstSizeInt = Point(
max(1, (dstSize.x + 0.5f).toInt()),
max(1, (dstSize.y + 0.5f).toInt())
)
@ -221,13 +222,26 @@ fun createResizedBitmap(
return null
}
// リサイズする場合、ビットマップサイズ上限の成約がある
if (max(dstSizeInt.x, dstSizeInt.y) > 4096) {
val scale = 4096f / max(dstSizeInt.x, dstSizeInt.y).toFloat()
dstSize = PointF(
min(4096f, dstSize.x * scale),
min(4096f, dstSize.y * scale),
)
dstSizeInt = Point(
max(1, (dstSize.x + 0.5f).toInt()),
max(1, (dstSize.y + 0.5f).toInt())
)
}
// 長辺
val dstMax = max(dstSize.x, dstSize.y).toInt()
val dstMax = min(4096, max(dstSize.x, dstSize.y).toInt())
// inSampleSizeを計算
var bits = 0
var x = max(srcSize.x, srcSize.y).toInt()
while (x > 512 && x > dstMax * 2) {
while (x > 4096 || (x > 512 && x > dstMax * 2)) {
++bits
x = x shr 1
}