Center padding option for Surface Duos/other foldables (#634)
* Add center margin option for horizontal dual page view * Update readme to make the point of the fork obvious * Center margins now added to large single images that would display in place of two smaller pages * Cleanup; reworked preference into an int selector to specify which margins to add * Suggested fixes and and readme reversion which somehow got skipped last commit * Missed the build block in readme. Oops.
This commit is contained in:
parent
5807920636
commit
89600fc7aa
@ -85,8 +85,8 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
<details><summary>Bugs</summary>
|
<details><summary>Bugs</summary>
|
||||||
|
|
||||||
* Include version (More → About → Version)
|
* Include version (More → About → Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* If not latest, try updating, it may have already been solved
|
||||||
* Preview version is equal to the number of commits as seen in the main page
|
* Preview version is equal to the number of commits as seen in the main page
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* Include steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
|
@ -517,5 +517,7 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun pageLayout() = flowPrefs.getInt("page_layout", PagerConfig.PageLayout.AUTOMATIC)
|
fun pageLayout() = flowPrefs.getInt("page_layout", PagerConfig.PageLayout.AUTOMATIC)
|
||||||
|
|
||||||
|
fun centerMarginType() = flowPrefs.getInt("center_margin_type", PagerConfig.CenterMarginType.NONE)
|
||||||
|
|
||||||
fun invertDoublePages() = flowPrefs.getBoolean("invert_double_pages", false)
|
fun invertDoublePages() = flowPrefs.getBoolean("invert_double_pages", false)
|
||||||
}
|
}
|
||||||
|
@ -856,7 +856,7 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
return imageSaver.save(
|
return imageSaver.save(
|
||||||
image = Image.Page(
|
image = Image.Page(
|
||||||
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, bg) },
|
inputStream = { ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, 0, bg) },
|
||||||
name = filename,
|
name = filename,
|
||||||
location = location,
|
location = location,
|
||||||
),
|
),
|
||||||
|
@ -97,6 +97,7 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
|
|||||||
binding.pagerPrefsGroup.pageTransitionsPager.bindToPreference(preferences.pageTransitionsPager())
|
binding.pagerPrefsGroup.pageTransitionsPager.bindToPreference(preferences.pageTransitionsPager())
|
||||||
binding.pagerPrefsGroup.pageLayout.bindToPreference(preferences.pageLayout())
|
binding.pagerPrefsGroup.pageLayout.bindToPreference(preferences.pageLayout())
|
||||||
binding.pagerPrefsGroup.invertDoublePages.bindToPreference(preferences.invertDoublePages())
|
binding.pagerPrefsGroup.invertDoublePages.bindToPreference(preferences.invertDoublePages())
|
||||||
|
binding.pagerPrefsGroup.centerMarginType.bindToPreference(preferences.centerMarginType())
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ class PagerConfig(
|
|||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
var pageCanvasColor = Color.WHITE
|
var pageCanvasColor = Color.WHITE
|
||||||
|
|
||||||
|
var centerMarginType = CenterMarginType.NONE
|
||||||
|
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -150,6 +153,9 @@ class PagerConfig(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
preferences.centerMarginType()
|
||||||
|
.register({ centerMarginType = it }, { imagePropertyChangedListener?.invoke() })
|
||||||
|
|
||||||
preferences.invertDoublePages()
|
preferences.invertDoublePages()
|
||||||
.register({ invertDoublePages = it && dualPageSplit == false }, { imagePropertyChangedListener?.invoke() })
|
.register({ invertDoublePages = it && dualPageSplit == false }, { imagePropertyChangedListener?.invoke() })
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -197,6 +203,13 @@ class PagerConfig(
|
|||||||
navigationModeChangedListener?.invoke()
|
navigationModeChangedListener?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object CenterMarginType {
|
||||||
|
const val NONE = 0
|
||||||
|
const val DOUBLE_PAGE_CENTER_MARGIN = 1
|
||||||
|
const val WIDE_PAGE_CENTER_MARGIN = 2
|
||||||
|
const val DOUBLE_AND_WIDE_CENTER_MARGIN = 3
|
||||||
|
}
|
||||||
|
|
||||||
object PageLayout {
|
object PageLayout {
|
||||||
const val SINGLE_PAGE = 0
|
const val SINGLE_PAGE = 0
|
||||||
const val DOUBLE_PAGES = 1
|
const val DOUBLE_PAGES = 1
|
||||||
|
@ -28,6 +28,7 @@ import java.io.BufferedInputStream
|
|||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -353,7 +354,18 @@ class PagerPageHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
||||||
imageStream2 ?: return imageStream
|
// Handle adding a center margin to wide images if requested
|
||||||
|
if (imageStream2 == null) {
|
||||||
|
if (imageStream is BufferedInputStream && ImageUtil.isWideImage(imageStream) &&
|
||||||
|
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
|
||||||
|
!viewer.config.imageCropBorders
|
||||||
|
) {
|
||||||
|
return ImageUtil.AddHorizontalCenterMargin(imageStream, getHeight(), context)
|
||||||
|
} else {
|
||||||
|
return imageStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (page.fullPage) return imageStream
|
if (page.fullPage) return imageStream
|
||||||
if (ImageUtil.isAnimatedAndSupported(imageStream)) {
|
if (ImageUtil.isAnimatedAndSupported(imageStream)) {
|
||||||
page.fullPage = true
|
page.fullPage = true
|
||||||
@ -424,7 +436,12 @@ class PagerPageHolder(
|
|||||||
|
|
||||||
imageStream.close()
|
imageStream.close()
|
||||||
imageStream2.close()
|
imageStream2.close()
|
||||||
return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, viewer.config.pageCanvasColor) {
|
|
||||||
|
val centerMargin = if (viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN > 0 &&
|
||||||
|
!viewer.config.imageCropBorders
|
||||||
|
) 96 / (max(1, getHeight()) / max(height, height2)) else 0
|
||||||
|
|
||||||
|
return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, centerMargin, viewer.config.pageCanvasColor) {
|
||||||
viewer.scope.launchUI {
|
viewer.scope.launchUI {
|
||||||
if (it == 100) {
|
if (it == 100) {
|
||||||
progressIndicator.hide()
|
progressIndicator.hide()
|
||||||
@ -461,7 +478,11 @@ class PagerPageHolder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageUtil.splitInHalf(imageStream, side)
|
val sideMargin = if ((viewer.config.centerMarginType and PagerConfig.CenterMarginType.DOUBLE_PAGE_CENTER_MARGIN) > 0 &&
|
||||||
|
viewer.config.doublePages && !viewer.config.imageCropBorders
|
||||||
|
) 48 else 0
|
||||||
|
|
||||||
|
return ImageUtil.splitInHalf(imageStream, side, sideMargin)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPageSplit(page: ReaderPage) {
|
private fun onPageSplit(page: ReaderPage) {
|
||||||
|
@ -508,6 +508,18 @@ class SettingsReaderController : SettingsController() {
|
|||||||
titleRes = R.string.invert_double_pages
|
titleRes = R.string.invert_double_pages
|
||||||
visibleIf(preferences.pageLayout()) { it != PagerConfig.PageLayout.SINGLE_PAGE }
|
visibleIf(preferences.pageLayout()) { it != PagerConfig.PageLayout.SINGLE_PAGE }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intListPreference {
|
||||||
|
bindTo(preferences.centerMarginType())
|
||||||
|
titleRes = R.string.center_margin
|
||||||
|
entriesRes = arrayOf(
|
||||||
|
R.string.center_margin_none,
|
||||||
|
R.string.center_margin_double_page,
|
||||||
|
R.string.center_margin_wide_page,
|
||||||
|
R.string.center_margin_double_and_wide_page,
|
||||||
|
)
|
||||||
|
entryValues = arrayOf("0", "1", "2", "3")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.util.system
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
@ -128,16 +127,16 @@ object ImageUtil {
|
|||||||
/**
|
/**
|
||||||
* Extract the 'side' part from imageStream and return it as InputStream.
|
* Extract the 'side' part from imageStream and return it as InputStream.
|
||||||
*/
|
*/
|
||||||
fun splitInHalf(imageStream: InputStream, side: Side): InputStream {
|
fun splitInHalf(imageStream: InputStream, side: Side, sidePadding: Int): InputStream {
|
||||||
val imageBytes = imageStream.readBytes()
|
val imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
val height = imageBitmap.height
|
val height = imageBitmap.height
|
||||||
val width = imageBitmap.width
|
val width = imageBitmap.width
|
||||||
|
|
||||||
val singlePage = Rect(0, 0, width / 2, height)
|
val singlePage = Rect(0, 0, width / 2 + sidePadding, height)
|
||||||
|
|
||||||
val half = createBitmap(width / 2, height)
|
val half = createBitmap(width / 2 + sidePadding, height)
|
||||||
val part = when (side) {
|
val part = when (side) {
|
||||||
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
|
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
|
||||||
Side.LEFT -> Rect(0, 0, width / 2, height)
|
Side.LEFT -> Rect(0, 0, width / 2, height)
|
||||||
@ -152,7 +151,8 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split the image into left and right parts, then merge them into a new image.
|
* Split the image into left and right parts, then merge them into a
|
||||||
|
* new vertically-aligned image.
|
||||||
*/
|
*/
|
||||||
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream {
|
fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream {
|
||||||
val imageBytes = imageStream.readBytes()
|
val imageBytes = imageStream.readBytes()
|
||||||
@ -187,6 +187,40 @@ object ImageUtil {
|
|||||||
enum class Side {
|
enum class Side {
|
||||||
RIGHT, LEFT
|
RIGHT, LEFT
|
||||||
}
|
}
|
||||||
|
// SY -->
|
||||||
|
/**
|
||||||
|
* Split the image into left and right parts, then merge them into a
|
||||||
|
* new image with added center padding scaled relative to the height of the display view
|
||||||
|
* to compensate for scaling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun AddHorizontalCenterMargin(imageStream: InputStream, viewHeight: Int, backgroundContext: Context): InputStream {
|
||||||
|
val imageBitmap = ImageDecoder.newInstance(imageStream)?.decode()!!
|
||||||
|
val height = imageBitmap.height
|
||||||
|
val width = imageBitmap.width
|
||||||
|
|
||||||
|
val centerPadding = 96 / (max(1, viewHeight) / height)
|
||||||
|
|
||||||
|
val leftSourcePart = Rect(0, 0, width / 2, height)
|
||||||
|
val rightSourcePart = Rect(width / 2, 0, width, height)
|
||||||
|
val leftTargetPart = Rect(0, 0, width / 2, height)
|
||||||
|
val rightTargetPart = Rect(width / 2 + centerPadding, 0, width + centerPadding, height)
|
||||||
|
|
||||||
|
val bgColor = chooseBackground(backgroundContext, imageStream)
|
||||||
|
bgColor.setBounds(width / 2, 0, width / 2 + centerPadding, height)
|
||||||
|
val result = createBitmap(width + centerPadding, height)
|
||||||
|
|
||||||
|
result.applyCanvas {
|
||||||
|
drawBitmap(imageBitmap, leftSourcePart, leftTargetPart, null)
|
||||||
|
drawBitmap(imageBitmap, rightSourcePart, rightTargetPart, null)
|
||||||
|
bgColor.draw(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the image is considered a tall image.
|
* Check whether the image is considered a tall image.
|
||||||
@ -534,31 +568,37 @@ object ImageUtil {
|
|||||||
imageBitmap: Bitmap,
|
imageBitmap: Bitmap,
|
||||||
imageBitmap2: Bitmap,
|
imageBitmap2: Bitmap,
|
||||||
isLTR: Boolean,
|
isLTR: Boolean,
|
||||||
|
centerMargin: Int,
|
||||||
@ColorInt background: Int = Color.WHITE,
|
@ColorInt background: Int = Color.WHITE,
|
||||||
|
|
||||||
progressCallback: ((Int) -> Unit)? = null,
|
progressCallback: ((Int) -> Unit)? = null,
|
||||||
): ByteArrayInputStream {
|
): ByteArrayInputStream {
|
||||||
val height = imageBitmap.height
|
val height = imageBitmap.height
|
||||||
val width = imageBitmap.width
|
val width = imageBitmap.width
|
||||||
val height2 = imageBitmap2.height
|
val height2 = imageBitmap2.height
|
||||||
val width2 = imageBitmap2.width
|
val width2 = imageBitmap2.width
|
||||||
|
|
||||||
val maxHeight = max(height, height2)
|
val maxHeight = max(height, height2)
|
||||||
val result = Bitmap.createBitmap(width + width2, max(height, height2), Bitmap.Config.ARGB_8888)
|
|
||||||
|
val result = Bitmap.createBitmap(width + width2 + centerMargin, max(height, height2), Bitmap.Config.ARGB_8888)
|
||||||
val canvas = Canvas(result)
|
val canvas = Canvas(result)
|
||||||
canvas.drawColor(background)
|
canvas.drawColor(background)
|
||||||
val upperPart = Rect(
|
val upperPart = Rect(
|
||||||
if (isLTR) 0 else width2,
|
if (isLTR) 0 else width2 + centerMargin,
|
||||||
(maxHeight - imageBitmap.height) / 2,
|
(maxHeight - imageBitmap.height) / 2,
|
||||||
(if (isLTR) 0 else width2) + imageBitmap.width,
|
(if (isLTR) 0 else width2 + centerMargin) + imageBitmap.width,
|
||||||
imageBitmap.height + (maxHeight - imageBitmap.height) / 2,
|
imageBitmap.height + (maxHeight - imageBitmap.height) / 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawBitmap(imageBitmap, imageBitmap.rect, upperPart, null)
|
canvas.drawBitmap(imageBitmap, imageBitmap.rect, upperPart, null)
|
||||||
progressCallback?.invoke(98)
|
progressCallback?.invoke(98)
|
||||||
val bottomPart = Rect(
|
val bottomPart = Rect(
|
||||||
if (!isLTR) 0 else width,
|
if (!isLTR) 0 else width + centerMargin,
|
||||||
(maxHeight - imageBitmap2.height) / 2,
|
(maxHeight - imageBitmap2.height) / 2,
|
||||||
(if (!isLTR) 0 else width) + imageBitmap2.width,
|
(if (!isLTR) 0 else width + centerMargin) + imageBitmap2.width,
|
||||||
imageBitmap2.height + (maxHeight - imageBitmap2.height) / 2,
|
imageBitmap2.height + (maxHeight - imageBitmap2.height) / 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
|
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
|
||||||
progressCallback?.invoke(99)
|
progressCallback?.invoke(99)
|
||||||
|
|
||||||
|
@ -116,6 +116,13 @@
|
|||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:text="@string/invert_double_pages" />
|
android:text="@string/invert_double_pages" />
|
||||||
|
|
||||||
|
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||||
|
android:id="@+id/center_margin_type"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:entries="@array/center_margin_types"
|
||||||
|
app:title="@string/pref_center_margin" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/tapping_prefs_group"
|
android:id="@+id/tapping_prefs_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -21,4 +21,11 @@
|
|||||||
<item>@string/double_pages</item>
|
<item>@string/double_pages</item>
|
||||||
<item>@string/automatic_orientation</item>
|
<item>@string/automatic_orientation</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="center_margin_types">
|
||||||
|
<item>@string/center_margin_none</item>
|
||||||
|
<item>@string/center_margin_double_page</item>
|
||||||
|
<item>@string/center_margin_wide_page</item>
|
||||||
|
<item>@string/center_margin_double_and_wide_page</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
@ -314,6 +314,15 @@
|
|||||||
<string name="automatic_can_still_switch">While using automatic page layout, you can still switch between layouts while reading without overriding this setting</string>
|
<string name="automatic_can_still_switch">While using automatic page layout, you can still switch between layouts while reading without overriding this setting</string>
|
||||||
<string name="invert_double_pages">Invert double pages</string>
|
<string name="invert_double_pages">Invert double pages</string>
|
||||||
|
|
||||||
|
<!-- Center margin -->
|
||||||
|
<string name="center_margin">Center Margin</string>
|
||||||
|
<string name="center_margin_none">None</string>
|
||||||
|
<string name="center_margin_double_page">Add to double Page</string>
|
||||||
|
<string name="center_margin_wide_page">Add to wide Page</string>
|
||||||
|
<string name="center_margin_double_and_wide_page">Add to both</string>
|
||||||
|
<string name="pref_center_margin">Center margin type</string>
|
||||||
|
<string name="pref_center_margin_summary">Insert spacer to accommodate deadspace on foldable devices.</string>
|
||||||
|
|
||||||
<!-- Manga Page -->
|
<!-- Manga Page -->
|
||||||
<!-- Manga Info -->
|
<!-- Manga Info -->
|
||||||
<string name="az_recommends">See Recommendations</string>
|
<string name="az_recommends">See Recommendations</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user