|
@@ -1,14 +1,25 @@
|
|
|
package eu.kanade.tachiyomi.util.system
|
|
|
|
|
|
+import android.content.Context
|
|
|
+import android.content.res.Configuration
|
|
|
import android.graphics.Bitmap
|
|
|
import android.graphics.BitmapFactory
|
|
|
import android.graphics.Canvas
|
|
|
+import android.graphics.Color
|
|
|
import android.graphics.Rect
|
|
|
+import android.graphics.drawable.ColorDrawable
|
|
|
+import android.graphics.drawable.Drawable
|
|
|
+import android.graphics.drawable.GradientDrawable
|
|
|
+import androidx.core.graphics.alpha
|
|
|
+import androidx.core.graphics.blue
|
|
|
import androidx.core.graphics.createBitmap
|
|
|
+import androidx.core.graphics.green
|
|
|
+import androidx.core.graphics.red
|
|
|
import java.io.ByteArrayInputStream
|
|
|
import java.io.ByteArrayOutputStream
|
|
|
import java.io.InputStream
|
|
|
import java.net.URLConnection
|
|
|
+import kotlin.math.abs
|
|
|
|
|
|
object ImageUtil {
|
|
|
|
|
@@ -153,4 +164,221 @@ object ImageUtil {
|
|
|
enum class Side {
|
|
|
RIGHT, LEFT
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Algorithm for determining what background to accompany a comic/manga page
|
|
|
+ */
|
|
|
+ fun chooseBackground(context: Context, imageStream: InputStream): Drawable {
|
|
|
+ imageStream.mark(imageStream.available() + 1)
|
|
|
+
|
|
|
+ val image = BitmapFactory.decodeStream(imageStream)
|
|
|
+
|
|
|
+ imageStream.reset()
|
|
|
+
|
|
|
+ val whiteColor = Color.WHITE
|
|
|
+ if (image == null) return ColorDrawable(whiteColor)
|
|
|
+ if (image.width < 50 || image.height < 50) {
|
|
|
+ return ColorDrawable(whiteColor)
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 leftOffsetX = left - offsetX
|
|
|
+ val rightOffsetX = right + offsetX
|
|
|
+
|
|
|
+ val topLeftPixel = image.getPixel(left, top)
|
|
|
+ val topRightPixel = image.getPixel(right, top)
|
|
|
+ val midLeftPixel = image.getPixel(left, midY)
|
|
|
+ val midRightPixel = image.getPixel(right, midY)
|
|
|
+ val topCenterPixel = image.getPixel(midX, top)
|
|
|
+ val botLeftPixel = image.getPixel(left, bot)
|
|
|
+ val bottomCenterPixel = image.getPixel(midX, bot)
|
|
|
+ val botRightPixel = image.getPixel(right, bot)
|
|
|
+
|
|
|
+ val topLeftIsDark = topLeftPixel.isDark()
|
|
|
+ val topRightIsDark = topRightPixel.isDark()
|
|
|
+ val midLeftIsDark = midLeftPixel.isDark()
|
|
|
+ val midRightIsDark = midRightPixel.isDark()
|
|
|
+ val topMidIsDark = topCenterPixel.isDark()
|
|
|
+ val botLeftIsDark = botLeftPixel.isDark()
|
|
|
+ val botRightIsDark = botRightPixel.isDark()
|
|
|
+
|
|
|
+ var darkBG = (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) ||
|
|
|
+ (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark))
|
|
|
+
|
|
|
+ val topAndBotPixels = listOf(topLeftPixel, topCenterPixel, topRightPixel, botRightPixel, bottomCenterPixel, botLeftPixel)
|
|
|
+ val isNotWhiteAndCloseTo = topAndBotPixels.mapIndexed { index, color ->
|
|
|
+ val other = topAndBotPixels[(index + 1) % topAndBotPixels.size]
|
|
|
+ !color.isWhite() && color.isCloseTo(other)
|
|
|
+ }
|
|
|
+ if (isNotWhiteAndCloseTo.all { it }) {
|
|
|
+ return ColorDrawable(topLeftPixel)
|
|
|
+ }
|
|
|
+
|
|
|
+ val cornerPixels = listOf(topLeftPixel, topRightPixel, botLeftPixel, botRightPixel)
|
|
|
+ val numberOfWhiteCorners = cornerPixels.map { cornerPixel -> cornerPixel.isWhite() }
|
|
|
+ .filter { it }
|
|
|
+ .size
|
|
|
+ if (numberOfWhiteCorners > 2) {
|
|
|
+ darkBG = false
|
|
|
+ }
|
|
|
+
|
|
|
+ var blackColor = when {
|
|
|
+ topLeftIsDark -> topLeftPixel
|
|
|
+ topRightIsDark -> topRightPixel
|
|
|
+ botLeftIsDark -> botLeftPixel
|
|
|
+ botRightIsDark -> botRightPixel
|
|
|
+ else -> whiteColor
|
|
|
+ }
|
|
|
+
|
|
|
+ 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, leftOffsetX, rightOffsetX)) {
|
|
|
+ var whitePixelsStreak = 0
|
|
|
+ var whitePixels = 0
|
|
|
+ var blackPixelsStreak = 0
|
|
|
+ var blackPixels = 0
|
|
|
+ var blackStreak = false
|
|
|
+ var whiteStreak = false
|
|
|
+ val notOffset = x == left || x == right
|
|
|
+ inner@ 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 (pixel.isWhite()) {
|
|
|
+ whitePixelsStreak++
|
|
|
+ whitePixels++
|
|
|
+ if (notOffset) {
|
|
|
+ overallWhitePixels++
|
|
|
+ }
|
|
|
+ if (whitePixelsStreak > 14) {
|
|
|
+ whiteStreak = true
|
|
|
+ }
|
|
|
+ if (whitePixelsStreak > 6 && whitePixelsStreak >= index - 1) {
|
|
|
+ topWhiteStreak = whitePixelsStreak
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ whitePixelsStreak = 0
|
|
|
+ if (pixel.isDark() && pixelOff.isDark()) {
|
|
|
+ blackPixels++
|
|
|
+ if (notOffset) {
|
|
|
+ overallBlackPixels++
|
|
|
+ }
|
|
|
+ blackPixelsStreak++
|
|
|
+ if (blackPixelsStreak >= 14) {
|
|
|
+ blackStreak = true
|
|
|
+ }
|
|
|
+ continue@inner
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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 == rightOffsetX) {
|
|
|
+ blackColor = when {
|
|
|
+ topRightIsDark -> topRightPixel
|
|
|
+ botRightIsDark -> botRightPixel
|
|
|
+ else -> blackColor
|
|
|
+ }
|
|
|
+ }
|
|
|
+ darkBG = true
|
|
|
+ overallWhitePixels = 0
|
|
|
+ break@outer
|
|
|
+ }
|
|
|
+ blackStreak -> {
|
|
|
+ darkBG = true
|
|
|
+ if (x == right || x == rightOffsetX) {
|
|
|
+ blackColor = when {
|
|
|
+ topRightIsDark -> topRightPixel
|
|
|
+ botRightIsDark -> botRightPixel
|
|
|
+ else -> blackColor
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (blackPixels > 18) {
|
|
|
+ overallWhitePixels = 0
|
|
|
+ break@outer
|
|
|
+ }
|
|
|
+ }
|
|
|
+ whiteStreak || whitePixels > 22 -> darkBG = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ val topIsBlackStreak = topBlackStreak > topWhiteStreak
|
|
|
+ val bottomIsBlackStreak = botBlackStreak > botWhiteStreak
|
|
|
+ if (overallWhitePixels > 9 && overallWhitePixels > overallBlackPixels) {
|
|
|
+ darkBG = false
|
|
|
+ }
|
|
|
+ if (topIsBlackStreak && bottomIsBlackStreak) {
|
|
|
+ darkBG = true
|
|
|
+ }
|
|
|
+
|
|
|
+ val isLandscape = context.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
|
|
|
+ if (isLandscape) {
|
|
|
+ return when {
|
|
|
+ darkBG -> ColorDrawable(blackColor)
|
|
|
+ else -> ColorDrawable(whiteColor)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ val botCornersIsWhite = botLeftPixel.isWhite() && botRightPixel.isWhite()
|
|
|
+ val topCornersIsWhite = topLeftPixel.isWhite() && topRightPixel.isWhite()
|
|
|
+
|
|
|
+ val topCornersIsDark = topLeftIsDark && topRightIsDark
|
|
|
+ val botCornersIsDark = botLeftIsDark && botRightIsDark
|
|
|
+
|
|
|
+ val topOffsetCornersIsDark = image.getPixel(leftOffsetX, top).isDark() && image.getPixel(rightOffsetX, top).isDark()
|
|
|
+ val botOffsetCornersIsDark = image.getPixel(leftOffsetX, bot).isDark() && image.getPixel(rightOffsetX, bot).isDark()
|
|
|
+
|
|
|
+ val gradient = when {
|
|
|
+ darkBG && botCornersIsWhite -> {
|
|
|
+ intArrayOf(blackColor, blackColor, whiteColor, whiteColor)
|
|
|
+ }
|
|
|
+ darkBG && topCornersIsWhite -> {
|
|
|
+ intArrayOf(whiteColor, whiteColor, blackColor, blackColor)
|
|
|
+ }
|
|
|
+ darkBG -> {
|
|
|
+ return ColorDrawable(blackColor)
|
|
|
+ }
|
|
|
+ topIsBlackStreak || (topCornersIsDark && topOffsetCornersIsDark && (topMidIsDark || overallBlackPixels > 9)) -> {
|
|
|
+ intArrayOf(blackColor, blackColor, whiteColor, whiteColor)
|
|
|
+ }
|
|
|
+ bottomIsBlackStreak || (botCornersIsDark && botOffsetCornersIsDark && (bottomCenterPixel.isDark() || overallBlackPixels > 9)) -> {
|
|
|
+ intArrayOf(whiteColor, whiteColor, blackColor, blackColor)
|
|
|
+ }
|
|
|
+ else -> {
|
|
|
+ return ColorDrawable(whiteColor)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return GradientDrawable(
|
|
|
+ GradientDrawable.Orientation.TOP_BOTTOM,
|
|
|
+ gradient
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun Int.isDark(): Boolean =
|
|
|
+ red < 40 && blue < 40 && green < 40 && alpha > 200
|
|
|
+
|
|
|
+ private fun Int.isCloseTo(other: Int): Boolean =
|
|
|
+ abs(red - other.red) < 30 && abs(green - other.green) < 30 && abs(blue - other.blue) < 30
|
|
|
+
|
|
|
+ private fun Int.isWhite(): Boolean =
|
|
|
+ red + blue + green > 740
|
|
|
}
|