feat(YouTube Music): Add Custom branding patch (#6007)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
@@ -372,6 +372,10 @@ public final class app/revanced/patches/music/interaction/permanentshuffle/Perma
|
|||||||
public static final fun getPermanentShufflePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getPermanentShufflePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/layout/branding/CustomBrandingPatchKt {
|
||||||
|
public static final fun getCustomBrandingPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/layout/castbutton/HideCastButtonKt {
|
public final class app/revanced/patches/music/layout/castbutton/HideCastButtonKt {
|
||||||
public static final fun getHideCastButton ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideCastButton ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package app.revanced.patches.music.layout.branding
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
|
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
|
||||||
|
import app.revanced.patches.shared.misc.mapping.get
|
||||||
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversed
|
||||||
|
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
private val disableSplashAnimationPatch = bytecodePatch {
|
||||||
|
|
||||||
|
dependsOn(resourceMappingPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// The existing YT animation usually only shows for a fraction of a second,
|
||||||
|
// and the existing animation does not match the new splash screen
|
||||||
|
// causing the original YT Music logo to momentarily flash on screen as the animation starts.
|
||||||
|
//
|
||||||
|
// Could replace the lottie animation file with our own custom animation (app_launch.json),
|
||||||
|
// but the animation is not always the same size as the launch screen and it's still
|
||||||
|
// barely shown. Instead turn off the animation entirely (app will also launch a little faster).
|
||||||
|
cairoSplashAnimationConfigFingerprint.method.apply {
|
||||||
|
val mainActivityLaunchAnimation = resourceMappings["layout", "main_activity_launch_animation"]
|
||||||
|
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
|
||||||
|
mainActivityLaunchAnimation
|
||||||
|
)
|
||||||
|
val insertIndex = indexOfFirstInstructionReversed(literalIndex) {
|
||||||
|
this.opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
getReference<MethodReference>()?.name == "setContentView"
|
||||||
|
} + 1
|
||||||
|
val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) {
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
getReference<MethodReference>()?.parameterTypes?.firstOrNull() == "Ljava/lang/Runnable;"
|
||||||
|
} + 1
|
||||||
|
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
insertIndex,
|
||||||
|
"goto :skip_animation",
|
||||||
|
ExternalLabel("skip_animation", getInstruction(jumpIndex))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val APP_NAME = "YT Music ReVanced"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val customBrandingPatch = baseCustomBrandingPatch(
|
||||||
|
defaultAppName = APP_NAME,
|
||||||
|
appNameValues = mapOf(
|
||||||
|
"YT Music ReVanced" to APP_NAME,
|
||||||
|
"Music ReVanced" to "Music ReVanced",
|
||||||
|
"Music" to "Music",
|
||||||
|
"YT Music" to "YT Music",
|
||||||
|
),
|
||||||
|
resourceFolder = "custom-branding/music",
|
||||||
|
iconResourceFileNames = arrayOf(
|
||||||
|
"adaptiveproduct_youtube_music_2024_q4_background_color_108",
|
||||||
|
"adaptiveproduct_youtube_music_2024_q4_foreground_color_108",
|
||||||
|
"ic_launcher_release",
|
||||||
|
),
|
||||||
|
|
||||||
|
block = {
|
||||||
|
dependsOn(disableSplashAnimationPatch)
|
||||||
|
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52",
|
||||||
|
"8.10.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patches.music.layout.branding
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.patches.music.shared.YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
|
|
||||||
|
internal val cairoSplashAnimationConfigFingerprint = fingerprint {
|
||||||
|
returns("V")
|
||||||
|
parameters("Landroid/os/Bundle;")
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "onCreate" && method.definingClass == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package app.revanced.patches.shared.layout.branding
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.ResourcePatch
|
||||||
|
import app.revanced.patcher.patch.ResourcePatchBuilder
|
||||||
|
import app.revanced.patcher.patch.ResourcePatchContext
|
||||||
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
|
import app.revanced.patcher.patch.stringOption
|
||||||
|
import app.revanced.util.ResourceGroup
|
||||||
|
import app.revanced.util.Utils.trimIndentMultiline
|
||||||
|
import app.revanced.util.copyResources
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
private const val REVANCED_ICON = "ReVanced*Logo" // Can never be a valid path.
|
||||||
|
|
||||||
|
internal val mipmapDirectories = arrayOf(
|
||||||
|
"xxxhdpi",
|
||||||
|
"xxhdpi",
|
||||||
|
"xhdpi",
|
||||||
|
"hdpi",
|
||||||
|
"mdpi",
|
||||||
|
).map { "mipmap-$it" }.toTypedArray()
|
||||||
|
|
||||||
|
private fun formatResourceFileList(resourceNames: Array<String>) = resourceNames.joinToString("\n") { "- $it" }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to fix unescaped and invalid characters not allowed for an Android app name.
|
||||||
|
*/
|
||||||
|
private fun escapeAppName(name: String): String? {
|
||||||
|
// Remove ASCII control characters.
|
||||||
|
val cleanedName = name.filter { it.code >= 32 }
|
||||||
|
|
||||||
|
// Replace invalid XML characters with escaped equivalents.
|
||||||
|
val escapedName = cleanedName
|
||||||
|
.replace("&", "&") // Must be first to avoid double-escaping.
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace(Regex("(?<!&)\""), """)
|
||||||
|
|
||||||
|
// Trim empty spacing.
|
||||||
|
val trimmed = escapedName.trim()
|
||||||
|
|
||||||
|
return trimmed.ifBlank { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared custom branding patch for YouTube and YT Music.
|
||||||
|
*/
|
||||||
|
internal fun baseCustomBrandingPatch(
|
||||||
|
defaultAppName: String,
|
||||||
|
appNameValues: Map<String, String>,
|
||||||
|
resourceFolder: String,
|
||||||
|
iconResourceFileNames: Array<String>,
|
||||||
|
block: ResourcePatchBuilder.() -> Unit = {},
|
||||||
|
executeBlock: ResourcePatchContext.() -> Unit = {}
|
||||||
|
): ResourcePatch = resourcePatch(
|
||||||
|
name = "Custom branding",
|
||||||
|
description = "Applies a custom app name and icon. Defaults to \"$defaultAppName\" and the ReVanced logo.",
|
||||||
|
use = false,
|
||||||
|
) {
|
||||||
|
val iconResourceFileNamesPng = iconResourceFileNames.map { "$it.png" }.toTypedArray<String>()
|
||||||
|
|
||||||
|
val appName by stringOption(
|
||||||
|
key = "appName",
|
||||||
|
default = defaultAppName,
|
||||||
|
values = appNameValues,
|
||||||
|
title = "App name",
|
||||||
|
description = "The name of the app.",
|
||||||
|
)
|
||||||
|
|
||||||
|
val iconPath by stringOption(
|
||||||
|
key = "iconPath",
|
||||||
|
default = REVANCED_ICON,
|
||||||
|
values = mapOf("ReVanced Logo" to REVANCED_ICON),
|
||||||
|
title = "App icon",
|
||||||
|
description = """
|
||||||
|
The icon to apply to the app.
|
||||||
|
|
||||||
|
If a path to a folder is provided, the folder must contain the following folders:
|
||||||
|
|
||||||
|
${formatResourceFileList(mipmapDirectories)}
|
||||||
|
|
||||||
|
Each of these folders must contain the following files:
|
||||||
|
|
||||||
|
${formatResourceFileList(iconResourceFileNamesPng)}
|
||||||
|
""".trimIndentMultiline(),
|
||||||
|
)
|
||||||
|
|
||||||
|
block()
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// Change the app icon and launch screen.
|
||||||
|
val iconResourceGroups = mipmapDirectories.map { directory ->
|
||||||
|
ResourceGroup(
|
||||||
|
directory,
|
||||||
|
*iconResourceFileNamesPng,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconPathTrimmed = iconPath!!.trim()
|
||||||
|
if (iconPathTrimmed == REVANCED_ICON) {
|
||||||
|
iconResourceGroups.forEach {
|
||||||
|
copyResources(resourceFolder, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val filePath = File(iconPathTrimmed)
|
||||||
|
val resourceDirectory = get("res")
|
||||||
|
|
||||||
|
iconResourceGroups.forEach { group ->
|
||||||
|
val fromDirectory = filePath.resolve(group.resourceDirectoryName)
|
||||||
|
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
|
||||||
|
|
||||||
|
group.resources.forEach { iconFileName ->
|
||||||
|
Files.write(
|
||||||
|
toDirectory.resolve(iconFileName).toPath(),
|
||||||
|
fromDirectory.resolve(iconFileName).readBytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the app name.
|
||||||
|
escapeAppName(appName!!)?.let { escapedAppName ->
|
||||||
|
val newValue = "android:label=\"$escapedAppName\""
|
||||||
|
|
||||||
|
val manifest = get("AndroidManifest.xml")
|
||||||
|
val original = manifest.readText()
|
||||||
|
val replacement = original
|
||||||
|
// YouTube
|
||||||
|
.replace("android:label=\"@string/application_name\"", newValue)
|
||||||
|
// YT Music
|
||||||
|
.replace("android:label=\"@string/app_launcher_name\"", newValue)
|
||||||
|
|
||||||
|
if (original == replacement) {
|
||||||
|
Logger.getLogger(this::class.java.name).warning(
|
||||||
|
"Could not replace manifest app name"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.writeText(replacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
executeBlock() // Must be after the main code to rename the new icons for YouTube 19.34+.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +1,56 @@
|
|||||||
package app.revanced.patches.youtube.layout.branding
|
package app.revanced.patches.youtube.layout.branding
|
||||||
|
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patches.shared.layout.branding.baseCustomBrandingPatch
|
||||||
import app.revanced.patcher.patch.stringOption
|
import app.revanced.patches.shared.layout.branding.mipmapDirectories
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
|
||||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
|
||||||
import app.revanced.util.ResourceGroup
|
|
||||||
import app.revanced.util.Utils.trimIndentMultiline
|
|
||||||
import app.revanced.util.copyResources
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
private const val REVANCED_ICON = "ReVanced*Logo" // Can never be a valid path.
|
|
||||||
private const val APP_NAME = "YouTube ReVanced"
|
private const val APP_NAME = "YouTube ReVanced"
|
||||||
|
|
||||||
private val iconResourceFileNames = arrayOf(
|
private val youtubeIconResourceFileNames_19_34 = mapOf(
|
||||||
"adaptiveproduct_youtube_background_color_108",
|
|
||||||
"adaptiveproduct_youtube_foreground_color_108",
|
|
||||||
"ic_launcher",
|
|
||||||
"ic_launcher_round",
|
|
||||||
).map { "$it.png" }.toTypedArray()
|
|
||||||
|
|
||||||
private val iconResourceFileNamesNew = mapOf(
|
|
||||||
"adaptiveproduct_youtube_foreground_color_108" to "adaptiveproduct_youtube_2024_q4_foreground_color_108",
|
"adaptiveproduct_youtube_foreground_color_108" to "adaptiveproduct_youtube_2024_q4_foreground_color_108",
|
||||||
"adaptiveproduct_youtube_background_color_108" to "adaptiveproduct_youtube_2024_q4_background_color_108",
|
"adaptiveproduct_youtube_background_color_108" to "adaptiveproduct_youtube_2024_q4_background_color_108",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val mipmapDirectories = arrayOf(
|
|
||||||
"xxxhdpi",
|
|
||||||
"xxhdpi",
|
|
||||||
"xhdpi",
|
|
||||||
"hdpi",
|
|
||||||
"mdpi",
|
|
||||||
).map { "mipmap-$it" }
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val customBrandingPatch = resourcePatch(
|
val customBrandingPatch = baseCustomBrandingPatch(
|
||||||
name = "Custom branding",
|
defaultAppName = APP_NAME,
|
||||||
description = "Applies a custom app name and icon. Defaults to \"YouTube ReVanced\" and the ReVanced logo.",
|
appNameValues = mapOf(
|
||||||
use = false,
|
"YouTube ReVanced" to APP_NAME,
|
||||||
) {
|
"YT ReVanced" to "YT ReVanced",
|
||||||
dependsOn(versionCheckPatch)
|
"YT" to "YT",
|
||||||
|
"YouTube" to "YouTube",
|
||||||
|
),
|
||||||
|
resourceFolder = "custom-branding/youtube",
|
||||||
|
iconResourceFileNames = arrayOf(
|
||||||
|
"adaptiveproduct_youtube_background_color_108",
|
||||||
|
"adaptiveproduct_youtube_foreground_color_108",
|
||||||
|
"ic_launcher",
|
||||||
|
"ic_launcher_round",
|
||||||
|
),
|
||||||
|
|
||||||
compatibleWith(
|
block = {
|
||||||
"com.google.android.youtube"(
|
compatibleWith(
|
||||||
"19.34.42",
|
"com.google.android.youtube"(
|
||||||
"20.07.39",
|
"19.34.42",
|
||||||
"20.13.41",
|
"20.07.39",
|
||||||
"20.14.43",
|
"20.13.41",
|
||||||
)
|
"20.14.43",
|
||||||
)
|
|
||||||
|
|
||||||
val appName by stringOption(
|
|
||||||
key = "appName",
|
|
||||||
default = APP_NAME,
|
|
||||||
values = mapOf(
|
|
||||||
"YouTube ReVanced" to APP_NAME,
|
|
||||||
"YT ReVanced" to "YT ReVanced",
|
|
||||||
"YT" to "YT",
|
|
||||||
"YouTube" to "YouTube",
|
|
||||||
),
|
|
||||||
title = "App name",
|
|
||||||
description = "The name of the app.",
|
|
||||||
)
|
|
||||||
|
|
||||||
val icon by stringOption(
|
|
||||||
key = "iconPath",
|
|
||||||
default = REVANCED_ICON,
|
|
||||||
values = mapOf("ReVanced Logo" to REVANCED_ICON),
|
|
||||||
title = "App icon",
|
|
||||||
description = """
|
|
||||||
The icon to apply to the app.
|
|
||||||
|
|
||||||
If a path to a folder is provided, the folder must contain the following folders:
|
|
||||||
|
|
||||||
${mipmapDirectories.joinToString("\n") { "- $it" }}
|
|
||||||
|
|
||||||
Each of these folders must contain the following files:
|
|
||||||
|
|
||||||
${iconResourceFileNames.joinToString("\n") { "- $it" }}
|
|
||||||
""".trimIndentMultiline(),
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
|
||||||
icon?.let { icon ->
|
|
||||||
// Change the app icon.
|
|
||||||
mipmapDirectories.map { directory ->
|
|
||||||
ResourceGroup(
|
|
||||||
directory,
|
|
||||||
*iconResourceFileNames,
|
|
||||||
)
|
|
||||||
}.let { resourceGroups ->
|
|
||||||
if (icon != REVANCED_ICON) {
|
|
||||||
val path = File(icon)
|
|
||||||
val resourceDirectory = get("res")
|
|
||||||
|
|
||||||
resourceGroups.forEach { group ->
|
|
||||||
val fromDirectory = path.resolve(group.resourceDirectoryName)
|
|
||||||
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
|
|
||||||
|
|
||||||
group.resources.forEach { iconFileName ->
|
|
||||||
Files.write(
|
|
||||||
toDirectory.resolve(iconFileName).toPath(),
|
|
||||||
fromDirectory.resolve(iconFileName).readBytes(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resourceGroups.forEach { copyResources("custom-branding", it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_19_34_or_greater) {
|
|
||||||
val resourceDirectory = get("res")
|
|
||||||
|
|
||||||
mipmapDirectories.forEach { directory ->
|
|
||||||
val targetDirectory = resourceDirectory.resolve(directory)
|
|
||||||
|
|
||||||
iconResourceFileNamesNew.forEach { (old, new) ->
|
|
||||||
val oldFile = targetDirectory.resolve("$old.png")
|
|
||||||
val newFile = targetDirectory.resolve("$new.png")
|
|
||||||
|
|
||||||
Files.write(newFile.toPath(), oldFile.readBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appName?.let { name ->
|
|
||||||
// Change the app name.
|
|
||||||
val manifest = get("AndroidManifest.xml")
|
|
||||||
manifest.writeText(
|
|
||||||
manifest.readText()
|
|
||||||
.replace(
|
|
||||||
"android:label=\"@string/application_name",
|
|
||||||
"android:label=\"$name",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
executeBlock = {
|
||||||
|
val resourceDirectory = get("res")
|
||||||
|
|
||||||
|
mipmapDirectories.forEach { directory ->
|
||||||
|
val targetDirectory = resourceDirectory.resolve(directory)
|
||||||
|
|
||||||
|
youtubeIconResourceFileNames_19_34.forEach { (old, new) ->
|
||||||
|
val oldFile = targetDirectory.resolve("$old.png")
|
||||||
|
val newFile = targetDirectory.resolve("$new.png")
|
||||||
|
|
||||||
|
Files.write(newFile.toPath(), oldFile.readBytes())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ var is_19_32_or_greater = false
|
|||||||
@Deprecated("19.34.42 is the lowest supported version")
|
@Deprecated("19.34.42 is the lowest supported version")
|
||||||
var is_19_33_or_greater = false
|
var is_19_33_or_greater = false
|
||||||
private set
|
private set
|
||||||
|
@Deprecated("19.34.42 is the lowest supported version")
|
||||||
var is_19_34_or_greater = false
|
var is_19_34_or_greater = false
|
||||||
private set
|
private set
|
||||||
var is_19_35_or_greater = false
|
var is_19_35_or_greater = false
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 98 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 92 B |
|
After Width: | Height: | Size: 916 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 100 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 117 B |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 98 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 92 B |
|
After Width: | Height: | Size: 922 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 100 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 117 B |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.2 KiB |