Initial commit
This commit is contained in:
commit
6072b2ba29
844 changed files with 220532 additions and 0 deletions
211
app/src/main/java/com/aryan/reader/pdf/MagnifierComposable.kt
Normal file
211
app/src/main/java/com/aryan/reader/pdf/MagnifierComposable.kt
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// MagnifierComposable.kt
|
||||
package com.aryan.reader.pdf
|
||||
|
||||
import android.graphics.Rect
|
||||
import timber.log.Timber
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun MagnifierComposable(
|
||||
sourceBitmap: ImageBitmap,
|
||||
tiles: List<PdfTile>,
|
||||
currentScale: Float,
|
||||
magnifierCenterOnBitmap: Offset,
|
||||
modifier: Modifier = Modifier,
|
||||
magnifierWidth: Dp = 120.dp,
|
||||
magnifierHeight: Dp = 60.dp,
|
||||
zoomFactor: Float = 1.5f,
|
||||
selectionRectsInBitmapCoords: List<Rect>,
|
||||
highlightColor: Color,
|
||||
colorFilter: ColorFilter? = null
|
||||
) {
|
||||
val stadiumShape = RoundedCornerShape(magnifierHeight / 2)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.width(magnifierWidth)
|
||||
.height(magnifierHeight)
|
||||
.shadow(4.dp, stadiumShape)
|
||||
.clip(stadiumShape)
|
||||
) {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
val magnifierWidthPx = size.width
|
||||
val magnifierHeightPx = size.height
|
||||
|
||||
Timber.d("Magnifier: START. scale=$currentScale, centerOnBitmap=$magnifierCenterOnBitmap")
|
||||
|
||||
val relevantTile = if (currentScale > 1f) {
|
||||
tiles.find {
|
||||
it.renderRect.contains(magnifierCenterOnBitmap.x.toInt(), magnifierCenterOnBitmap.y.toInt())
|
||||
}
|
||||
} else null
|
||||
|
||||
if (relevantTile != null) {
|
||||
// --- HIGH-RES TILE PATH ---
|
||||
Timber.d("Magnifier: Using HIGH-RES TILE path.")
|
||||
Timber.d("Magnifier: Tile.renderRect=${relevantTile.renderRect}, Tile.bitmap.size=${relevantTile.bitmap.width}x${relevantTile.bitmap.height}")
|
||||
val bitmapToUse = relevantTile.bitmap.asImageBitmap()
|
||||
|
||||
val tileBitmapWidth = relevantTile.bitmap.width.toFloat()
|
||||
val tileRenderRectWidth = relevantTile.renderRect.width().toFloat()
|
||||
|
||||
val tileScale = if (tileRenderRectWidth > 0) {
|
||||
tileBitmapWidth / tileRenderRectWidth
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
Timber.d("Magnifier: Using derived tileScale=$tileScale instead of parent's currentScale=$currentScale")
|
||||
|
||||
|
||||
val centerInTileBitmap = Offset(
|
||||
x = (magnifierCenterOnBitmap.x - relevantTile.renderRect.left) * tileScale,
|
||||
y = (magnifierCenterOnBitmap.y - relevantTile.renderRect.top) * tileScale
|
||||
)
|
||||
|
||||
Timber.d("Magnifier: Calculated centerInTileBitmap=$centerInTileBitmap")
|
||||
|
||||
val sourceRectWidth = magnifierWidthPx / zoomFactor
|
||||
val sourceRectHeight = magnifierHeightPx / zoomFactor
|
||||
Timber.d("Magnifier: Desired sourceRect size=${sourceRectWidth}x$sourceRectHeight")
|
||||
|
||||
val srcLeft = (centerInTileBitmap.x - sourceRectWidth / 2f)
|
||||
val srcTop = (centerInTileBitmap.y - sourceRectHeight / 2f)
|
||||
Timber.d("Magnifier: Calculated source top-left=($srcLeft, $srcTop)")
|
||||
|
||||
val clampedSrcLeft = srcLeft.coerceIn(0f, bitmapToUse.width.toFloat() - sourceRectWidth.coerceAtLeast(1f))
|
||||
val clampedSrcTop = srcTop.coerceIn(0f, bitmapToUse.height.toFloat() - sourceRectHeight.coerceAtLeast(1f))
|
||||
Timber.d("Magnifier: Clamped source top-left=($clampedSrcLeft, $clampedSrcTop)")
|
||||
|
||||
val finalSrcLeftInt = clampedSrcLeft.roundToInt()
|
||||
val finalSrcTopInt = clampedSrcTop.roundToInt()
|
||||
|
||||
val finalSrcWidthInt = (bitmapToUse.width - finalSrcLeftInt)
|
||||
.coerceAtMost(sourceRectWidth.roundToInt()).coerceAtLeast(1)
|
||||
val finalSrcHeightInt = (bitmapToUse.height - finalSrcTopInt)
|
||||
.coerceAtMost(sourceRectHeight.roundToInt()).coerceAtLeast(1)
|
||||
Timber.d("Magnifier: Final source rect to draw from tile: offset=($finalSrcLeftInt, $finalSrcTopInt), size=${finalSrcWidthInt}x$finalSrcHeightInt")
|
||||
|
||||
if (finalSrcWidthInt <= 0 || finalSrcHeightInt <= 0 || finalSrcLeftInt >= bitmapToUse.width || finalSrcTopInt >= bitmapToUse.height) {
|
||||
Timber.w("Magnifier: Final source rect is invalid, returning.")
|
||||
return@Canvas
|
||||
}
|
||||
|
||||
drawImage(
|
||||
image = bitmapToUse,
|
||||
srcOffset = IntOffset(finalSrcLeftInt, finalSrcTopInt),
|
||||
srcSize = IntSize(finalSrcWidthInt, finalSrcHeightInt),
|
||||
dstSize = IntSize(magnifierWidthPx.roundToInt(), magnifierHeightPx.roundToInt()),
|
||||
colorFilter = colorFilter
|
||||
)
|
||||
|
||||
selectionRectsInBitmapCoords.forEach { rectInBitmap ->
|
||||
val translatedLeft = (rectInBitmap.left - relevantTile.renderRect.left) * tileScale
|
||||
val translatedTop = (rectInBitmap.top - relevantTile.renderRect.top) * tileScale
|
||||
val translatedRight = (rectInBitmap.right - relevantTile.renderRect.left) * tileScale
|
||||
val translatedBottom = (rectInBitmap.bottom - relevantTile.renderRect.top) * tileScale
|
||||
|
||||
val finalLeft = translatedLeft - clampedSrcLeft
|
||||
val finalTop = translatedTop - clampedSrcTop
|
||||
val finalRight = translatedRight - clampedSrcLeft
|
||||
val finalBottom = translatedBottom - clampedSrcTop
|
||||
|
||||
val magnifiedLeft = finalLeft * zoomFactor
|
||||
val magnifiedTop = finalTop * zoomFactor
|
||||
val magnifiedRight = finalRight * zoomFactor
|
||||
val magnifiedBottom = finalBottom * zoomFactor
|
||||
|
||||
if (magnifiedRight > 0 && magnifiedLeft < magnifierWidthPx && magnifiedBottom > 0 && magnifiedTop < magnifierHeightPx) {
|
||||
drawRect(
|
||||
color = highlightColor,
|
||||
topLeft = Offset(magnifiedLeft, magnifiedTop),
|
||||
size = androidx.compose.ui.geometry.Size(
|
||||
width = magnifiedRight - magnifiedLeft,
|
||||
height = magnifiedBottom - magnifiedTop
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// --- LOW-RES / NO-ZOOM PATH ---
|
||||
Timber.d("Magnifier: Using LOW-RES (base bitmap) path.")
|
||||
val sourceRectWidth = magnifierWidthPx / zoomFactor
|
||||
val sourceRectHeight = magnifierHeightPx / zoomFactor
|
||||
Timber.d("Magnifier: Desired sourceRect size=${sourceRectWidth}x$sourceRectHeight")
|
||||
|
||||
val srcLeft = (magnifierCenterOnBitmap.x - sourceRectWidth / 2f)
|
||||
val srcTop = (magnifierCenterOnBitmap.y - sourceRectHeight / 2f)
|
||||
Timber.d("Magnifier: Calculated source top-left=($srcLeft, $srcTop)")
|
||||
|
||||
val clampedSrcLeft = srcLeft.coerceIn(0f, sourceBitmap.width.toFloat() - sourceRectWidth.coerceAtLeast(1f))
|
||||
val clampedSrcTop = srcTop.coerceIn(0f, sourceBitmap.height.toFloat() - sourceRectHeight.coerceAtLeast(1f))
|
||||
Timber.d("Magnifier: Clamped source top-left=($clampedSrcLeft, $clampedSrcTop)")
|
||||
|
||||
val finalSrcLeftInt = clampedSrcLeft.roundToInt()
|
||||
val finalSrcTopInt = clampedSrcTop.roundToInt()
|
||||
|
||||
val finalSrcWidthInt = (sourceBitmap.width - finalSrcLeftInt)
|
||||
.coerceAtMost(sourceRectWidth.roundToInt()).coerceAtLeast(1)
|
||||
val finalSrcHeightInt = (sourceBitmap.height - finalSrcTopInt)
|
||||
.coerceAtMost(sourceRectHeight.roundToInt()).coerceAtLeast(1)
|
||||
Timber.d("Magnifier: Final source rect to draw from base: offset=($finalSrcLeftInt, $finalSrcTopInt), size=${finalSrcWidthInt}x$finalSrcHeightInt")
|
||||
|
||||
if (finalSrcWidthInt <= 0 || finalSrcHeightInt <= 0 || finalSrcLeftInt >= sourceBitmap.width || finalSrcTopInt >= sourceBitmap.height) {
|
||||
Timber.w("Magnifier: Final source rect is invalid, returning.")
|
||||
return@Canvas
|
||||
}
|
||||
|
||||
drawImage(
|
||||
image = sourceBitmap,
|
||||
srcOffset = IntOffset(finalSrcLeftInt, finalSrcTopInt),
|
||||
srcSize = IntSize(finalSrcWidthInt, finalSrcHeightInt),
|
||||
dstSize = IntSize(magnifierWidthPx.roundToInt(), magnifierHeightPx.roundToInt()),
|
||||
colorFilter = colorFilter
|
||||
)
|
||||
|
||||
selectionRectsInBitmapCoords.forEach { rectInBitmap ->
|
||||
val translatedLeft = rectInBitmap.left - clampedSrcLeft
|
||||
val translatedTop = rectInBitmap.top - clampedSrcTop
|
||||
val rectWidthInBitmap = rectInBitmap.width().toFloat()
|
||||
val rectHeightInBitmap = rectInBitmap.height().toFloat()
|
||||
|
||||
val magnifiedLeft = translatedLeft * zoomFactor
|
||||
val magnifiedTop = translatedTop * zoomFactor
|
||||
val magnifiedWidth = rectWidthInBitmap * zoomFactor
|
||||
val magnifiedHeight = rectHeightInBitmap * zoomFactor
|
||||
|
||||
if (magnifiedLeft + magnifiedWidth > 0 && magnifiedLeft < magnifierWidthPx &&
|
||||
magnifiedTop + magnifiedHeight > 0 && magnifiedTop < magnifierHeightPx) {
|
||||
drawRect(
|
||||
color = highlightColor,
|
||||
topLeft = Offset(magnifiedLeft, magnifiedTop),
|
||||
size = androidx.compose.ui.geometry.Size(
|
||||
width = magnifiedWidth,
|
||||
height = magnifiedHeight
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue