mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-02-07 23:58:44 +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.JsonObject
|
||||||
import jp.juggler.util.data.getStreamSize
|
import jp.juggler.util.data.getStreamSize
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
import jp.juggler.util.log.errorEx
|
||||||
import jp.juggler.util.media.ResizeConfig
|
import jp.juggler.util.media.ResizeConfig
|
||||||
import jp.juggler.util.media.VideoInfo.Companion.videoInfo
|
import jp.juggler.util.media.VideoInfo.Companion.videoInfo
|
||||||
import jp.juggler.util.media.createResizedBitmap
|
import jp.juggler.util.media.createResizedBitmap
|
||||||
@ -141,55 +142,28 @@ class AttachmentRequest(
|
|||||||
private fun createResizedImageOpener(): InputStreamOpener {
|
private fun createResizedImageOpener(): InputStreamOpener {
|
||||||
try {
|
try {
|
||||||
pa.progress = context.getString(R.string.attachment_handling_compress)
|
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(
|
createResizedBitmap(
|
||||||
context,
|
context,
|
||||||
uri,
|
uri,
|
||||||
imageResizeConfig,
|
imageResizeConfig,
|
||||||
skipIfNoNeedToResizeAndRotate = true,
|
skipIfNoNeedToResizeAndRotate = canUseOriginal,
|
||||||
serverMaxSqPixel = serverMaxSqPixel
|
serverMaxSqPixel = serverMaxSqPixel
|
||||||
)?.let { bitmap ->
|
)?.let { bitmap ->
|
||||||
try {
|
try {
|
||||||
val canUseWebP = hasServerSupport(MIME_TYPE_WEBP) &&
|
return bitmap.compressAutoType(canUseWebP)
|
||||||
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.")
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
bitmap.recycle()
|
bitmap.recycle()
|
||||||
}
|
}
|
||||||
@ -203,6 +177,48 @@ class AttachmentRequest(
|
|||||||
return contentUriOpener(context.contentResolver, uri, mimeType, isImage = true)
|
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 を返す
|
* Bitmapを指定フォーマットで圧縮して tempFileOpener を返す
|
||||||
* 失敗したら例外を投げる
|
* 失敗したら例外を投げる
|
||||||
|
@ -2,7 +2,13 @@ package jp.juggler.util.media
|
|||||||
|
|
||||||
//import it.sephiroth.android.library.exif2.ExifInterface
|
//import it.sephiroth.android.library.exif2.ExifInterface
|
||||||
import android.content.Context
|
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 android.net.Uri
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
@ -11,6 +17,7 @@ import jp.juggler.util.log.showToast
|
|||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
private val log = LogCategory("BitmapUtils")
|
private val log = LogCategory("BitmapUtils")
|
||||||
@ -131,11 +138,9 @@ fun createResizedBitmap(
|
|||||||
|
|
||||||
// 真の場合、リサイズも回転も必要ないならnullを返す
|
// 真の場合、リサイズも回転も必要ないならnullを返す
|
||||||
skipIfNoNeedToResizeAndRotate: Boolean = false,
|
skipIfNoNeedToResizeAndRotate: Boolean = false,
|
||||||
|
): Bitmap? {
|
||||||
): Bitmap? {
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
val orientation: Int? = context.contentResolver.openInputStream(uri)?.use {
|
val orientation: Int? = context.contentResolver.openInputStream(uri)?.use {
|
||||||
it.imageOrientation()
|
it.imageOrientation()
|
||||||
}
|
}
|
||||||
@ -149,7 +154,6 @@ fun createResizedBitmap(
|
|||||||
context.contentResolver.openInputStream(uri)?.use {
|
context.contentResolver.openInputStream(uri)?.use {
|
||||||
BitmapFactory.decodeStream(it, null, options)
|
BitmapFactory.decodeStream(it, null, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
var srcWidth = options.outWidth
|
var srcWidth = options.outWidth
|
||||||
var srcHeight = options.outHeight
|
var srcHeight = options.outHeight
|
||||||
if (srcWidth <= 0 || srcHeight <= 0) {
|
if (srcWidth <= 0 || srcHeight <= 0) {
|
||||||
@ -167,22 +171,19 @@ fun createResizedBitmap(
|
|||||||
|
|
||||||
ResizeType.None -> srcSize
|
ResizeType.None -> srcSize
|
||||||
|
|
||||||
ResizeType.LongSide ->
|
ResizeType.LongSide -> when {
|
||||||
if (max(srcSize.x, srcSize.y) <= resizeConfig.size) {
|
max(srcSize.x, srcSize.y) <= resizeConfig.size -> srcSize
|
||||||
srcSize
|
|
||||||
} else {
|
aspect >= 1f -> PointF(
|
||||||
if (aspect >= 1f) {
|
resizeConfig.size.toFloat(),
|
||||||
PointF(
|
sizeSpec / aspect
|
||||||
resizeConfig.size.toFloat(),
|
)
|
||||||
sizeSpec / aspect
|
|
||||||
)
|
else -> PointF(
|
||||||
} else {
|
sizeSpec * aspect,
|
||||||
PointF(
|
resizeConfig.size.toFloat()
|
||||||
sizeSpec * aspect,
|
)
|
||||||
resizeConfig.size.toFloat()
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResizeType.SquarePixel -> srcSize.limitBySqPixel(aspect, sizeSpec * sizeSpec)
|
ResizeType.SquarePixel -> srcSize.limitBySqPixel(aspect, sizeSpec * sizeSpec)
|
||||||
}
|
}
|
||||||
@ -191,7 +192,7 @@ fun createResizedBitmap(
|
|||||||
dstSize = dstSize.limitBySqPixel(aspect, serverMaxSqPixel.toFloat())
|
dstSize = dstSize.limitBySqPixel(aspect, serverMaxSqPixel.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
val dstSizeInt = Point(
|
var dstSizeInt = Point(
|
||||||
max(1, (dstSize.x + 0.5f).toInt()),
|
max(1, (dstSize.x + 0.5f).toInt()),
|
||||||
max(1, (dstSize.y + 0.5f).toInt())
|
max(1, (dstSize.y + 0.5f).toInt())
|
||||||
)
|
)
|
||||||
@ -221,13 +222,26 @@ fun createResizedBitmap(
|
|||||||
return null
|
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を計算
|
// inSampleSizeを計算
|
||||||
var bits = 0
|
var bits = 0
|
||||||
var x = max(srcSize.x, srcSize.y).toInt()
|
var x = max(srcSize.x, srcSize.y).toInt()
|
||||||
while (x > 512 && x > dstMax * 2) {
|
while (x > 4096 || (x > 512 && x > dstMax * 2)) {
|
||||||
++bits
|
++bits
|
||||||
x = x shr 1
|
x = x shr 1
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user