feat: Add option to lower the threshold for hardware bitmaps

This commit is contained in:
AntsyLich 2024-11-20 23:01:54 +07:00 committed by Ahmad Ansori Palembani
parent 27002a20ef
commit fd73958923
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
7 changed files with 85 additions and 48 deletions

View file

@ -51,6 +51,8 @@ import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.source.SourcePresenter import eu.kanade.tachiyomi.ui.source.SourcePresenter
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
@ -102,6 +104,10 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
modules(preferenceModule(this@App), appModule(this@App), domainModule()) modules(preferenceModule(this@App), appModule(this@App), domainModule())
} }
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
val scope = ProcessLifecycleOwner.get().lifecycleScope
basePreferences.crashReport().changes() basePreferences.crashReport().changes()
.onEach { .onEach {
try { try {
@ -110,18 +116,23 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
// Probably already enabled/disabled // Probably already enabled/disabled
} }
} }
.launchIn(ProcessLifecycleOwner.get().lifecycleScope) .launchIn(scope)
setupNotificationChannels() setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
MangaCoverMetadata.load() MangaCoverMetadata.load()
preferences.nightMode().changes() preferences.nightMode().changes()
.onEach { AppCompatDelegate.setDefaultNightMode(it) } .onEach { AppCompatDelegate.setDefaultNightMode(it) }
.launchIn(ProcessLifecycleOwner.get().lifecycleScope) .launchIn(scope)
ProcessLifecycleOwner.get().lifecycleScope.launchIO { basePreferences.hardwareBitmapThreshold().let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
}
basePreferences.hardwareBitmapThreshold().changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope)
scope.launchIO {
with(TachiyomiWidgetManager()) { this@App.init() } with(TachiyomiWidgetManager()) { this@App.init() }
} }
@ -160,7 +171,7 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
notificationManager.cancel(Notifications.ID_INCOGNITO_MODE) notificationManager.cancel(Notifications.ID_INCOGNITO_MODE)
} }
} }
.launchIn(ProcessLifecycleOwner.get().lifecycleScope) .launchIn(scope)
initializeMigrator() initializeMigrator()
} }

View file

