android——Bitmap.Config ARGB_8888

android——Bitmap.Config ARGB_8888

Bitmap.Config有以下几个常量:

1
2
3
4
public static final Bitmap.Config  ALPHA_8
public static final Bitmap.Config  ARGB_4444 
public static final Bitmap.Config  ARGB_8888
public static final Bitmap.Config  RGB_565

这些是色彩的存储方法:我们知道 ARGB指的是一种 色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue.

其实所有的可见色都是右红绿蓝组成的,所以红绿蓝又称为三原色,每个原色都存储着所表示颜色的信息值

ALPHA_8就是Alpha由8位组成

ARGB_4444就是由4个4位组成即16位

ARGB_8888就是由4个8位组成即32位

RGB_565就是R为5位,G为6位,B为5位共16位

由此可见

  • ALPHA_8——代表8位Alpha位图
  • ARGB_4444——代表16位ARGB位图
  • ARGB_8888——代表32位ARGB位图
  • RGB_565——代表8位RGB位图

位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真

data class JAFRSHO31020UiStateDate( override var screenId: String="JAFRSHO31020", val currentColor: String="", val currentWidth: Int=0, val lines: ArrayDeque<HandDrawnLineModel> = ArrayDeque(), val currentPoints: List<Pair<Float, Float>> = emptyList(), val bitmap: ImageBitmap? = null, ): JafUiStateData data class JAFRSHO31020Model( /** 画像ファイル */ val filefilePath: String, ) : JafNavModel by DefaultJafNavModel() @HiltViewModel class JAFRSHO31020ViewModel @Inject constructor( private val localStorage: LocalStorageRepository ) : JafViewModel<JAFRSHO31020UiStateDate>() { /** 手書きラインメモリ*/ // private val handDrawnLineMemory = ArrayDeque<HandDrawnLineModel>() private val _state = mutableStateOf(JAFRSHO31020UiStateDate()) val state: MutableState<JAFRSHO31020UiStateDate> = _state private val _lines = mutableStateOf<ArrayDeque<HandDrawnLineModel>>(ArrayDeque()) val handDrawnLineMemory: State<ArrayDeque<HandDrawnLineModel>> = _lines /** 線の色*/ var lineColor: String = "" /** 線の太さ*/ var lineSize: String = "" /** 画像ファイル*/ var file: File = File("") /** * システムイベント処理 * @param event */ override fun onEvent(event: JafEvent) { when (event) { is JafEvent.Companion.InitializeWithParameters<*> -> { val initialParamTmp = event.params as JAFRSHO31020Model launchWithLoading { onPopupDisplay(initialParamTmp) } } } } /** * アクション処理 * * @param action アクション */ override fun onAction(action: JafAction) { when (action) { is JAFRSHO31020Action.OnConfirm -> { launchSilent { /*onPopupDisplay()*/ } } } } /** * 【JAFRSHO31020E001】画面表示 * @param filePathParam 画像ファイルパス */ fun onPopupDisplay(filePathParam: JAFRSHO31020Model) { //インスタンス変数.画像ファイルを取得する。 file = File(filePathParam.filefilePath) viewModelScope.launch { val bitmap = withContext(Dispatchers.IO) { if (!file.exists()) { file = File("[実行フォルダー]/Damage/original.jpg") } BitmapFactory.decodeFile(file.absolutePath)?.asImageBitmap() } _state.value = _state.value.copy(bitmap = bitmap) } viewModelScope.launch { // コルーチンを起動する //インスタンス変数.線の色を設定する。 val lineColorTmp: String? = localStorage.getMCarWorkSettingInfoList().find { it.parameterName == "線の色" } ?.let { it.parameterValue } if (lineColorTmp.isNullOrEmpty()) { lineColor = "#FF0000"//赤色 } else { lineColor = lineColorTmp } //インスタンス変数.線のサイズを設定する。 val lineSizeTmp: String? = localStorage.getMCarWorkSettingInfoList() .find { it.parameterName == "線のサイズ" } ?.let { it.parameterValue } if (lineSizeTmp.isNullOrEmpty()) { lineSize = "1"//1pt } else { lineSize = lineSizeTmp } } } /** * 【JAFRSHO31020E003】×(閉じる) - 押下 */ fun onWindowClose(function: () -> Unit) { //未確定手書き描画データの存在を判断する。 if (_lines.value.isNotEmpty()) { showMessage( Message.JAFRSHO99010IC00086.id, listOf("未確定の編集画像があります。破棄して戻りますか?"), onNo = { navigateBack() } ) } } /** * 【JAFRSHO31020E004】1つ前を削除 - 押下 */ fun onDelete() { if (_lines.value.isEmpty()) { } else { _lines.value.removeLast() } //手書き描画データを保持したメモリ情報の直前のデータを解放する。 } /** * 删除全部线条 */ fun clearAllLines() { _lines.value.clear() } // 添加新线条 fun addLine(line: HandDrawnLineModel) { _lines.value.addLast(line) } fun setBitmap(bitmap: ImageBitmap) { _state.value = _state.value.copy(bitmap = bitmap) } fun startDrawing(x: Float, y: Float) { _state.value = _state.value.copy(currentPoints = listOf(Pair(x, y))) } fun updateDrawing(x: Float, y: Float) { val newPoints = _state.value.currentPoints + Pair(x, y) _state.value = _state.value.copy(currentPoints = newPoints) } fun endDrawing() { val currentState = _state.value if (currentState.currentPoints.size > 1) { val newLine = HandDrawnLineModel( color = currentState.currentColor, width = currentState.currentWidth, point = currentState.currentPoints ) val newLines = ArrayDeque(currentState.lines) newLines.addLast(newLine) _state.value = currentState.copy( lines = newLines, currentPoints = emptyList() ) } } @Composable fun JAFRSHO31020Popup( modifier: Modifier = Modifier, model: JAFRSHO31020Model, viewModel: JAFRSHO31020ViewModel = hiltViewModel(), stateData: JAFRSHO31020UiStateDate=JAFRSHO31020UiStateDate(), ) { JafUiStateHost( modifier = modifier, viewModel = viewModel, params = model ) { LaunchedEffect(model.filefilePath) { // 从 File 加载图片 val bitmap = if (File(model.filefilePath).exists()) { BitmapFactory.decodeFile(File(model.filefilePath).absolutePath)?.asImageBitmap() } else { createDefaultBitmap() } bitmap?.let { viewModel.setBitmap(it) } } Column { // 操作按钮栏 - 只有三个按钮 Row( modifier = Modifier .fillMaxWidth() .padding(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { // 撤销按钮 JafBottomButton( text = "撤销", style = MaterialTheme.typography.buttonText.copy(fontSize = 32.sp), onClick = { /*viewModel.onAction(JAFRSHO28060Action.OnRsSho)*/ }, modifier = Modifier.size(220.dp, 78.dp) ) Button( onClick = {}, enabled = stateData.lines.isNotEmpty() ) { Text("撤销", modifier = Modifier.padding(start = 4.dp)) } // 清空按钮 Button( onClick = {}, enabled = stateData.lines.isNotEmpty() || stateData.currentPoints.isNotEmpty(), colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { Icon(Icons.Default.Clear, contentDescription = "清空") Text( "清空", modifier = Modifier.padding(start = 4.dp) //modifier = Modifier.padding(start = 4.dp) ) } // 保存按钮 Button(onClick = {}) { Text("保存", modifier = Modifier.padding(start = 4.dp)) } } // 图片编辑区域 Canvas( modifier = Modifier .fillMaxSize() .background(Color.LightGray) .pointerInput(Unit) { detectDragGestures( onDragStart = { offset -> viewModel.startDrawing(offset.x, offset.y) }, onDrag = { change, _ -> viewModel.updateDrawing(change.position.x, change.position.y) }, onDragEnd = { viewModel.endDrawing() } ) } ) { // 绘制原始图片 stateData.bitmap?.let { bitmap -> drawImage( image = bitmap, dstSize = IntSize(size.width.toInt(), size.height.toInt()) ) } // 绘制已保存的线条 stateData.lines.forEach { line -> val path = Path().also { path -> if (line.point.isNotEmpty()) { path.moveTo(line.point.first().first, line.point.first().second) line.point.forEach { point -> path.lineTo(point.first, point.second) } } } drawPath( path = path, color = Color(viewModel.lineColor.toInt()), style = Stroke(width = line.width.toFloat()) ) } // 绘制当前正在绘制的线条 if (stateData.currentPoints.isNotEmpty()) { val currentPath = Path().also { path -> path.moveTo( stateData.currentPoints.first().first, stateData.currentPoints.first().second ) stateData.currentPoints.forEach { point -> path.lineTo(point.first, point.second) } } drawPath( path = currentPath, color = Color(stateData.currentColor.toInt()), style = Stroke(width = stateData.currentWidth.toFloat()) ) } } } } } private fun createDefaultBitmap(): ImageBitmap? { return try { val bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(bitmap) // 填充浅灰色背景 canvas.drawColor(android.graphics.Color.parseColor("#F0F0F0")) val paint = android.graphics.Paint().apply { color = android.graphics.Color.DKGRAY textSize = 36f textAlign = android.graphics.Paint.Align.CENTER isAntiAlias = true } // 绘制提示文字 canvas.drawText("No Image", 200f, 200f, paint) bitmap.asImageBitmap() } catch (e: Exception) { e.printStackTrace() null } 分析一下这些代码,能否实现编辑图片的撤销,清除全部,以及保存,这些代码能否成功运行
最新发布
10-16
你提供的代码是一个基于 **Jetpack Compose + Kotlin + Hilt + ViewModel** 的 Android 图片编辑功能模块,主要实现了: - 显示一张图片 - 允许用户在图片上手绘线条(类似涂鸦) - 提供撤销、清空、保存等操作按钮的 UI 框架 但目前这些代码**不能完全成功运行**,存在多个关键问题。下面我们来逐项分析其是否能实现“撤销”、“清除全部”和“保存”功能,并指出缺陷与改进建议。 --- ### ✅ 一、功能分析 #### 1. 【清除全部】——✅基本可用,但有逻辑漏洞 ```kotlin fun clearAllLines() { _lines.value.clear() } ``` 这是清除所有已绘制线条的方法。 在 `JAFRSHO31020Popup` 中使用如下: ```kotlin Button( onClick = {}, enabled = stateData.lines.isNotEmpty() || stateData.currentPoints.isNotEmpty(), ) { Icon(Icons.Default.Clear, contentDescription = "清空") Text("清空", modifier = Modifier.padding(start = 4.dp)) } ``` 🔴 **问题:** - `onClick = {}` 是空函数!没有调用 `viewModel.clearAllLines()`。 - 虽然 ViewModel 实现了 `clearAllLines()` 方法,但在 Composable 中未绑定。 🟢 **修复建议:** ```kotlin Button( onClick = { viewModel.clearAllLines() }, enabled = stateData.lines.isNotEmpty() || stateData.currentPoints.isNotEmpty(), colors = ButtonDefaults.buttonColors(containerColor = Color.Red) ) { Icon(Icons.Default.Clear, contentDescription = "清空") Text("清空", modifier = Modifier.padding(start = 4.dp)) } ``` > ✅ 修复后可以正常清除所有线条。 --- #### 2. 【撤销】——🟡部分实现,但严重缺失逻辑 ViewModel 中提供了删除最后一条线的功能: ```kotlin fun onDelete() { if (_lines.value.isEmpty()) { } else { _lines.value.removeLast() } } ``` 但在 Composable 中: ```kotlin Button( onClick = {}, enabled = stateData.lines.isNotEmpty() ) { Text("撤销", modifier = Modifier.padding(start = 4.dp)) } ``` 🔴 **问题:** - `onClick = {}` 空实现,没有调用 `onDelete()`。 - 没有从 `stateData.lines` 更新状态 → 因为 `_lines` 和 `stateData.lines` 并不同步! ⚠️ 更严重的问题是: - `JAFRSHO31020UiStateDate` 自己持有一个 `lines: ArrayDeque<HandDrawnLineModel>` 字段, - 同时 ViewModel 又维护了一个独立的 `_lines: MutableState<ArrayDeque<...>>` - 这导致数据不一致! 🟢 正确做法应是:**只通过 UiState 统一管理 UI 数据状态** 🔴 当前结构错误地将 `lines` 分散在两个地方,造成同步混乱。 🟢 **修复建议:** 移除 `_lines`,把所有线条数据统一放入 `JAFRSHO31020UiStateDate`,并通过 `_state` 更新: ```kotlin // 修改 startDrawing / updateDrawing / endDrawing / clearAllLines / onDelete // 都改为修改 _state.value.lines ``` 例如: ```kotlin fun clearAllLines() { _state.value = _state.value.copy(lines = ArrayDeque()) } fun onDelete() { val newLines = ArrayDeque(_state.value.lines) if (newLines.isNotEmpty()) { newLines.removeLast() _state.value = _state.value.copy(lines = newLines) } } ``` 并在 `endDrawing()` 中也更新的是 `_state.value.lines`,而不是另起炉灶。 然后 Composable 使用 `state.value.lines` 来绘制。 --- #### 3. 【保存】——🔴 完全未实现 ```kotlin Button(onClick = {}) { Text("保存", modifier = Modifier.padding(start = 4.dp)) } ``` - `onClick = {}` 是空的。 - 没有任何“保存图片”的逻辑。 - 即使画好了线条,也无法导出为新的图片文件。 🟢 **要实现保存功能,需完成以下几步:** 1. 将当前 Canvas 上的内容(原始图 + 所有线条)渲染到一个离屏位图(Off-screen Bitmap) 2. 编码为 JPEG/PNG 并写入文件系统 3. 请求存储权限(Android 10+ 注意分区存储) 🟢 示例保存方法(添加到 ViewModel): ```kotlin fun saveImage(context: Context, onSuccess: (String) -> Unit) { viewModelScope.launch(Dispatchers.IO) { val currentState = _state.value val bitmap = currentState.bitmap ?: return@launch // 创建可变副本用于绘制 val mutableBitmap = bitmap.asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true) val canvas = android.graphics.Canvas(mutableBitmap) val paint = android.graphics.Paint().apply { isAntiAlias = true style = android.graphics.Paint.Style.STROKE } // 绘制已有线条 for (line in currentState.lines) { val path = android.graphics.Path() if (line.point.isNotEmpty()) { val first = line.point.first() path.moveTo(first.first, first.second) line.point.forEach { pt -> path.lineTo(pt.first, pt.second) } paint.color = Color(line.color).toArgb() paint.strokeWidth = line.width.toFloat() canvas.drawPath(path, paint) } } // 保存到文件 val dir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) val file = File(dir, "edited_${System.currentTimeMillis()}.jpg") try { val stream = FileOutputStream(file) mutableBitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) stream.close() withContext(Dispatchers.Main) { onSuccess(file.absolutePath) } } catch (e: Exception) { e.printStackTrace() // 处理错误(可通过消息提示用户) } } } ``` 然后在 Composable 中调用: ```kotlin Button(onClick = { (context as? Activity)?.let { activity -> if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { viewModel.saveImage(activity) { path -> Toast.makeText(activity, "保存成功: $path", Toast.LENGTH_LONG).show() // 可选择关闭弹窗或通知刷新 } } else { // 请求权限 } } }) { Text("保存") } ``` --- ### ❌ 二、核心架构问题总结 | 问题 | 描述 | |------|------| | 🔴 数据源分裂 | `_state` 和 `_lines` 各自维护 `lines`,极易导致状态不同步 | | 🔴 操作未绑定 | “撤销”、“清空”、“保存”按钮的 `onClick` 均为空 | | 🔴 无法保存图像 | 缺少实际编码和写入文件逻辑 | | 🔴 颜色解析风险 | `Color(viewModel.lineColor.toInt())` 可能崩溃,因为 `#FF0000` 不是整数字符串 | | 🔴 State 流向错误 | `stateData` 参数传入 Composable 但未观察 ViewModel 的真实状态 | --- ### ✅ 改进后的关键点 #### ✅ 1. 合并状态:只保留 `_state` 管理所有 UI 数据 ```kotlin data class JAFRSHO31020UiStateDate( override var screenId: String = "JAFRSHO31020", val currentColor: String = "#FF0000", val currentWidth: Int = 5, val lines: ArrayDeque<HandDrawnLineModel> = ArrayDeque(), val currentPoints: List<Pair<Float, Float>> = emptyList(), val bitmap: ImageBitmap? = null, ) : JafUiStateData ``` 不再需要单独的 `_lines`。 #### ✅ 2. 修改 ViewModel 的操作方法 ```kotlin fun onDelete() { val newLines = ArrayDeque(_state.value.lines) if (newLines.isNotEmpty()) { newLines.removeLast() _state.value = _state.value.copy(lines = newLines) } } fun clearAllLines() { _state.value = _state.value.copy(lines = ArrayDeque()) } ``` #### ✅ 3. 正确处理颜色转换(避免崩溃) ```kotlin Color(android.graphics.Color.parseColor("#FF0000")) ``` 替代: ```kotlin Color(viewModel.lineColor.toInt()) // ❌ 错误方式 ``` ✅ 应改为: ```kotlin android.graphics.Color.parseColor(line.color) // 获取 int color Color(colorInt) // 转换为 Jetpack Compose Color ``` #### ✅ 4. 在 Composable 中正确监听状态 不要传参 `stateData`,而是直接观察 `viewModel.state` ```kotlin val state by viewModel.state.collectAsState() ``` 并用它来驱动 UI。 --- ### ✅ 最终结论 > ❌ 当前代码**不能成功运行**,虽然框架完整,但存在严重的状态管理错误和功能空实现。 > ✅ 经过以下修改后,可实现完整功能: - ✅ 修复状态统一(仅用 `_state`) - ✅ 绑定按钮事件(撤销、清空、保存) - ✅ 实现图像保存逻辑 - ✅ 正确解析颜色 - ✅ 动态响应状态变化 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值