| 
 | 1 | +package com.smarttoolfactory.composecolorpicker.demo  | 
 | 2 | + | 
 | 3 | +import android.graphics.Bitmap  | 
 | 4 | +import android.graphics.Canvas  | 
 | 5 | +import androidx.compose.foundation.*  | 
 | 6 | +import androidx.compose.foundation.gestures.detectDragGestures  | 
 | 7 | +import androidx.compose.foundation.layout.*  | 
 | 8 | +import androidx.compose.foundation.shape.RoundedCornerShape  | 
 | 9 | +import androidx.compose.material.Button  | 
 | 10 | +import androidx.compose.material.Text  | 
 | 11 | +import androidx.compose.runtime.*  | 
 | 12 | +import androidx.compose.ui.Modifier  | 
 | 13 | +import androidx.compose.ui.draw.clip  | 
 | 14 | +import androidx.compose.ui.draw.drawWithContent  | 
 | 15 | +import androidx.compose.ui.geometry.Offset  | 
 | 16 | +import androidx.compose.ui.geometry.Rect  | 
 | 17 | +import androidx.compose.ui.graphics.Color  | 
 | 18 | +import androidx.compose.ui.graphics.ImageBitmap  | 
 | 19 | +import androidx.compose.ui.graphics.drawscope.Stroke  | 
 | 20 | +import androidx.compose.ui.graphics.isUnspecified  | 
 | 21 | +import androidx.compose.ui.input.pointer.PointerInputChange  | 
 | 22 | +import androidx.compose.ui.input.pointer.pointerInput  | 
 | 23 | +import androidx.compose.ui.layout.*  | 
 | 24 | +import androidx.compose.ui.platform.LocalContext  | 
 | 25 | +import androidx.compose.ui.platform.LocalView  | 
 | 26 | +import androidx.compose.ui.res.imageResource  | 
 | 27 | +import androidx.compose.ui.unit.dp  | 
 | 28 | +import androidx.compose.ui.unit.sp  | 
 | 29 | +import com.smarttoolfactory.colorpicker.ui.Blue400  | 
 | 30 | +import com.smarttoolfactory.composecolorpicker.R  | 
 | 31 | + | 
 | 32 | +@Composable  | 
 | 33 | +fun ColorDetectionFromScreenDemo() {  | 
 | 34 | +    // These are for debugging  | 
 | 35 | +    var text by remember { mutableStateOf("") }  | 
 | 36 | +    var colorAtTouchPosition by remember { mutableStateOf(Color.Unspecified) }  | 
 | 37 | + | 
 | 38 | + | 
 | 39 | +    Column(  | 
 | 40 | +        modifier = Modifier  | 
 | 41 | +            .fillMaxSize()  | 
 | 42 | +            .verticalScroll(rememberScrollState())  | 
 | 43 | +    ) {  | 
 | 44 | + | 
 | 45 | +        Box(  | 
 | 46 | +            modifier = Modifier  | 
 | 47 | +                .fillMaxWidth()  | 
 | 48 | +                .height(100.dp)  | 
 | 49 | +        )  | 
 | 50 | +        Text(  | 
 | 51 | +            text = "Simple demonstration to detect colors in ImageColorDetection composable " +  | 
 | 52 | +                    "covered with green border. Bitmap of the composable is created when after " +  | 
 | 53 | +                    "globally positioned",  | 
 | 54 | +            fontSize = 14.sp,  | 
 | 55 | +            color = Blue400,  | 
 | 56 | +            modifier = Modifier.padding(2.dp)  | 
 | 57 | +        )  | 
 | 58 | + | 
 | 59 | +        ImageColorDetection(  | 
 | 60 | +            modifier=Modifier.padding(10.dp),  | 
 | 61 | +            content = {  | 
 | 62 | + | 
 | 63 | +                Column(  | 
 | 64 | +                    modifier = Modifier  | 
 | 65 | +                        .border(2.dp, Color.Green)  | 
 | 66 | +                        .padding(5.dp)  | 
 | 67 | +                ) {  | 
 | 68 | + | 
 | 69 | +                    Image(  | 
 | 70 | +                        bitmap = ImageBitmap.imageResource(  | 
 | 71 | +                            LocalContext.current.resources,  | 
 | 72 | +                            R.drawable.landscape  | 
 | 73 | +                        ),  | 
 | 74 | +                        contentDescription = null,  | 
 | 75 | +                        modifier = Modifier  | 
 | 76 | +                            .background(Color.LightGray)  | 
 | 77 | +                            .fillMaxWidth()  | 
 | 78 | +                            // This is for displaying different ratio, optional  | 
 | 79 | +                            .aspectRatio(4f / 3),  | 
 | 80 | +                        contentScale = ContentScale.Crop  | 
 | 81 | +                    )  | 
 | 82 | + | 
 | 83 | +                    Button(onClick = { /*TODO*/ }) {  | 
 | 84 | +                        Text("Sample Button")  | 
 | 85 | +                    }  | 
 | 86 | +                }  | 
 | 87 | +            },  | 
 | 88 | +            { colorChange: Color, textChange: String ->  | 
 | 89 | +                colorAtTouchPosition = colorChange  | 
 | 90 | +                text = textChange  | 
 | 91 | +            },  | 
 | 92 | + | 
 | 93 | +            )  | 
 | 94 | + | 
 | 95 | +        Text(text = text)  | 
 | 96 | +        Box(  | 
 | 97 | +            modifier = Modifier  | 
 | 98 | +                .then(  | 
 | 99 | +                    if (colorAtTouchPosition.isUnspecified) {  | 
 | 100 | +                        Modifier  | 
 | 101 | +                    } else {  | 
 | 102 | +                        Modifier  | 
 | 103 | +                            .clip(RoundedCornerShape(20))  | 
 | 104 | +                            .border(2.dp, Color.Black, shape = RoundedCornerShape(20))  | 
 | 105 | +                            .background(colorAtTouchPosition)  | 
 | 106 | +                    }  | 
 | 107 | +                )  | 
 | 108 | +                .size(100.dp)  | 
 | 109 | + | 
 | 110 | +        )  | 
 | 111 | +    }  | 
 | 112 | +}  | 
 | 113 | + | 
 | 114 | +@Composable  | 
 | 115 | +private fun ImageColorDetection(  | 
 | 116 | +    modifier: Modifier=Modifier,  | 
 | 117 | +    content: @Composable () -> Unit,  | 
 | 118 | +    onColorChange: (Color, String) -> Unit,  | 
 | 119 | +) {  | 
 | 120 | + | 
 | 121 | +    val view = LocalView.current  | 
 | 122 | + | 
 | 123 | +    var offsetX by remember { mutableStateOf(0f) }  | 
 | 124 | +    var offsetY by remember { mutableStateOf(0f) }  | 
 | 125 | + | 
 | 126 | +    var color by remember { mutableStateOf(Color.Unspecified) }  | 
 | 127 | + | 
 | 128 | +    var composableBounds by remember {  | 
 | 129 | +        mutableStateOf<Rect?>(null)  | 
 | 130 | +    }  | 
 | 131 | +    var bitmap by remember {  | 
 | 132 | +        mutableStateOf<Bitmap?>(  | 
 | 133 | +            null  | 
 | 134 | +        )  | 
 | 135 | +    }  | 
 | 136 | + | 
 | 137 | +    DisposableEffect(Unit) {  | 
 | 138 | +        onDispose {  | 
 | 139 | +            bitmap?.apply {  | 
 | 140 | +                if (!isRecycled) recycle()  | 
 | 141 | +                bitmap = null  | 
 | 142 | +            }  | 
 | 143 | +        }  | 
 | 144 | +    }  | 
 | 145 | + | 
 | 146 | +    val colorDetectionModifier = modifier  | 
 | 147 | +        .pointerInput(Unit) {  | 
 | 148 | +            detectDragGestures(onDrag = { change: PointerInputChange, _: Offset ->  | 
 | 149 | + | 
 | 150 | +                if (composableBounds == null) return@detectDragGestures  | 
 | 151 | + | 
 | 152 | +                if (bitmap == null) {  | 
 | 153 | +                    val boundsRect = composableBounds!!  | 
 | 154 | + | 
 | 155 | +                    bitmap = Bitmap.createBitmap(  | 
 | 156 | +                        boundsRect.width.toInt(),  | 
 | 157 | +                        boundsRect.height.toInt(),  | 
 | 158 | +                        Bitmap.Config.ARGB_8888  | 
 | 159 | +                    )  | 
 | 160 | + | 
 | 161 | +                    bitmap?.let { bmp ->  | 
 | 162 | +                        val canvas = Canvas(bmp)  | 
 | 163 | +                            .apply {  | 
 | 164 | +                                translate(-boundsRect.left, -boundsRect.top)  | 
 | 165 | +                            }  | 
 | 166 | +                        view.draw(canvas)  | 
 | 167 | +                    }  | 
 | 168 | +                }  | 
 | 169 | + | 
 | 170 | +                val bmp: Bitmap = bitmap!!  | 
 | 171 | +                val bitmapWidth = bmp.width  | 
 | 172 | +                val bitmapHeight = bmp.height  | 
 | 173 | + | 
 | 174 | +                // Touch coordinates on image  | 
 | 175 | +                offsetX = change.position.x.coerceIn(0f, bitmapWidth.toFloat())  | 
 | 176 | +                offsetY = change.position.y.coerceIn(0f, bitmapHeight.toFloat())  | 
 | 177 | + | 
 | 178 | +                // Scale from Image touch coordinates to range in Bitmap  | 
 | 179 | +                val scaledX = offsetX  | 
 | 180 | +                val scaledY = offsetY  | 
 | 181 | + | 
 | 182 | +                try {  | 
 | 183 | +                    val pixel: Int = bmp.getPixel(scaledX.toInt(), scaledY.toInt())  | 
 | 184 | + | 
 | 185 | +                    val red = android.graphics.Color.red(pixel)  | 
 | 186 | +                    val green = android.graphics.Color.green(pixel)  | 
 | 187 | +                    val blue = android.graphics.Color.blue(pixel)  | 
 | 188 | + | 
 | 189 | +                    val text = "Touch offsetX:$offsetX, offsetY: $offsetY\n" +  | 
 | 190 | +                            "Bitmap width: ${bitmapWidth}, height: $bitmapHeight\n" +  | 
 | 191 | +                            "scaledX: $scaledX, scaledY: $scaledY\n" +  | 
 | 192 | +                            "red: $red, green: $green, blue: $blue\n"  | 
 | 193 | + | 
 | 194 | +                    color = Color(red, green, blue)  | 
 | 195 | + | 
 | 196 | +                    onColorChange(color, text)  | 
 | 197 | +                } catch (e: Exception) {  | 
 | 198 | +                    println("Exception e: ${e.message}")  | 
 | 199 | +                }  | 
 | 200 | +            }  | 
 | 201 | +            )  | 
 | 202 | +        }  | 
 | 203 | +        .drawWithContent {  | 
 | 204 | +            drawContent()  | 
 | 205 | +            val center = Offset(offsetX, offsetY)  | 
 | 206 | +            drawCircle(Color.Black, radius = 20f, style = Stroke(16f), center = center)  | 
 | 207 | +            drawCircle(Color.White, radius = 20f, style = Stroke(14f), center = center)  | 
 | 208 | +        }  | 
 | 209 | +        .onGloballyPositioned {  | 
 | 210 | +            composableBounds = it.boundsInRoot()  | 
 | 211 | +        }  | 
 | 212 | + | 
 | 213 | + | 
 | 214 | +    Column(modifier = colorDetectionModifier) {  | 
 | 215 | +        content()  | 
 | 216 | +    }  | 
 | 217 | +}  | 
0 commit comments