11package com.smarttoolfactory.composeimage.demo
22
3+ import android.graphics.Bitmap
34import androidx.compose.foundation.*
45import androidx.compose.foundation.layout.*
56import androidx.compose.foundation.shape.RoundedCornerShape
67import androidx.compose.material3.*
78import androidx.compose.runtime.*
89import androidx.compose.ui.Modifier
9- import androidx.compose.ui.graphics.Color
10- import androidx.compose.ui.graphics.ImageBitmap
10+ import androidx.compose.ui.draw.clipToBounds
11+ import androidx.compose.ui.geometry.Offset
12+ import androidx.compose.ui.graphics.*
13+ import androidx.compose.ui.graphics.drawscope.Stroke
14+ import androidx.compose.ui.input.pointer.PointerInputChange
1115import androidx.compose.ui.layout.ContentScale
1216import androidx.compose.ui.platform.LocalContext
1317import androidx.compose.ui.res.imageResource
1418import androidx.compose.ui.text.font.FontWeight
15- import androidx.compose.ui.unit.dp
16- import androidx.compose.ui.unit.sp
19+ import androidx.compose.ui.unit.*
1720import com.smarttoolfactory.composeimage.ContentScaleSelectionMenu
1821import com.smarttoolfactory.composeimage.ImageSelectionButton
1922import com.smarttoolfactory.composeimage.R
23+ import com.smarttoolfactory.gesture.MotionEvent
24+ import com.smarttoolfactory.gesture.pointerMotionEvents
2025import com.smarttoolfactory.image.ImageWithConstraints
2126
2227/* *
@@ -44,7 +49,6 @@ fun ImageWithConstraintsDemo() {
4449 modifier = Modifier
4550 .padding(paddingValues)
4651 .fillMaxSize()
47- .verticalScroll(rememberScrollState())
4852 .padding(10 .dp)
4953 ) {
5054 ImageScale (imageBitmap = imageBitmap)
@@ -56,28 +60,52 @@ fun ImageWithConstraintsDemo() {
5660@Composable
5761fun ImageScale (imageBitmap : ImageBitmap ) {
5862
59- val modifier = Modifier
60- .background(Color .LightGray )
61- .border(2 .dp, Color .Red )
62- .fillMaxWidth()
63- .aspectRatio(4 / 3f )
63+ var contentScale by remember { mutableStateOf(ContentScale .Fit ) }
64+ ContentScaleSelectionMenu (contentScale = contentScale) {
65+ contentScale = it
66+ }
67+
68+ Column (
69+ modifier = Modifier
70+ .fillMaxSize()
71+ .verticalScroll(rememberScrollState())
72+ ) {
73+ val modifier = Modifier
74+ .background(Color .LightGray )
75+ .border(2 .dp, Color .Red )
76+ .fillMaxWidth()
77+ .aspectRatio(4 / 3f )
78+
79+
80+ ImageWithConstraintsSample (modifier, imageBitmap, contentScale)
81+ Spacer (modifier = Modifier .height(30 .dp))
82+ DrawSample (modifier, imageBitmap, contentScale)
83+ Spacer (modifier = Modifier .height(30 .dp))
84+ CropSample (modifier, imageBitmap, contentScale)
85+ Spacer (modifier = Modifier .height(30 .dp))
86+ ImageSample (modifier, imageBitmap, contentScale)
87+ }
88+
89+ }
90+
91+ @Composable
92+ private fun ImageWithConstraintsSample (
93+ modifier : Modifier ,
94+ imageBitmap : ImageBitmap ,
95+ contentScale : ContentScale
96+ ) {
6497
6598 var text by remember { mutableStateOf(" " ) }
6699
67100 Spacer (modifier = Modifier .height(20 .dp))
68101 Text (
69102 text = " ImageWithConstraints ContentScale" ,
70- fontSize = 20 .sp,
103+ fontSize = 16 .sp,
71104 fontWeight = FontWeight .Bold ,
72105 color = MaterialTheme .colorScheme.primary,
73106 modifier = Modifier .padding(8 .dp)
74107 )
75108
76- var contentScale by remember { mutableStateOf(ContentScale .Fit ) }
77- ContentScaleSelectionMenu (contentScale = contentScale) {
78- contentScale = it
79- }
80-
81109 ImageWithConstraints (
82110 modifier = modifier,
83111 imageBitmap = imageBitmap,
@@ -118,10 +146,157 @@ fun ImageScale(imageBitmap: ImageBitmap) {
118146 modifier = Modifier .padding(8 .dp)
119147 )
120148 }
149+ }
150+
151+ @Composable
152+ private fun CropSample (
153+ modifier : Modifier ,
154+ imageBitmap : ImageBitmap ,
155+ contentScale : ContentScale
156+ ) {
121157
158+ Text (
159+ text = " Crop Using ImageScope" ,
160+ fontSize = 16 .sp,
161+ fontWeight = FontWeight .Bold ,
162+ color = MaterialTheme .colorScheme.primary,
163+ modifier = Modifier .padding(8 .dp)
164+ )
165+
166+ var showDialog by remember { mutableStateOf(false ) }
167+
168+ var bitmapRect by remember(imageBitmap, contentScale) {
169+ mutableStateOf(
170+ IntRect (
171+ offset = IntOffset .Zero ,
172+ size = IntSize (imageBitmap.width, imageBitmap.height)
173+ )
174+ )
175+ }
176+
177+ val croppedImage = remember(imageBitmap, bitmapRect) {
178+ Bitmap .createBitmap(
179+ imageBitmap.asAndroidBitmap(),
180+ bitmapRect.left,
181+ bitmapRect.top,
182+ bitmapRect.width,
183+ bitmapRect.height
184+ ).asImageBitmap()
185+ }
186+
187+ ImageWithConstraints (
188+ modifier = modifier,
189+ imageBitmap = imageBitmap,
190+ contentDescription = null ,
191+ contentScale = contentScale
192+ ) {
193+ bitmapRect = this .rect
194+ }
195+
196+
197+ Button (
198+ modifier = Modifier
199+ .padding(8 .dp)
200+ .fillMaxSize(),
201+ onClick = { showDialog = true }) {
202+ Text (" Crop" )
203+ }
204+
205+ if (showDialog) {
206+ AlertDialog (onDismissRequest = { showDialog = ! showDialog },
207+
208+ text = {
209+ Column (
210+ modifier = Modifier
211+ .fillMaxWidth()
212+ ) {
213+
214+ Text (
215+ text = " Original Image" ,
216+ fontSize = 16 .sp,
217+ fontWeight = FontWeight .Bold ,
218+ color = MaterialTheme .colorScheme.primary,
219+ modifier = Modifier .padding(8 .dp)
220+ )
221+ Image (
222+ modifier = Modifier
223+ .fillMaxWidth()
224+ .aspectRatio(4 / 3f ),
225+ bitmap = imageBitmap,
226+ contentScale = ContentScale .FillBounds ,
227+ contentDescription = " original image"
228+ )
229+
230+ Text (
231+ text = " Cropped Image" ,
232+ fontSize = 16 .sp,
233+ fontWeight = FontWeight .Bold ,
234+ color = MaterialTheme .colorScheme.primary,
235+ modifier = Modifier .padding(8 .dp)
236+ )
237+ Image (
238+ modifier = Modifier
239+ .fillMaxWidth()
240+ .aspectRatio(4 / 3f ),
241+ bitmap = croppedImage,
242+ contentScale = ContentScale .FillBounds ,
243+ contentDescription = " cropped image"
244+ )
245+ }
246+ },
247+ confirmButton = {
248+ Button (onClick = { showDialog = ! showDialog }) {
249+ Text (" Confirm" )
250+ }
251+ },
252+ dismissButton = {
253+ Button (onClick = { showDialog = ! showDialog }) {
254+ Text (" Dismiss" )
255+ }
256+ }
257+ )
258+ }
259+ }
260+
261+ @Composable
262+ private fun DrawSample (
263+ modifier : Modifier ,
264+ imageBitmap : ImageBitmap ,
265+ contentScale : ContentScale
266+ ) {
267+
268+ Text (
269+ text = " Draw Using ImageScope" ,
270+ fontSize = 16 .sp,
271+ fontWeight = FontWeight .Bold ,
272+ color = MaterialTheme .colorScheme.primary,
273+ modifier = Modifier .padding(8 .dp)
274+ )
275+
276+ ImageWithConstraints (
277+ modifier = modifier,
278+ imageBitmap = imageBitmap,
279+ contentDescription = null ,
280+ contentScale = contentScale
281+ ) {
282+
283+ val imageWidth = this .imageWidth
284+ val imageHeight = this .imageHeight
285+
286+ Drawing (modifier = Modifier .size(imageWidth, imageHeight))
287+ }
288+ }
289+
290+ // This is a Sample to show ImageWithConstraints match with Compose Image content scale changes
291+ @Composable
292+ private fun ImageSample (
293+ modifier : Modifier ,
294+ imageBitmap : ImageBitmap ,
295+ contentScale : ContentScale
296+ ) {
122297 Text (
123298 text = " Image Content Scale" ,
124- fontSize = 20 .sp,
299+ fontSize = 16 .sp,
125300 fontWeight = FontWeight .Bold ,
126301 color = MaterialTheme .colorScheme.primary,
127302 modifier = Modifier .padding(8 .dp)
@@ -133,5 +308,87 @@ fun ImageScale(imageBitmap: ImageBitmap) {
133308 contentDescription = null ,
134309 contentScale = contentScale
135310 )
311+ }
312+
313+ @Composable
314+ private fun Drawing (modifier : Modifier ) {
315+
316+ var motionEvent by remember { mutableStateOf(MotionEvent .Idle ) }
317+ // This is our motion event we get from touch motion
318+ var currentPosition by remember { mutableStateOf(Offset .Unspecified ) }
319+ // This is previous motion event before next touch is saved into this current position
320+ var previousPosition by remember { mutableStateOf(Offset .Unspecified ) }
321+
322+ val brush = remember {
323+ Brush .verticalGradient(
324+ colors = listOf (
325+ Color .Red ,
326+ Color .Green ,
327+ Color .Yellow ,
328+ Color .Blue ,
329+ Color .Cyan ,
330+ Color .Magenta ,
331+ Color .Red ,
332+ )
333+ )
334+ }
335+
336+ // Path is what is used for drawing line on Canvas
337+ val path = remember(modifier) { Path () }
338+
339+ val drawModifier = modifier
340+ .clipToBounds()
341+ .pointerMotionEvents(
342+ onDown = { pointerInputChange: PointerInputChange ->
343+ currentPosition = pointerInputChange.position
344+ motionEvent = MotionEvent .Down
345+ pointerInputChange.consume()
346+ },
347+ onMove = { pointerInputChange: PointerInputChange ->
348+ currentPosition = pointerInputChange.position
349+ motionEvent = MotionEvent .Move
350+ pointerInputChange.consume()
351+ },
352+ onUp = { pointerInputChange: PointerInputChange ->
353+ motionEvent = MotionEvent .Up
354+ pointerInputChange.consume()
355+ },
356+ delayAfterDownInMillis = 25L
357+ )
136358
359+ Canvas (modifier = drawModifier) {
360+ when (motionEvent) {
361+ MotionEvent .Down -> {
362+ path.moveTo(currentPosition.x, currentPosition.y)
363+ previousPosition = currentPosition
364+ }
365+
366+ MotionEvent .Move -> {
367+ path.quadraticBezierTo(
368+ previousPosition.x,
369+ previousPosition.y,
370+ (previousPosition.x + currentPosition.x) / 2 ,
371+ (previousPosition.y + currentPosition.y) / 2
372+
373+ )
374+
375+ previousPosition = currentPosition
376+ }
377+
378+ MotionEvent .Up -> {
379+ path.lineTo(currentPosition.x, currentPosition.y)
380+ currentPosition = Offset .Unspecified
381+ previousPosition = currentPosition
382+ motionEvent = MotionEvent .Idle
383+ }
384+
385+ else -> Unit
386+ }
387+
388+ drawPath(
389+ brush = brush,
390+ path = path,
391+ style = Stroke (width = 4 .dp.toPx(), cap = StrokeCap .Round , join = StrokeJoin .Round )
392+ )
393+ }
137394}
0 commit comments