Remove obsolete Android PNG user-avatar preparation
This commit is contained in:
1 parent
feccea21a5
commit
c36fd3bfc8
1 file changed
+13
-28
@@ -234,7 +234,7 @@ class AvatarRepository(context: Context) {
|
|||||||
require(!record.failed) { "core rejected stored group avatar" }
|
require(!record.failed) { "core rejected stored group avatar" }
|
||||||
val state = GroupAvatarState(record.version, record.hash, record.canEdit, hasImage = record.image.isNotEmpty())
|
val state = GroupAvatarState(record.version, record.hash, record.canEdit, hasImage = record.image.isNotEmpty())
|
||||||
val bitmap = if (record.image.isEmpty()) null else cache.get(groupKey(group, state)) ?: withContext(Dispatchers.IO) {
|
val bitmap = if (record.image.isEmpty()) null else cache.get(groupKey(group, state)) ?: withContext(Dispatchers.IO) {
|
||||||
decode(record.image, record.hash, true)
|
decodeGroup(record.image, record.hash)
|
||||||
}
|
}
|
||||||
alive(epoch)
|
alive(epoch)
|
||||||
if (!_groups.value.containsKey(group)) return@launch
|
if (!_groups.value.containsKey(group)) return@launch
|
||||||
@@ -383,30 +383,15 @@ class AvatarRepository(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readImage(file: File): ByteArray {
|
|
||||||
val size = file.length()
|
|
||||||
require(size in 1..AvatarProtocol.MAX_USER) { "invalid cached file size=$size path=$file" }
|
|
||||||
return file.inputStream().use { input ->
|
|
||||||
val bytes = ByteArray(size.toInt()); var offset = 0
|
|
||||||
while (offset < bytes.size) {
|
|
||||||
val count = input.read(bytes, offset, bytes.size - offset)
|
|
||||||
require(count > 0) { "cached file truncated while reading" }
|
|
||||||
offset += count
|
|
||||||
}
|
|
||||||
require(input.read() == -1) { "cached file grew while reading" }
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Проверяем размер, хеш и геометрию до выделения декодированного изображения. */
|
/** Проверяем размер, хеш и геометрию до выделения декодированного изображения. */
|
||||||
private fun decode(bytes: ByteArray, hash: String, group: Boolean): Bitmap {
|
private fun decodeGroup(bytes: ByteArray, hash: String): Bitmap {
|
||||||
require(bytes.size in 1..if (group) AvatarProtocol.MAX_GROUP else AvatarProtocol.MAX_USER) { "image byte limit" }
|
require(bytes.size in 1..AvatarProtocol.MAX_GROUP) { "image byte limit" }
|
||||||
require(digest(bytes) == hash) { "image SHA256 mismatch" }
|
require(digest(bytes) == hash) { "image SHA256 mismatch" }
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
|
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
|
||||||
val edge = if (group) 128 else 512
|
val edge = 128
|
||||||
require(options.outWidth in 1..edge && options.outHeight == options.outWidth &&
|
require(options.outWidth in 1..edge && options.outHeight == options.outWidth &&
|
||||||
options.outMimeType == if (group) "image/jpeg" else "image/png") {
|
options.outMimeType == "image/jpeg") {
|
||||||
"invalid image ${options.outMimeType} ${options.outWidth}x${options.outHeight} limit=$edge"
|
"invalid image ${options.outMimeType} ${options.outWidth}x${options.outHeight} limit=$edge"
|
||||||
}
|
}
|
||||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: throw IOException("image decode failed")
|
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: throw IOException("image decode failed")
|
||||||
@@ -429,7 +414,7 @@ class AvatarRepository(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun editGroup(group: String, uri: Uri?, expectedAccount: Long) = edit("group:$group", expectedAccount) { epoch ->
|
fun editGroup(group: String, uri: Uri?, expectedAccount: Long) = edit("group:$group", expectedAccount) { epoch ->
|
||||||
val bytes = if (uri == null) ByteArray(0) else prepare(uri, true)
|
val bytes = if (uri == null) ByteArray(0) else prepareGroup(uri)
|
||||||
alive(epoch)
|
alive(epoch)
|
||||||
require(editTokens["group:$group"] == epoch) { "group edit superseded" }
|
require(editTokens["group:$group"] == epoch) { "group edit superseded" }
|
||||||
val reply = AvatarProtocol.published(request(52, group) { id, gid -> NativeLib.setGroupAvatar(id, gid, bytes) })
|
val reply = AvatarProtocol.published(request(52, group) { id, gid -> NativeLib.setGroupAvatar(id, gid, bytes) })
|
||||||
@@ -466,14 +451,14 @@ class AvatarRepository(context: Context) {
|
|||||||
if (account == 0L) start()
|
if (account == 0L) start()
|
||||||
if (expectedAccount == 0L || expectedAccount != account) {
|
if (expectedAccount == 0L || expectedAccount != account) {
|
||||||
log("WARN", "Ignored stale picker result target=$key expected=${AvatarProtocol.hex(expectedAccount)} actual=${AvatarProtocol.hex(account)}")
|
log("WARN", "Ignored stale picker result target=$key expected=${AvatarProtocol.hex(expectedAccount)} actual=${AvatarProtocol.hex(account)}")
|
||||||
rejectedSource?.let { file -> scope.launch(Dispatchers.IO) {
|
rejectedSource?.let { file -> scope.launch(Dispatchers.IO+NonCancellable) {
|
||||||
if (!file.delete()) log("WARN", "Cannot remove stale editor source=$file")
|
if (!file.delete()) log("WARN", "Cannot remove stale editor source=$file")
|
||||||
} }
|
} }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (account == 0L || _edits.value[key] == AvatarEditState.WORKING) {
|
if (account == 0L || _edits.value[key] == AvatarEditState.WORKING) {
|
||||||
log("WARN", "Rejected concurrent avatar edit target=$key")
|
log("WARN", "Rejected concurrent avatar edit target=$key")
|
||||||
rejectedSource?.let { file -> scope.launch(Dispatchers.IO) {
|
rejectedSource?.let { file -> scope.launch(Dispatchers.IO+NonCancellable) {
|
||||||
if (!file.delete()) log("WARN", "Cannot remove rejected editor source=$file")
|
if (!file.delete()) log("WARN", "Cannot remove rejected editor source=$file")
|
||||||
} }
|
} }
|
||||||
return
|
return
|
||||||
@@ -551,7 +536,7 @@ class AvatarRepository(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Чтение URI, EXIF, crop и кодирование выполняются вне UI и потока ядра. */
|
/** Чтение URI, EXIF, crop и кодирование выполняются вне UI и потока ядра. */
|
||||||
private suspend fun prepare(uri: Uri, group: Boolean): ByteArray = preparations.withPermit { withContext(Dispatchers.IO) {
|
private suspend fun prepareGroup(uri: Uri): ByteArray = preparations.withPermit { withContext(Dispatchers.IO) {
|
||||||
val temp = File.createTempFile("avatar-source-", ".image", app.cacheDir)
|
val temp = File.createTempFile("avatar-source-", ".image", app.cacheDir)
|
||||||
try {
|
try {
|
||||||
app.contentResolver.openInputStream(uri)?.use { input ->
|
app.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
@@ -571,15 +556,15 @@ class AvatarRepository(context: Context) {
|
|||||||
val decoded = PhotoUtil.decodeOriented(temp.path, sample) ?: throw IOException("source decode failed")
|
val decoded = PhotoUtil.decodeOriented(temp.path, sample) ?: throw IOException("source decode failed")
|
||||||
val edge = minOf(decoded.width, decoded.height)
|
val edge = minOf(decoded.width, decoded.height)
|
||||||
val crop = Bitmap.createBitmap(decoded, (decoded.width - edge) / 2, (decoded.height - edge) / 2, edge, edge)
|
val crop = Bitmap.createBitmap(decoded, (decoded.width - edge) / 2, (decoded.height - edge) / 2, edge, edge)
|
||||||
val scaled = Bitmap.createScaledBitmap(crop, if (group) 128 else 256, if (group) 128 else 256, true)
|
val scaled = Bitmap.createScaledBitmap(crop,128,128,true)
|
||||||
try {
|
try {
|
||||||
for (quality in if (group) listOf(85, 70, 50, 35, 20) else listOf(100)) {
|
for (quality in listOf(85,70,50,35,20)) {
|
||||||
val output = java.io.ByteArrayOutputStream()
|
val output = java.io.ByteArrayOutputStream()
|
||||||
require(scaled.compress(if (group) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG, quality, output)) {
|
require(scaled.compress(Bitmap.CompressFormat.JPEG,quality,output)) {
|
||||||
"avatar compression failed"
|
"avatar compression failed"
|
||||||
}
|
}
|
||||||
val bytes = output.toByteArray()
|
val bytes = output.toByteArray()
|
||||||
if (bytes.size <= if (group) AvatarProtocol.MAX_GROUP else AvatarProtocol.MAX_USER) return@withContext bytes
|
if (bytes.size<=AvatarProtocol.MAX_GROUP) return@withContext bytes
|
||||||
}
|
}
|
||||||
throw IOException("encoded avatar exceeds limit")
|
throw IOException("encoded avatar exceeds limit")
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in new issue
Block a user