1+ package com.smarttoolfactory.tutorial1_1basics.chapter5_gesture
2+
3+ import androidx.compose.foundation.Image
4+ import androidx.compose.foundation.background
5+ import androidx.compose.foundation.border
6+ import androidx.compose.foundation.gestures.detectTapGestures
7+ import androidx.compose.foundation.layout.Box
8+ import androidx.compose.foundation.layout.Column
9+ import androidx.compose.foundation.layout.Spacer
10+ import androidx.compose.foundation.layout.aspectRatio
11+ import androidx.compose.foundation.layout.fillMaxSize
12+ import androidx.compose.foundation.layout.fillMaxWidth
13+ import androidx.compose.foundation.layout.height
14+ import androidx.compose.foundation.layout.padding
15+ import androidx.compose.foundation.rememberScrollState
16+ import androidx.compose.foundation.verticalScroll
17+ import androidx.compose.material.Text
18+ import androidx.compose.runtime.Composable
19+ import androidx.compose.runtime.getValue
20+ import androidx.compose.runtime.mutableStateListOf
21+ import androidx.compose.runtime.mutableStateOf
22+ import androidx.compose.runtime.remember
23+ import androidx.compose.runtime.setValue
24+ import androidx.compose.ui.Alignment
25+ import androidx.compose.ui.Modifier
26+ import androidx.compose.ui.draw.drawWithContent
27+ import androidx.compose.ui.geometry.Offset
28+ import androidx.compose.ui.geometry.Size
29+ import androidx.compose.ui.graphics.Color
30+ import androidx.compose.ui.graphics.drawscope.Stroke
31+ import androidx.compose.ui.graphics.painter.Painter
32+ import androidx.compose.ui.input.pointer.pointerInput
33+ import androidx.compose.ui.layout.ContentScale
34+ import androidx.compose.ui.layout.onSizeChanged
35+ import androidx.compose.ui.res.painterResource
36+ import androidx.compose.ui.tooling.preview.Preview
37+ import androidx.compose.ui.unit.dp
38+ import androidx.compose.ui.unit.toSize
39+ import com.smarttoolfactory.tutorial1_1basics.R
40+
41+ @Preview
42+ @Composable
43+ fun ImageBoundsDrawSample () {
44+
45+ Column (
46+ modifier = Modifier
47+ .fillMaxSize()
48+ .verticalScroll(rememberScrollState())
49+ .padding(8 .dp)
50+ ) {
51+
52+ val pointsOnBitmap = remember {
53+ mutableStateListOf<Offset >()
54+ }
55+
56+ val painter = painterResource(R .drawable.landscape10)
57+
58+ Text (" ContentScale: ContentScale.Fit, alignment: TopCenter" )
59+ ImageWithBounds (
60+ modifier = Modifier
61+ .border(2 .dp, Color .Red )
62+ .background(Color .LightGray )
63+ .fillMaxWidth()
64+ .aspectRatio(4 / 3f ),
65+ contentScale = ContentScale .Fit ,
66+ pointsOnBitmap = pointsOnBitmap,
67+ painter = painter
68+ ) { pointOnBitmap: Offset ->
69+ pointsOnBitmap.add(pointOnBitmap)
70+ }
71+
72+ Spacer (modifier = Modifier .height(16 .dp))
73+ Text (" ContentScale: ContentScale.FillBounds, alignment: TopCenter" )
74+ ImageWithBounds (
75+ modifier = Modifier
76+ .border(2 .dp, Color .Red )
77+ .background(Color .LightGray )
78+ .fillMaxWidth()
79+ .aspectRatio(3 / 2f ),
80+ contentScale = ContentScale .FillBounds ,
81+ pointsOnBitmap = pointsOnBitmap,
82+ painter = painter
83+ ) { pointOnBitmap: Offset ->
84+ pointsOnBitmap.add(pointOnBitmap)
85+ }
86+
87+ Spacer (modifier = Modifier .height(16 .dp))
88+ Text (" ContentScale: ContentScale.Fit, alignment: BottomEnd" )
89+ ImageWithBounds (
90+ modifier = Modifier
91+ .border(2 .dp, Color .Red )
92+ .background(Color .LightGray )
93+ .fillMaxWidth()
94+ .aspectRatio(5 / 3f ),
95+ contentScale = ContentScale .Fit ,
96+ alignment = Alignment .BottomEnd ,
97+ pointsOnBitmap = pointsOnBitmap,
98+ painter = painter
99+ ) { pointOnBitmap: Offset ->
100+ pointsOnBitmap.add(pointOnBitmap)
101+ }
102+
103+ Spacer (modifier = Modifier .height(16 .dp))
104+ Text (" ContentScale: ContentScale.Crop, alignment: TopCenter" )
105+ ImageWithBounds (
106+ modifier = Modifier
107+ .border(2 .dp, Color .Red )
108+ .background(Color .LightGray )
109+ .fillMaxWidth()
110+ .aspectRatio(3 / 4f ),
111+ contentScale = ContentScale .Crop ,
112+ pointsOnBitmap = pointsOnBitmap,
113+ painter = painter
114+ ) { pointOnBitmap: Offset ->
115+ pointsOnBitmap.add(pointOnBitmap)
116+ }
117+ Spacer (modifier = Modifier .height(16 .dp))
118+ Text (" ContentScale: ContentScale.Crop, alignment: TopStart" )
119+ ImageWithBounds (
120+ modifier = Modifier
121+ .border(2 .dp, Color .Red )
122+ .background(Color .LightGray )
123+ .fillMaxWidth()
124+ .aspectRatio(3 / 4f ),
125+ contentScale = ContentScale .Crop ,
126+ alignment = Alignment .TopStart ,
127+ pointsOnBitmap = pointsOnBitmap,
128+ painter = painter
129+ ) { pointOnBitmap: Offset ->
130+ pointsOnBitmap.add(pointOnBitmap)
131+ }
132+ }
133+ }
134+
135+
136+ @Composable
137+ private fun ImageWithBounds (
138+ modifier : Modifier = Modifier ,
139+ contentScale : ContentScale ,
140+ alignment : Alignment = Alignment .Center ,
141+ painter : Painter ,
142+ pointsOnBitmap : List <Offset >,
143+ onClick : (positionOnBitmap: Offset ) -> Unit ,
144+ ) {
145+
146+ var imageProperties by remember {
147+ mutableStateOf(ImageProperties .Zero )
148+ }
149+
150+
151+ Box (
152+ modifier = Modifier .padding(vertical = 16 .dp)
153+ ) {
154+
155+ Image (
156+ modifier = modifier
157+ .drawWithContent {
158+ drawContent()
159+
160+ val drawAreaRect = imageProperties.drawAreaRect
161+ val bitmapRect = imageProperties.bitmapRect
162+ val scaleFactor = imageProperties.scaleFactor
163+
164+ // This is for displaying area that bitmap is drawn in Image Composable
165+ drawRect(
166+ color = Color .Green ,
167+ topLeft = drawAreaRect.topLeft,
168+ size = drawAreaRect.size,
169+ style = Stroke (
170+ 4 .dp.toPx(),
171+ )
172+ )
173+
174+ val size = pointsOnBitmap.size
175+ pointsOnBitmap.forEachIndexed { index: Int , offset: Offset ->
176+
177+ val scaledCurrentOffset = scaleFromBitmapToScreenPosition(
178+ offsetBitmap = offset,
179+ drawAreaRect = drawAreaRect,
180+ bitmapRect = bitmapRect,
181+ scaleFactor = scaleFactor
182+ )
183+ if (size > 1 && index > 0 ) {
184+
185+ val scaledPreviousOffset = scaleFromBitmapToScreenPosition(
186+ offsetBitmap = pointsOnBitmap[index - 1 ],
187+ drawAreaRect = drawAreaRect,
188+ bitmapRect = bitmapRect,
189+ scaleFactor = scaleFactor
190+ )
191+
192+ drawLine(
193+ color = Color .Blue ,
194+ start = scaledPreviousOffset,
195+ end = scaledCurrentOffset,
196+ strokeWidth = 3 .dp.toPx()
197+ )
198+ }
199+
200+ drawCircle(
201+ color = Color .Red ,
202+ center = scaledCurrentOffset,
203+ radius = 6 .dp.toPx()
204+ )
205+ }
206+ }
207+ .pointerInput(contentScale) {
208+ detectTapGestures { offset: Offset ->
209+
210+ val bitmapRect = imageProperties.bitmapRect
211+ val drawAreaRect = imageProperties.drawAreaRect
212+ val isTouchInImage = drawAreaRect.contains(offset)
213+
214+ if (isTouchInImage) {
215+
216+ val offsetOnBitmap = scaleFromScreenToBitmapPosition(
217+ offsetScreen = offset,
218+ bitmapRect = bitmapRect,
219+ drawAreaRect = drawAreaRect,
220+ scaleFactor = imageProperties.scaleFactor
221+ )
222+
223+ onClick(offsetOnBitmap)
224+ }
225+ }
226+ }
227+ .onSizeChanged {
228+ val imageSize = it
229+ val dstSize: Size = imageSize.toSize()
230+
231+ val srcSize = painter.intrinsicSize
232+
233+ imageProperties = calculateImageDrawProperties(
234+ srcSize = srcSize,
235+ dstSize = dstSize,
236+ contentScale = contentScale,
237+ alignment = alignment
238+ )
239+ },
240+ painter = painter,
241+ contentScale = contentScale,
242+ alignment = alignment,
243+ contentDescription = null
244+ )
245+
246+ }
247+ }
0 commit comments