mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-02-01 11:26:48 +01:00
リサイズ不要なPNGもWebPに変換する
This commit is contained in:
parent
bdbbe5583c
commit
f45ebd780f
@ -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 を返す
|
||||
* 失敗したら例外を投げる
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user