saket / telephoto

Building blocks for designing media experiences in Compose UI
https://saket.github.io/telephoto/
Apache License 2.0
996 stars 32 forks source link

Need SubsamplingScaleImageView.visibleFileRect(Rect fRect) analog #89

Open NatalySd opened 6 months ago

NatalySd commented 6 months ago

I need to get the visible area Rect of the source bitmap to save it into another one (android wallpapers app). It seems I need something like davemorissey SubsamplingScaleImageView.visibleFileRect(Rect fRect) in ZoomableState. I tried to look at contentTransformation & transformedContentBounds, but did not succeded in getting the exact rect which is shown. It would be nice if you add such method.

brinsche commented 6 months ago

This sounds similar to https://github.com/saket/telephoto/issues/64#issuecomment-2086222965 maybe the solution works for you as well?

NatalySd commented 6 months ago

Thanks, but this is different. Composable-to-bitmap api writes to a bitmap a piece of the screen and I need a piece of the original bitmap.

saket commented 6 months ago

Hmm, I would have expected transformedContentBounds to give you the bounds you're looking for.

https://github.com/saket/telephoto/blob/7015d6eed1c1ff876844682728912bcfb94773f9/zoomable/src/commonMain/kotlin/me/saket/telephoto/zoomable/RealZoomableState.kt#L179-L184

Are its numbers significantly off?

NatalySd commented 6 months ago

I made some experiments. I have bitmap 1920 x 1080 (aspect ratio = 1,777777777777778) and screensize = 2000 x 1200 (aspect ratio = 1,666666666666667). I show it full size with ContentScale.Crop and want to get the visible area from the bitmap.

If I pan to top left corner, then visible bitmap crop area will be left=0, top=0, right=1800, bottom=1080 According to logs I have contentSize=Size(1920.0, 1080.0) offset=Offset(0.0, 0.0) scaleX=1.1111112 scaleY=1.1111112 transformedContentBounds=Rect.fromLTRB(0.0, 0.0, 2133.3, 1200.0)

If I pan to bottom right corner , then visible bitmap crop area will be left=120, top=0, right=1920, bottom=1080 According to logs I have contentSize=Size(1920.0, 1080.0) offset=Offset(-133.3, 0.0) scaleX=1.1111112 scaleY=1.1111112 transformedContentBounds=Rect.fromLTRB(-133.3, 0.0, 2000.0, 1200.0)

So I finnaly succedded to get this area correctly with the following code

getCroppedRectFromBitmap(
    sourceBitmap,
    contentWidth = zoomableState.contentTransformation.contentSize.width,
    contentHeight = zoomableState.contentTransformation.contentSize.height,
    scaledRectWidth = imageContentSizeInDp.width.value * density.density,
    scaledRectHeight = imageContentSizeInDp.height.value * density.density,
    scaledOffsetX = zoomableState.contentTransformation.offset.x,
    scaledOffsetY = zoomableState.contentTransformation.offset.y,
    scaleX = zoomableState.contentTransformation.scale.scaleX,
    scaleY = zoomableState.contentTransformation.scale.scaleY,
)

fun getCroppedRectFromBitmap(
    sourceBitmap:Bitmap,
    scaledRectWidth: Float,
    scaledRectHeight: Float,
    scaledOffsetX: Float,
    scaledOffsetY: Float,
    scaleX: Float,
    scaleY: Float,
): Bitmap? {
    val scale = max(scaleX, scaleY)
    val left = abs(scaledOffsetX / scale).toInt()
    val top = abs(scaledOffsetY / scale).toInt()
    var width = (scaledRectWidth / scale).toInt()
    width = min(width, sourceBitmap.width - left)
    var height = (scaledRectHeight / scale).toInt()
    height = min(height, sourceBitmap.height - top)
    return Bitmap.createBitmap(sourceBitmap, left, top, width, height)
}