mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Long Strip Split for Webtoon (#5759)
* Long Strip Split for Webtoon * Review Changes * Review Changes 2 + Rebase
This commit is contained in:
		@@ -83,12 +83,14 @@ class PreferencesHelper(val context: Context) {
 | 
			
		||||
 | 
			
		||||
    fun dualPageSplitPaged() = flowPrefs.getBoolean("pref_dual_page_split", false)
 | 
			
		||||
 | 
			
		||||
    fun dualPageSplitWebtoon() = flowPrefs.getBoolean("pref_dual_page_split_webtoon", false)
 | 
			
		||||
 | 
			
		||||
    fun dualPageInvertPaged() = flowPrefs.getBoolean("pref_dual_page_invert", false)
 | 
			
		||||
 | 
			
		||||
    fun dualPageSplitWebtoon() = flowPrefs.getBoolean("pref_dual_page_split_webtoon", false)
 | 
			
		||||
 | 
			
		||||
    fun dualPageInvertWebtoon() = flowPrefs.getBoolean("pref_dual_page_invert_webtoon", false)
 | 
			
		||||
 | 
			
		||||
    fun longStripSplitWebtoon() = flowPrefs.getBoolean("pref_long_strip_split_webtoon", true)
 | 
			
		||||
 | 
			
		||||
    fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
 | 
			
		||||
 | 
			
		||||
    fun trueColor() = flowPrefs.getBoolean("pref_true_color_key", false)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.reader.model
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.ImageUtil
 | 
			
		||||
 | 
			
		||||
class StencilPage(
 | 
			
		||||
    parent: ReaderPage,
 | 
			
		||||
    val splitData: ImageUtil.SplitData,
 | 
			
		||||
) : ReaderPage(parent.index, parent.url, parent.imageUrl) {
 | 
			
		||||
 | 
			
		||||
    override var chapter: ReaderChapter = parent.chapter
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        stream = parent.stream
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -86,8 +86,8 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
 | 
			
		||||
        binding.pagerPrefsGroup.cropBorders.bindToPreference(preferences.cropBorders())
 | 
			
		||||
        binding.pagerPrefsGroup.navigatePan.bindToPreference(preferences.navigateToPan())
 | 
			
		||||
 | 
			
		||||
        // Makes so that dual page invert gets hidden away when turning of dual page split
 | 
			
		||||
        binding.pagerPrefsGroup.dualPageSplit.bindToPreference(preferences.dualPageSplitPaged())
 | 
			
		||||
        // Makes it so that dual page invert gets hidden away when dual page split is turned off
 | 
			
		||||
        preferences.dualPageSplitPaged()
 | 
			
		||||
            .asHotFlow { binding.pagerPrefsGroup.dualPageInvert.isVisible = it }
 | 
			
		||||
            .launchIn((context as ReaderActivity).lifecycleScope)
 | 
			
		||||
@@ -110,11 +110,12 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
 | 
			
		||||
        binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(preferences.cropBordersWebtoon())
 | 
			
		||||
        binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(preferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
 | 
			
		||||
 | 
			
		||||
        // Makes so that dual page invert gets hidden away when turning of dual page split
 | 
			
		||||
        binding.webtoonPrefsGroup.dualPageSplit.bindToPreference(preferences.dualPageSplitWebtoon())
 | 
			
		||||
        // Makes it so that dual page invert gets hidden away when dual page split is turned off
 | 
			
		||||
        preferences.dualPageSplitWebtoon()
 | 
			
		||||
            .asHotFlow { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it }
 | 
			
		||||
            .launchIn((context as ReaderActivity).lifecycleScope)
 | 
			
		||||
        binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(preferences.dualPageInvertWebtoon())
 | 
			
		||||
        binding.webtoonPrefsGroup.longStripSplit.bindToPreference(preferences.longStripSplitWebtoon())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,12 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.StencilPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * RecyclerView Adapter used by this [viewer] to where [ViewerChapters] updates are posted.
 | 
			
		||||
@@ -25,6 +27,26 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter<RecyclerV
 | 
			
		||||
 | 
			
		||||
    var currentChapter: ReaderChapter? = null
 | 
			
		||||
 | 
			
		||||
    fun onLongStripSplit(currentStrip: Any?, newStrips: List<StencilPage>) {
 | 
			
		||||
        if (currentStrip is StencilPage) return
 | 
			
		||||
 | 
			
		||||
        val placeAtIndex = items.indexOf(currentStrip) + 1
 | 
			
		||||
        // Stop constantly adding split images
 | 
			
		||||
        if (items[placeAtIndex] is StencilPage) return
 | 
			
		||||
 | 
			
		||||
        val updatedItems = items.toMutableList()
 | 
			
		||||
        updatedItems.addAll(placeAtIndex, newStrips)
 | 
			
		||||
        updateItems(updatedItems)
 | 
			
		||||
        logcat { "New adapter item count is $itemCount" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun cleanupSplitStrips() {
 | 
			
		||||
        if (items.any { it is StencilPage }) {
 | 
			
		||||
            val updatedItems = items.filterNot { it is StencilPage }
 | 
			
		||||
            updateItems(updatedItems)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Context that has been wrapped to use the correct theme values based on the
 | 
			
		||||
     * current app theme and reader background color
 | 
			
		||||
@@ -79,6 +101,10 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter<RecyclerV
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateItems(newItems)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateItems(newItems: List<Any>) {
 | 
			
		||||
        val result = DiffUtil.calculateDiff(Callback(items, newItems))
 | 
			
		||||
        items = newItems
 | 
			
		||||
        result.dispatchUpdatesTo(this)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,11 @@ class WebtoonConfig(
 | 
			
		||||
    var sidePadding = 0
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    var longStripSplit = false
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    var longStripSplitChangedListener: ((Boolean) -> Unit)? = null
 | 
			
		||||
 | 
			
		||||
    val theme = preferences.readerTheme().get()
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
@@ -57,6 +62,15 @@ class WebtoonConfig(
 | 
			
		||||
        preferences.dualPageInvertWebtoon()
 | 
			
		||||
            .register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() })
 | 
			
		||||
 | 
			
		||||
        preferences.longStripSplitWebtoon()
 | 
			
		||||
            .register(
 | 
			
		||||
                { longStripSplit = it },
 | 
			
		||||
                {
 | 
			
		||||
                    imagePropertyChangedListener?.invoke()
 | 
			
		||||
                    longStripSplitChangedListener?.invoke(it)
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        preferences.readerTheme().asFlow()
 | 
			
		||||
            .drop(1)
 | 
			
		||||
            .distinctUntilChanged()
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,12 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
 | 
			
		||||
import eu.kanade.tachiyomi.databinding.ReaderErrorBinding
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.StencilPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
 | 
			
		||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.ImageUtil
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.ImageUtil.SplitData
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.dpToPx
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import rx.Subscription
 | 
			
		||||
@@ -274,17 +276,37 @@ class WebtoonPageHolder(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun process(imageStream: BufferedInputStream): InputStream {
 | 
			
		||||
        if (!viewer.config.dualPageSplit) {
 | 
			
		||||
            return imageStream
 | 
			
		||||
        if (viewer.config.dualPageSplit) {
 | 
			
		||||
            val isDoublePage = ImageUtil.isWideImage(imageStream)
 | 
			
		||||
            if (isDoublePage) {
 | 
			
		||||
                val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
 | 
			
		||||
                return ImageUtil.splitAndMerge(imageStream, upperSide)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val isDoublePage = ImageUtil.isWideImage(imageStream)
 | 
			
		||||
        if (!isDoublePage) {
 | 
			
		||||
            return imageStream
 | 
			
		||||
        if (viewer.config.longStripSplit) {
 | 
			
		||||
            if (page is StencilPage) {
 | 
			
		||||
                val splitData = (page as StencilPage).splitData
 | 
			
		||||
                return ImageUtil.splitStrip(imageStream, splitData)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val isStripSplitNeeded = ImageUtil.isStripSplitNeeded(imageStream)
 | 
			
		||||
            if (isStripSplitNeeded) {
 | 
			
		||||
                val splitData = onStripSplit(imageStream)
 | 
			
		||||
                splitData?.let { return ImageUtil.splitStrip(imageStream, it) }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
 | 
			
		||||
        return ImageUtil.splitAndMerge(imageStream, upperSide)
 | 
			
		||||
        return imageStream
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun onStripSplit(imageStream: BufferedInputStream): SplitData? {
 | 
			
		||||
        val page = page ?: return null
 | 
			
		||||
        val splitData = ImageUtil.getSplitDataForStream(imageStream).toMutableList()
 | 
			
		||||
        val toReturn = splitData.removeFirstOrNull()
 | 
			
		||||
        val newPages = splitData.map { StencilPage(page, it) }
 | 
			
		||||
        viewer.onLongStripSplit(page, newPages)
 | 
			
		||||
        return toReturn
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.StencilPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
 | 
			
		||||
@@ -154,6 +155,12 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
 | 
			
		||||
            activity.binding.navigationOverlay.setNavigation(config.navigator, showOnStart)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        config.longStripSplitChangedListener = { enabled ->
 | 
			
		||||
            if (!enabled) {
 | 
			
		||||
                cleanupSplitStrips()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        frame.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
 | 
			
		||||
        frame.addView(recycler)
 | 
			
		||||
    }
 | 
			
		||||
@@ -354,4 +361,15 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
 | 
			
		||||
            min(position + 3, adapter.itemCount - 1),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onLongStripSplit(currentStrip: Any?, newStrips: List<StencilPage>) {
 | 
			
		||||
        activity.runOnUiThread {
 | 
			
		||||
            // Need to insert on UI thread else images will go blank
 | 
			
		||||
            adapter.onLongStripSplit(currentStrip, newStrips)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun cleanupSplitStrips() {
 | 
			
		||||
        adapter.cleanupSplitStrips()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -289,6 +289,11 @@ class SettingsReaderController : SettingsController() {
 | 
			
		||||
                summaryRes = R.string.pref_dual_page_invert_summary
 | 
			
		||||
                visibleIf(preferences.dualPageSplitWebtoon()) { it }
 | 
			
		||||
            }
 | 
			
		||||
            switchPreference {
 | 
			
		||||
                bindTo(preferences.longStripSplitWebtoon())
 | 
			
		||||
                titleRes = R.string.pref_long_strip_split
 | 
			
		||||
                summaryRes = R.string.pref_long_strip_split_summary
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        preferenceCategory {
 | 
			
		||||
 
 | 
			
		||||
@@ -206,35 +206,6 @@ object ImageUtil {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
 | 
			
		||||
        // Values are stored as they get modified during split loop
 | 
			
		||||
        val imageHeight = options.outHeight
 | 
			
		||||
        val imageWidth = options.outWidth
 | 
			
		||||
 | 
			
		||||
        val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
 | 
			
		||||
        // -1 so it doesn't try to split when imageHeight = getDisplayHeightInPx
 | 
			
		||||
        val partCount = (imageHeight - 1) / splitHeight + 1
 | 
			
		||||
 | 
			
		||||
        val optimalSplitHeight = imageHeight / partCount
 | 
			
		||||
 | 
			
		||||
        val splitDataList = (0 until partCount).fold(mutableListOf<SplitData>()) { list, index ->
 | 
			
		||||
            list.apply {
 | 
			
		||||
                // Only continue if the list is empty or there is image remaining
 | 
			
		||||
                if (isEmpty() || imageHeight > last().bottomOffset) {
 | 
			
		||||
                    val topOffset = index * optimalSplitHeight
 | 
			
		||||
                    var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
 | 
			
		||||
 | 
			
		||||
                    val remainingHeight = imageHeight - (topOffset + outputImageHeight)
 | 
			
		||||
                    // If remaining height is smaller or equal to 1/3th of
 | 
			
		||||
                    // optimal split height then include it in current page
 | 
			
		||||
                    if (remainingHeight <= (optimalSplitHeight / 3)) {
 | 
			
		||||
                        outputImageHeight += remainingHeight
 | 
			
		||||
                    }
 | 
			
		||||
                    add(SplitData(index, topOffset, outputImageHeight))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
 | 
			
		||||
            BitmapRegionDecoder.newInstance(imageFile.openInputStream())
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -247,10 +218,12 @@ object ImageUtil {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logcat {
 | 
			
		||||
            "Splitting image with height of $imageHeight into $partCount part " +
 | 
			
		||||
                "with estimated ${optimalSplitHeight}px height per split"
 | 
			
		||||
        }
 | 
			
		||||
        val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { inJustDecodeBounds = false }
 | 
			
		||||
 | 
			
		||||
        // Values are stored as they get modified during split loop
 | 
			
		||||
        val imageWidth = options.outWidth
 | 
			
		||||
 | 
			
		||||
        val splitDataList = getSplitDataForOptions(options)
 | 
			
		||||
 | 
			
		||||
        return try {
 | 
			
		||||
            splitDataList.forEach { splitData ->
 | 
			
		||||
@@ -285,6 +258,93 @@ object ImageUtil {
 | 
			
		||||
    private fun splitImagePath(imageFilePath: String, index: Int) =
 | 
			
		||||
        imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check whether the image is a long Strip that needs splitting
 | 
			
		||||
     * @return true if the image is not animated and it's height is greater than image width and screen height
 | 
			
		||||
     */
 | 
			
		||||
    fun isStripSplitNeeded(imageStream: BufferedInputStream): Boolean {
 | 
			
		||||
        if (isAnimatedAndSupported(imageStream)) return false
 | 
			
		||||
        val options = extractImageOptions(imageStream)
 | 
			
		||||
 | 
			
		||||
        val imageHeightIsBiggerThanWidth = options.outHeight > options.outWidth
 | 
			
		||||
        val imageHeightBiggerThanScreenHeight = options.outHeight > getDisplayMaxHeightInPx
 | 
			
		||||
        return imageHeightIsBiggerThanWidth && imageHeightBiggerThanScreenHeight
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Split the imageStream according to the provided splitData
 | 
			
		||||
     */
 | 
			
		||||
    fun splitStrip(imageStream: InputStream, splitData: SplitData): InputStream {
 | 
			
		||||
        val bitmapRegionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
 | 
			
		||||
            BitmapRegionDecoder.newInstance(imageStream)
 | 
			
		||||
        } else {
 | 
			
		||||
            @Suppress("DEPRECATION")
 | 
			
		||||
            BitmapRegionDecoder.newInstance(imageStream, false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (bitmapRegionDecoder == null) {
 | 
			
		||||
            throw Exception("Failed to create new instance of BitmapRegionDecoder")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logcat {
 | 
			
		||||
            "WebtoonSplit #${splitData.index} with topOffset=${splitData.topOffset} " +
 | 
			
		||||
                "outputImageHeight=${splitData.outputImageHeight} bottomOffset=${splitData.bottomOffset}"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val options = extractImageOptions(imageStream).apply { inJustDecodeBounds = false }
 | 
			
		||||
 | 
			
		||||
        val region = Rect(0, splitData.topOffset, splitData.outputImageHeight, splitData.bottomOffset)
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
 | 
			
		||||
            val outputStream = ByteArrayOutputStream()
 | 
			
		||||
            splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
 | 
			
		||||
            return ByteArrayInputStream(outputStream.toByteArray())
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            throw e
 | 
			
		||||
        } finally {
 | 
			
		||||
            bitmapRegionDecoder.recycle()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getSplitDataForStream(imageStream: InputStream): List<SplitData> {
 | 
			
		||||
        val options = extractImageOptions(imageStream)
 | 
			
		||||
        return getSplitDataForOptions(options)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getSplitDataForOptions(options: BitmapFactory.Options): List<SplitData> {
 | 
			
		||||
        val imageHeight = options.outHeight
 | 
			
		||||
 | 
			
		||||
        val splitHeight = (getDisplayMaxHeightInPx * 1.5).toInt()
 | 
			
		||||
        // -1 so it doesn't try to split when imageHeight = splitHeight
 | 
			
		||||
        val partCount = (imageHeight - 1) / splitHeight + 1
 | 
			
		||||
 | 
			
		||||
        val optimalSplitHeight = imageHeight / partCount
 | 
			
		||||
 | 
			
		||||
        logcat {
 | 
			
		||||
            "Generating SplitData for image with height of $imageHeight. " +
 | 
			
		||||
                "Estimated $partCount part and ${optimalSplitHeight}px height per part"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return mutableListOf<SplitData>().apply {
 | 
			
		||||
            for (index in (0 until partCount)) {
 | 
			
		||||
                // Only continue if the list is empty or there is image remaining
 | 
			
		||||
                if (isNotEmpty() && imageHeight <= last().bottomOffset) break
 | 
			
		||||
 | 
			
		||||
                val topOffset = index * optimalSplitHeight
 | 
			
		||||
                var outputImageHeight = min(optimalSplitHeight, imageHeight - topOffset)
 | 
			
		||||
 | 
			
		||||
                val remainingHeight = imageHeight - (topOffset + outputImageHeight)
 | 
			
		||||
                // If remaining height is smaller or equal to 1/10th of
 | 
			
		||||
                // optimal split height then include it in current page
 | 
			
		||||
                if (remainingHeight <= (optimalSplitHeight / 10)) {
 | 
			
		||||
                    outputImageHeight += remainingHeight
 | 
			
		||||
                }
 | 
			
		||||
                add(SplitData(index, topOffset, outputImageHeight))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data class SplitData(
 | 
			
		||||
        val index: Int,
 | 
			
		||||
        val topOffset: Int,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user