@ -36,7 +36,6 @@ import eu.kanade.tachiyomi.data.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale
import okio.BufferedSource import okio.BufferedSource
@ -127,7 +126,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
} else { } else {
SubsamplingScaleImageView(context) SubsamplingScaleImageView(context)
}.apply { }.apply {
setMaxTileSize(GLUtil.maxTextureSize) setMaxTileSize(ImageUtil.hardwareBitmapThreshold)
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER) setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
setMinimumTileDpi(180) setMinimumTileDpi(180)
@ -232,8 +231,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
isVisible = true isVisible = true
} }
is BufferedSource -> { is BufferedSource -> {
// FIXME: Remove `ImageUtil.isMaxTextureSizeExceeded` after porting https://github.com/mihonapp/mihon/commit/dcddac5daaff3ec89c8507c35dc13d345ffdb6d7#diff-cbb19957efc1d319c0cdc62d5cf4b32bad5b51da21d3c67c3d4256200eb9c5d1 if (!isWebtoon) {
if (!isWebtoon || ImageUtil.isMaxTextureSizeExceeded(data)) {
setHardwareConfig(!ImageUtil.isMaxTextureSizeExceeded(data)) setHardwareConfig(!ImageUtil.isMaxTextureSizeExceeded(data))
setImage(ImageSource.inputStream(data.inputStream())) setImage(ImageSource.inputStream(data.inputStream()))
isVisible = true isVisible = true

View file

@ -57,6 +57,7 @@ import eu.kanade.tachiyomi.ui.setting.preference
import eu.kanade.tachiyomi.ui.setting.preferenceCategory import eu.kanade.tachiyomi.ui.setting.preferenceCategory
import eu.kanade.tachiyomi.ui.setting.switchPreference import eu.kanade.tachiyomi.ui.setting.switchPreference
import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.disableItems import eu.kanade.tachiyomi.util.system.disableItems
import eu.kanade.tachiyomi.util.system.e import eu.kanade.tachiyomi.util.system.e
import eu.kanade.tachiyomi.util.system.isPackageInstalled import eu.kanade.tachiyomi.util.system.isPackageInstalled
@ -73,6 +74,7 @@ import eu.kanade.tachiyomi.util.view.setPositiveButton
import eu.kanade.tachiyomi.util.view.setTitle import eu.kanade.tachiyomi.util.view.setTitle
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import java.io.File import java.io.File
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -393,8 +395,25 @@ class SettingsAdvancedController : SettingsLegacyController() {
preferenceCategory { preferenceCategory {
titleRes = MR.strings.reader titleRes = MR.strings.reader
listPreference(activity) {
bindTo(basePreferences.hardwareBitmapThreshold())
titleRes = MR.strings.pref_hardware_bitmap_threshold
val entryMap = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.associateWith { it.toString() }
.toImmutableMap()
entries = entryMap.values.toList()
entryValues = entryMap.keys.map { it.toString() }.toList()
isVisible = GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT
basePreferences.hardwareBitmapThreshold().changesIn(viewScope) { threshold ->
summary = context.getString(MR.strings.pref_hardware_bitmap_threshold_summary, threshold)
}
}
preference { preference {
key = "pref_display_profile" bindTo(basePreferences.displayProfile())
titleRes = MR.strings.pref_display_profile titleRes = MR.strings.pref_display_profile
onClick { onClick {
(activity as? MainActivity)?.showColourProfilePicker() (activity as? MainActivity)?.showColourProfilePicker()

View file

@ -3,52 +3,54 @@ package eu.kanade.tachiyomi.util.system
import javax.microedition.khronos.egl.EGL10 import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLContext import javax.microedition.khronos.egl.EGLContext
import kotlin.math.max
class GLUtil private constructor() { object GLUtil {
companion object { val DEVICE_TEXTURE_LIMIT: Int by lazy {
// Safe minimum default size // Get EGL Display
private const val IMAGE_MAX_BITMAP_DIMENSION = 2048 val egl = EGLContext.getEGL() as EGL10
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
val maxTextureSize: Int // Initialise
get() { val version = IntArray(2)
// Get EGL Display egl.eglInitialize(display, version)
val egl = EGLContext.getEGL() as EGL10
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
// Initialise // Query total number of configurations
val version = IntArray(2) val totalConfigurations = IntArray(1)
egl.eglInitialize(display, version) egl.eglGetConfigs(display, null, 0, totalConfigurations)
// Query total number of configurations // Query actual list configurations
val totalConfigurations = IntArray(1) val configurationsList = arrayOfNulls<EGLConfig>(totalConfigurations[0])
egl.eglGetConfigs(display, null, 0, totalConfigurations) egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations)
// Query actual list configurations val textureSize = IntArray(1)
val configurationsList = arrayOfNulls<EGLConfig>(totalConfigurations[0]) var maximumTextureSize = 0
egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations)
val textureSize = IntArray(1) // Iterate through all the configurations to located the maximum texture size
var maximumTextureSize = 0 for (i in 0 until totalConfigurations[0]) {
// Only need to check for width since opengl textures are always squared
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize)
// Iterate through all the configurations to located the maximum texture size // Keep track of the maximum texture size
for (i in 0 until totalConfigurations[0]) { if (maximumTextureSize < textureSize[0]) maximumTextureSize = textureSize[0]
// Only need to check for width since opengl textures are always squared }
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize)
// Keep track of the maximum texture size // Release
if (maximumTextureSize < textureSize[0]) maximumTextureSize = textureSize[0] egl.eglTerminate(display)
}
// Release // Return largest texture size found (after making it a multiplier of [Multiplier]), or default
egl.eglTerminate(display) if (maximumTextureSize > SAFE_TEXTURE_LIMIT) {
(maximumTextureSize / MULTIPLIER) * MULTIPLIER
// Return largest texture size found, or default } else {
return max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION) SAFE_TEXTURE_LIMIT
} }
} }
init { const val SAFE_TEXTURE_LIMIT: Int = 2048
throw InstantiationException("This class is not for instantiation")
val CUSTOM_TEXTURE_LIMIT_OPTIONS: List<Int> by lazy {
val steps = ((DEVICE_TEXTURE_LIMIT / MULTIPLIER) - 1)
List(steps) { (it + 2) * MULTIPLIER }.asReversed()
} }
} }
private const val MULTIPLIER: Int = 1024

View file

@ -785,9 +785,11 @@ object ImageUtil {
fun isMaxTextureSizeExceeded(bitmap: Bitmap): Boolean = fun isMaxTextureSizeExceeded(bitmap: Bitmap): Boolean =
isMaxTextureSizeExceeded(bitmap.width, bitmap.height) isMaxTextureSizeExceeded(bitmap.width, bitmap.height)
var hardwareBitmapThreshold: Int = GLUtil.SAFE_TEXTURE_LIMIT
private fun isMaxTextureSizeExceeded(width: Int, height: Int): Boolean { private fun isMaxTextureSizeExceeded(width: Int, height: Int): Boolean {
if (minOf(width, height) <= 0) return false if (minOf(width, height) <= 0) return false
return maxOf(width, height) > GLUtil.maxTextureSize return maxOf(width, height) > hardwareBitmapThreshold
} }
} }

View file

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.core.preference.Preference
import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.core.preference.getEnum import eu.kanade.tachiyomi.core.preference.getEnum
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.util.system.GLUtil
import yokai.i18n.MR import yokai.i18n.MR
class BasePreferences(private val preferenceStore: PreferenceStore) { class BasePreferences(private val preferenceStore: PreferenceStore) {
@ -46,4 +47,6 @@ class BasePreferences(private val preferenceStore: PreferenceStore) {
DEFAULT(MR.strings.recents_long_tap_default), DEFAULT(MR.strings.recents_long_tap_default),
LAST_READ(MR.strings.recents_long_tap_last_read) LAST_READ(MR.strings.recents_long_tap_last_read)
} }
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
} }

View file

@ -513,6 +513,8 @@
<string name="pref_low">Low</string> <string name="pref_low">Low</string>
<string name="pref_lowest">Lowest</string> <string name="pref_lowest">Lowest</string>
<string name="pref_display_profile">Custom display profile</string> <string name="pref_display_profile">Custom display profile</string>
<string name="pref_hardware_bitmap_threshold">Custom hardware bitmap threshold</string>
<string name="pref_hardware_bitmap_threshold_summary">If reader loads a blank image incrementally reduce the threshold.\nSelected: %s</string>
<string name="pref_double_tap_zoom">Double tap to zoom</string> <string name="pref_double_tap_zoom">Double tap to zoom</string>
<!-- Manga details --> <!-- Manga details -->