From c2adf2fe0afbff0c089972d0c9e97a986f1a4123 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Thu, 6 Aug 2020 16:11:00 -0400 Subject: [PATCH] Copy smart background from J2k --- .../tachiyomi/ui/reader/ReaderActivity.kt | 6 +- .../ui/reader/loader/HttpPageLoader.kt | 15 ++ .../tachiyomi/ui/reader/model/ReaderPage.kt | 6 + .../ui/reader/viewer/pager/PagerConfig.kt | 10 + .../ui/reader/viewer/pager/PagerPageHolder.kt | 61 ++++- .../ui/setting/SettingsReaderController.kt | 4 +- .../kanade/tachiyomi/util/system/ImageUtil.kt | 209 ++++++++++++++++++ .../main/java/exh/util/ContextExtensions.kt | 6 + app/src/main/res/values/arrays.xml | 4 + app/src/main/res/values/strings_sy.xml | 2 + 10 files changed, 318 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 17ec4ff83..95bdab5dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -901,10 +901,12 @@ class ReaderActivity : BaseRxActivity() } .launchIn(scope) - preferences.readerTheme().asFlow() + // SY --> + /*preferences.readerTheme().asFlow() .drop(1) // We only care about updates .onEach { recreate() } - .launchIn(scope) + .launchIn(scope)*/ + // SY <-- preferences.showPageNumber().asFlow() .onEach { setPageNumberVisibility(it) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 6d3df4b05..ec16c98ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -1,12 +1,15 @@ package eu.kanade.tachiyomi.ui.reader.loader +import android.graphics.BitmapFactory import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerPageHolder import eu.kanade.tachiyomi.util.lang.plusAssign +import eu.kanade.tachiyomi.util.system.ImageUtil import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID import java.util.concurrent.PriorityBlockingQueue @@ -258,6 +261,18 @@ class HttpPageLoader( } } .doOnNext { + // SY --> + val readerTheme = prefs.readerTheme().get() + if (readerTheme >= 3) { + val stream = chapterCache.getImageFile(imageUrl).inputStream() + val image = BitmapFactory.decodeStream(stream) + page.bg = ImageUtil.autoSetBackground( + image, readerTheme == 2, prefs.context + ) + page.bgType = PagerPageHolder.getBGType(readerTheme, prefs.context) + stream.close() + } + // SY <-- page.stream = { chapterCache.getImageFile(imageUrl).inputStream() } page.status = Page.READY } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt index 35a10331d..768cf1b8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.model +import android.graphics.drawable.Drawable import eu.kanade.tachiyomi.source.model.Page import java.io.InputStream @@ -7,7 +8,12 @@ class ReaderPage( index: Int, url: String = "", imageUrl: String? = null, + // SY --> + var bg: Drawable? = null, + var bgType: Int? = null, + // SY <-- var stream: (() -> InputStream)? = null + ) : Page(index, url, imageUrl, null) { lateinit var chapter: ReaderChapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt index e4dee7643..27722c847 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt @@ -20,6 +20,11 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe var imageCropBorders = false private set + // SY --> + var readerTheme = 0 + private set + // SY <-- + init { preferences.imageScaleType() .register({ imageScaleType = it }, { imagePropertyChangedListener?.invoke() }) @@ -29,6 +34,11 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe preferences.cropBorders() .register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() }) + + // SY --> + preferences.readerTheme() + .register({ readerTheme = it }, { imagePropertyChangedListener?.invoke() }) + // SY <-- } private fun zoomTypeFromPreference(value: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 1d4242a41..732664640 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.annotation.SuppressLint +import android.content.Context +import android.graphics.BitmapFactory import android.graphics.PointF import android.graphics.drawable.Drawable import android.view.GestureDetector @@ -27,20 +29,26 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.github.chrisbanes.photoview.PhotoView import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.glide.GlideApp +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.widget.ViewPagerAdapter +import exh.util.isInNightMode import java.io.InputStream import java.util.concurrent.TimeUnit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy /** * View of the ViewPager that contains a page of a chapter. @@ -242,7 +250,34 @@ class PagerPageHolder( .observeOn(AndroidSchedulers.mainThread()) .doOnNext { isAnimated -> if (!isAnimated) { - initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) + // SY --> + if (viewer.config.readerTheme >= 3) { + val imageView = initSubsamplingImageView() + if (page.bg != null && page.bgType == getBGType( + viewer.config.readerTheme, + context + ) + ) { + imageView.setImage(ImageSource.inputStream(openStream!!)) + imageView.background = page.bg + } + // if the user switches to automatic when pages are already cached, the bg needs to be loaded + else { + val bytesArray = openStream!!.readBytes() + val bytesStream = bytesArray.inputStream() + imageView.setImage(ImageSource.inputStream(bytesStream)) + bytesStream.close() + + launchUI { + imageView.background = setBG(bytesArray) + page.bg = imageView.background + page.bgType = getBGType(viewer.config.readerTheme, context) + } + } + } else { + initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) + } + // SY <-- } else { initImageView().setImage(openStream!!) } @@ -253,6 +288,20 @@ class PagerPageHolder( .subscribe({}, {}) } + // SY --> + private suspend fun setBG(bytesArray: ByteArray): Drawable { + return withContext(Dispatchers.Default) { + val preferences by injectLazy() + ImageUtil.autoSetBackground( + BitmapFactory.decodeByteArray( + bytesArray, 0, bytesArray.size + ), + preferences.readerTheme().get() == 3, context + ) + } + } + // SY <-- + /** * Called when the page has an error. */ @@ -464,4 +513,14 @@ class PagerPageHolder( }) .into(this) } + + // SY --> + companion object { + fun getBGType(readerTheme: Int, context: Context): Int { + return if (readerTheme == 3) { + if (context.isInNightMode()) 2 else 1 + } else 0 + } + } + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt index 1cceb75bf..f3ff3e5ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt @@ -78,8 +78,8 @@ class SettingsReaderController : SettingsController() { intListPreference { key = Keys.readerTheme titleRes = R.string.pref_reader_theme - entriesRes = arrayOf(R.string.black_background, R.string.gray_background, R.string.white_background) - entryValues = arrayOf("1", "2", "0") + entriesRes = arrayOf(R.string.black_background, R.string.gray_background, R.string.white_background, R.string.smart_based_on_page, R.string.smart_based_on_page_and_theme) + entryValues = arrayOf("1", "2", "0", "3", "4") defaultValue = "1" summary = "%s" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index b519a3361..85bb61e01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -1,7 +1,15 @@ package eu.kanade.tachiyomi.util.system +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import eu.kanade.tachiyomi.R import java.io.InputStream import java.net.URLConnection +import kotlin.math.abs object ImageUtil { @@ -71,4 +79,205 @@ object ImageUtil { GIF("image/gif", "gif"), WEBP("image/webp", "webp") } + + // SY --> + fun autoSetBackground(image: Bitmap?, alwaysUseWhite: Boolean, context: Context): Drawable { + val backgroundColor = if (alwaysUseWhite) Color.WHITE else { + context.getResourceColor(R.attr.colorPrimary) + } + if (image == null) return ColorDrawable(backgroundColor) + if (image.width < 50 || image.height < 50) { + return ColorDrawable(backgroundColor) + } + val top = 5 + val bot = image.height - 5 + val left = (image.width * 0.0275).toInt() + val right = image.width - left + val midX = image.width / 2 + val midY = image.height / 2 + val offsetX = (image.width * 0.01).toInt() + val offsetY = (image.height * 0.01).toInt() + val topLeftIsDark = isDark(image.getPixel(left, top)) + val topRightIsDark = isDark(image.getPixel(right, top)) + val midLeftIsDark = isDark(image.getPixel(left, midY)) + val midRightIsDark = isDark(image.getPixel(right, midY)) + val topMidIsDark = isDark(image.getPixel(midX, top)) + val botLeftIsDark = isDark(image.getPixel(left, bot)) + val botRightIsDark = isDark(image.getPixel(right, bot)) + + var darkBG = (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) || + (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark)) + + if (!isWhite(image.getPixel(left, top)) && pixelIsClose(image.getPixel(left, top), image.getPixel(midX, top)) && + !isWhite(image.getPixel(midX, top)) && pixelIsClose(image.getPixel(midX, top), image.getPixel(right, top)) && + !isWhite(image.getPixel(right, top)) && pixelIsClose(image.getPixel(right, top), image.getPixel(right, bot)) && + !isWhite(image.getPixel(right, bot)) && pixelIsClose(image.getPixel(right, bot), image.getPixel(midX, bot)) && + !isWhite(image.getPixel(midX, bot)) && pixelIsClose(image.getPixel(midX, bot), image.getPixel(left, bot)) && + !isWhite(image.getPixel(left, bot)) && pixelIsClose(image.getPixel(left, bot), image.getPixel(left, top)) + ) { + return ColorDrawable(image.getPixel(left, top)) + } + + if (isWhite(image.getPixel(left, top)).toInt() + + isWhite(image.getPixel(right, top)).toInt() + + isWhite(image.getPixel(left, bot)).toInt() + + isWhite(image.getPixel(right, bot)).toInt() > 2 + ) { + darkBG = false + } + + var blackPixel = when { + topLeftIsDark -> image.getPixel(left, top) + topRightIsDark -> image.getPixel(right, top) + botLeftIsDark -> image.getPixel(left, bot) + botRightIsDark -> image.getPixel(right, bot) + else -> backgroundColor + } + + var overallWhitePixels = 0 + var overallBlackPixels = 0 + var topBlackStreak = 0 + var topWhiteStreak = 0 + var botBlackStreak = 0 + var botWhiteStreak = 0 + outer@ for (x in intArrayOf(left, right, left - offsetX, right + offsetX)) { + var whitePixelsStreak = 0 + var whitePixels = 0 + var blackPixelsStreak = 0 + var blackPixels = 0 + var blackStreak = false + var whiteStrak = false + val notOffset = x == left || x == right + for ((index, y) in (0 until image.height step image.height / 25).withIndex()) { + val pixel = image.getPixel(x, y) + val pixelOff = image.getPixel(x + (if (x < image.width / 2) -offsetX else offsetX), y) + if (isWhite(pixel)) { + whitePixelsStreak++ + whitePixels++ + if (notOffset) { + overallWhitePixels++ + } + if (whitePixelsStreak > 14) { + whiteStrak = true + } + if (whitePixelsStreak > 6 && whitePixelsStreak >= index - 1) { + topWhiteStreak = whitePixelsStreak + } + } else { + whitePixelsStreak = 0 + if (isDark(pixel) && isDark(pixelOff)) { + blackPixels++ + if (notOffset) { + overallBlackPixels++ + } + blackPixelsStreak++ + if (blackPixelsStreak >= 14) { + blackStreak = true + } + continue + } + } + if (blackPixelsStreak > 6 && blackPixelsStreak >= index - 1) { + topBlackStreak = blackPixelsStreak + } + blackPixelsStreak = 0 + } + if (blackPixelsStreak > 6) { + botBlackStreak = blackPixelsStreak + } else if (whitePixelsStreak > 6) { + botWhiteStreak = whitePixelsStreak + } + when { + blackPixels > 22 -> { + if (x == right || x == right + offsetX) { + blackPixel = when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } + } + darkBG = true + overallWhitePixels = 0 + break@outer + } + blackStreak -> { + darkBG = true + if (x == right || x == right + offsetX) { + blackPixel = when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } + } + if (blackPixels > 18) { + overallWhitePixels = 0 + break@outer + } + } + whiteStrak || whitePixels > 22 -> darkBG = false + } + } + + val topIsBlackStreak = topBlackStreak > topWhiteStreak + val bottomIsBlackStreak = botBlackStreak > botWhiteStreak + if (overallWhitePixels > 9 && overallWhitePixels > overallBlackPixels) { + darkBG = false + } + if (topIsBlackStreak && bottomIsBlackStreak) { + darkBG = true + } + if (darkBG) { + return if (isWhite(image.getPixel(left, bot)) && isWhite(image.getPixel(right, bot))) { + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(blackPixel, blackPixel, backgroundColor, backgroundColor) + ) + } else if (isWhite(image.getPixel(left, top)) && isWhite(image.getPixel(right, top))) { + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(backgroundColor, backgroundColor, blackPixel, blackPixel) + ) + } else ColorDrawable(blackPixel) + } + if (topIsBlackStreak || ( + topLeftIsDark && topRightIsDark && + isDark(image.getPixel(left - offsetX, top)) && isDark(image.getPixel(right + offsetX, top)) && + (topMidIsDark || overallBlackPixels > 9) + ) + ) { + return GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(blackPixel, blackPixel, backgroundColor, backgroundColor) + ) + } else if (bottomIsBlackStreak || ( + botLeftIsDark && botRightIsDark && + isDark(image.getPixel(left - offsetX, bot)) && isDark(image.getPixel(right + offsetX, bot)) && + (isDark(image.getPixel(midX, bot)) || overallBlackPixels > 9) + ) + ) { + return GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(backgroundColor, backgroundColor, blackPixel, blackPixel) + ) + } + return ColorDrawable(backgroundColor) + } + + private fun isDark(color: Int): Boolean { + return Color.red(color) < 40 && Color.blue(color) < 40 && Color.green(color) < 40 && + Color.alpha(color) > 200 + } + + private fun isWhite(color: Int): Boolean { + return Color.red(color) + Color.blue(color) + Color.green(color) > 740 + } + + private fun Boolean.toInt() = if (this) 1 else 0 + + private fun pixelIsClose(color1: Int, color2: Int): Boolean { + return abs(Color.red(color1) - Color.red(color2)) < 30 && + abs(Color.green(color1) - Color.green(color2)) < 30 && + abs(Color.blue(color1) - Color.blue(color2)) < 30 + } + // SY <-- } diff --git a/app/src/main/java/exh/util/ContextExtensions.kt b/app/src/main/java/exh/util/ContextExtensions.kt index 876e65a06..48f71d2e0 100644 --- a/app/src/main/java/exh/util/ContextExtensions.kt +++ b/app/src/main/java/exh/util/ContextExtensions.kt @@ -3,6 +3,7 @@ package exh.util import android.app.job.JobScheduler import android.content.ClipboardManager import android.content.Context +import android.content.res.Configuration import android.net.wifi.WifiManager /** @@ -16,3 +17,8 @@ val Context.clipboardManager: ClipboardManager val Context.jobScheduler: JobScheduler get() = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler + +fun Context.isInNightMode(): Boolean { + val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return currentNightMode == Configuration.UI_MODE_NIGHT_YES +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index eacccb2d1..290c2535a 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -13,12 +13,16 @@ @string/black_background @string/gray_background @string/white_background + @string/smart_based_on_page + @string/smart_based_on_page_and_theme 1 2 0 + 3 + 4 diff --git a/app/src/main/res/values/strings_sy.xml b/app/src/main/res/values/strings_sy.xml index 5add633e7..351b52fd8 100644 --- a/app/src/main/res/values/strings_sy.xml +++ b/app/src/main/res/values/strings_sy.xml @@ -187,6 +187,8 @@ Auto Webtoon Mode Use auto webtoon mode for manga that are detected to likely use the long strip format Enable zoom out + Smart (based on page) + Smart (based on page and theme)