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" }
|
||||
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) {
|
||||
decode(record.image, record.hash, true)
|
||||
decodeGroup(record.image, record.hash)
|
||||
}
|
||||
alive(epoch)
|
||||
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 {
|
||||
require(bytes.size in 1..if (group) AvatarProtocol.MAX_GROUP else AvatarProtocol.MAX_USER) { "image byte limit" }
|
||||
private fun decodeGroup(bytes: ByteArray, hash: String): Bitmap {
|
||||
require(bytes.size in 1..AvatarProtocol.MAX_GROUP) { "image byte limit" }
|
||||
require(digest(bytes) == hash) { "image SHA256 mismatch" }
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
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 &&
|
||||
options.outMimeType == if (group) "image/jpeg" else "image/png") {
|
||||
options.outMimeType == "image/jpeg") {
|
||||
"invalid image ${options.outMimeType} ${options.outWidth}x${options.outHeight} limit=$edge"
|
||||
}
|
||||
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 ->
|
||||
val bytes = if (uri == null) ByteArray(0) else prepare(uri, true)
|
||||
val bytes = if (uri == null) ByteArray(0) else prepareGroup(uri)
|
||||
alive(epoch)
|
||||
require(editTokens["group:$group"] == epoch) { "group edit superseded" }
|
||||
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 (expectedAccount == 0L || expectedAccount != 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")
|
||||
} }
|
||||
return
|
||||
}
|
||||
if (account == 0L || _edits.value[key] == AvatarEditState.WORKING) {
|
||||
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")
|
||||
} }
|
||||
return
|
||||
@@ -551,7 +536,7 @@ class AvatarRepository(context: Context) {
|
||||
}
|
||||
|
||||
/** Чтение 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)
|
||||
try {
|
||||
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 edge = minOf(decoded.width, decoded.height)
|
||||
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 {
|
||||
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()
|
||||
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"
|
||||
}
|
||||
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")
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user