diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchIconsStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchIconsStep.kt index cbc82af3..fccf98f7 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchIconsStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/PatchIconsStep.kt @@ -18,14 +18,13 @@ import com.aliucord.manager.patcher.steps.StepGroup import com.aliucord.manager.patcher.steps.base.Step import com.aliucord.manager.patcher.steps.base.StepState import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep -import com.aliucord.manager.patcher.util.ArscUtil +import com.aliucord.manager.patcher.util.* import com.aliucord.manager.patcher.util.ArscUtil.addColorResource import com.aliucord.manager.patcher.util.ArscUtil.addResource import com.aliucord.manager.patcher.util.ArscUtil.getMainArscChunk import com.aliucord.manager.patcher.util.ArscUtil.getPackageChunk import com.aliucord.manager.patcher.util.ArscUtil.getResourceFileName import com.aliucord.manager.patcher.util.ArscUtil.getResourceFileNames -import com.aliucord.manager.patcher.util.AxmlUtil import com.aliucord.manager.ui.screens.patchopts.PatchOptions import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement import com.aliucord.manager.util.getRawBytes @@ -103,13 +102,17 @@ class PatchIconsStep(private val options: PatchOptions) : Step(), KoinComponent var foregroundIcon: BinaryResourceIdentifier? = null var backgroundIcon: BinaryResourceIdentifier? = null var monochromeIcon: BinaryResourceIdentifier? = null + val monochromePath = when (options.iconReplacement) { + is IconReplacement.CustomImage -> "res/ic_aliucord_monochrome.png" + else -> "res/ic_aliucord_monochrome.xml" + } // Add the monochrome resource and add the resource file later if (isMonochromeIconsAvailable) { container.log("Adding monochrome icon resource to arsc") val filePathIdx = arsc.getMainArscChunk().stringPool - .addString("res/ic_aliucord_monochrome.xml") + .addString(monochromePath) monochromeIcon = arsc.getPackageChunk().addResource( typeName = "drawable", @@ -177,14 +180,22 @@ class PatchIconsStep(private val options: PatchOptions) : Step(), KoinComponent container.log("Writing other patched files back to apk") ZipWriter(apk, /* append = */ true).use { if (isMonochromeIconsAvailable) { - val monochromeIconId = if (options.iconReplacement is IconReplacement.OldDiscord) { - R.drawable.ic_discord_old_monochrome - } else { - R.drawable.ic_discord_monochrome + val monochromeBytes = when (val icon = options.iconReplacement) { + is IconReplacement.CustomImage -> { + val image = BitmapFactory.decodeStream(icon.imageBytes.inputStream()) + ?: error("Custom icon could not be decoded") + + MonochromeMask.create(image) + .also { image.recycle() } + } + + is IconReplacement.OldDiscord -> context.resources.getRawBytes(R.drawable.ic_discord_old_monochrome) + + else -> context.resources.getRawBytes(R.drawable.ic_discord_monochrome) } - container.log("Writing monochrome icon AXML to apk") - it.writeEntry("res/ic_aliucord_monochrome.xml", context.resources.getRawBytes(monochromeIconId)) + container.log("Writing monochrome icon to apk at $monochromePath") + it.writeEntry(monochromePath, monochromeBytes) } if (options.iconReplacement is IconReplacement.OldDiscord) { diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/util/MonochromeMask.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/util/MonochromeMask.kt new file mode 100644 index 00000000..d0ee041d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/util/MonochromeMask.kt @@ -0,0 +1,51 @@ +package com.aliucord.manager.patcher.util + +import android.graphics.Bitmap +import androidx.core.graphics.createBitmap +import java.io.ByteArrayOutputStream + +object MonochromeMask { + /** + * Create a monochrome adaptive icon layer for a specific image. + */ + fun create(image: Bitmap): ByteArray { + val width = image.width + val height = image.height + + val pixels = IntArray(width * height) + image.getPixels(pixels, 0, width, 0, 0, width, height) + + val hasTransparency = pixels.any { (it ushr 24 and 0xFF) < /* transparency level = */ 250 } + + pixels.forEachIndexed { i, pixel -> + val alpha = pixel ushr 24 and 0xFF + val mask = if (hasTransparency) { + alpha + } else { + luminance(pixel) * alpha / 255 + } + + // Only replace the colors depending on alpha + pixels[i] = (mask shl 24) or 0xFFFFFF + } + + val newImage = createBitmap(width, height).apply { + setPixels(pixels, 0, width, 0, 0, width, height) + } + + return ByteArrayOutputStream().use { + newImage.compress(Bitmap.CompressFormat.PNG, 100, it) + newImage.recycle() + it.toByteArray() + } + } + + // Rec. 601: Perceptual brightness, the safe default for photos and most logos + private fun luminance(pixel: Int): Int { + val red = (pixel ushr 16) and 0xFF + val green = (pixel ushr 8) and 0xFF + val blue = pixel and 0xFF + + return (red * 299 + green * 587 + blue * 114) / 1000 + } +}