/
ZoomImage.kt
148 lines (141 loc) · 6.25 KB
/
ZoomImage.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* Copyright (C) 2023 panpf <panpfpanpf@outlook.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.panpf.zoomimage
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntSize
import com.github.panpf.zoomimage.compose.ZoomState
import com.github.panpf.zoomimage.compose.internal.MyImage
import com.github.panpf.zoomimage.compose.internal.round
import com.github.panpf.zoomimage.compose.rememberZoomState
import com.github.panpf.zoomimage.compose.subsampling.subsampling
import com.github.panpf.zoomimage.compose.zoom.ScrollBarSpec
import com.github.panpf.zoomimage.compose.zoom.zoom
import com.github.panpf.zoomimage.compose.zoom.zoomScrollBar
import com.github.panpf.zoomimage.compose.zoom.zooming
import kotlin.math.roundToInt
/**
* A native Image component that zoom and subsampling huge images
*
* Example usages:
*
* ```kotlin
* val state: ZoomState by rememberZoomState()
* val context = LocalContext.current
* LaunchedEffect(Unit) {
* val imageSource = ImageSource.fromResource(context, R.drawable.huge_image)
* state.subsampling.setImageSource(imageSource)
* }
* ZoomImage(
* painter = painterResource(R.drawable.huge_image_thumbnail),
* contentDescription = "view image",
* modifier = Modifier.fillMaxSize(),
* state = state,
* )
* ```
*
* @param contentDescription text used by accessibility services to describe what this image
* represents. This should always be provided unless this image is used for decorative purposes,
* and does not represent a meaningful action that a user can take. This text should be
* localized, such as by using androidx.compose.ui.res.stringResource or similar
* @param modifier Modifier used to adjust the layout algorithm or draw decoration content (ex.
* background)
* @param alignment Optional alignment parameter used to place the [Painter] in the given
* bounds defined by the width and height.
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
* if the bounds are a different size from the intrinsic size of the [Painter]
* @param alpha Optional opacity to be applied to the [Painter] when it is rendered onscreen
* the default renders the [Painter] completely opaque
* @param colorFilter Optional colorFilter to apply for the [Painter] when it is rendered onscreen
* @param state The state to control zoom
* @param scrollBar Controls whether scroll bars are displayed and their style
* @param onLongPress Called when the user long presses the image
* @param onTap Called when the user taps the image
*/
@Composable
fun ZoomImage(
painter: Painter,
contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
state: ZoomState = rememberZoomState(),
scrollBar: ScrollBarSpec? = ScrollBarSpec.Default,
onLongPress: ((Offset) -> Unit)? = null,
onTap: ((Offset) -> Unit)? = null,
) {
state.zoomable.contentScale = contentScale
state.zoomable.alignment = alignment
state.zoomable.contentSize = remember(painter.intrinsicSize) {
painter.intrinsicSize.round()
}
BoxWithConstraints(modifier = modifier) {
/*
* Here use BoxWithConstraints and then actively set containerSize,
* In order to prepare the transform in advance, so that when the position of the image needs to be adjusted,
* the position change will not be seen by the user
*/
val density = LocalDensity.current
val newContainerSize = remember(density, maxWidth, maxHeight) {
val width = with(density) { maxWidth.toPx() }.roundToInt()
val height = with(density) { maxHeight.toPx() }.roundToInt()
IntSize(width = width, height = height)
}
state.zoomable.containerSize = newContainerSize
MyImage(
painter = painter,
contentDescription = contentDescription,
alignment = Alignment.TopStart,
contentScale = ContentScale.None,
alpha = alpha,
colorFilter = colorFilter,
clipToBounds = false,
modifier = Modifier
.matchParentSize()
.zoom(state.zoomable, onLongPress = onLongPress, onTap = onTap),
)
// Why are subsampling tiles drawn on separate components?
// Because when drawing the bottom and right edge subsampling tiles on the desktop platform,
// a drawing failure will occur, resulting in the loss of all component content.
// Therefore, if the subsampling tile is drawn on a separate component, when a problem occurs, the user will only see that the problem area is unclear, rather than the entire component content being lost.
// issue: https://github.com/JetBrains/compose-multiplatform/issues/3904
Box(
Modifier
.matchParentSize()
.zooming(state.zoomable)
.subsampling(state.zoomable, state.subsampling)
)
if (scrollBar != null) {
Box(
Modifier
.matchParentSize()
.zoomScrollBar(state.zoomable, scrollBar)
)
}
}
}