移动开发7 min3 次浏览

从 View 到 Miuix Compose:来咔!的界面重构与离线色彩管线

接上一篇《来咔!把一整间暗房装进一台离线手机》,这篇拆「来咔!」的工程骨头:一条从导入到出片的离线色彩管线,以及把它从 View/XML 搬到 Miuix Compose 的过程。所有结论都来自对源码的实读,不臆测。 项目来源与署名 :本文所涉「来咔!(leicaMoments)」是基于开源项目 L…

从 View 到 Miuix Compose:来咔!的界面重构与离线色彩管线
目录(展开)

接上一篇《来咔!把一整间暗房装进一台离线手机》,这篇拆「来咔!」的工程骨头:一条从导入到出片的离线色彩管线,以及把它从 View/XML 搬到 Miuix Compose 的过程。所有结论都来自对源码的实读,不臆测。

项目来源与署名:本文所涉「来咔!(leicaMoments)」是基于开源项目 L-Moment 的二次开发版本。原项目由 GitHub 用户 昔白(@a307608689-afk) 创建,采用 MIT 许可证;本站的「来咔!」在其基础上完成了 Miuix 风格化改造以及色彩管线、构建工具链等方面的优化。原作者保留全部权利,本衍生版本不开源,亦不提供源码或构建产物。

数据流总览

flowchart TD
    A[相册 / 分享 Intent] --> B[读取原始字节]
    B --> C{是否 RAW?}
    C -- 是 --> D[RawDecoder JNI / LibRaw]
    C -- 否 --> E[ImageDecoder / BitmapFactory]
    D --> F[工作 JPEG + 预览 Bitmap]
    E --> F
    F --> G[LeicaEngine 内置风格]
    F --> H[CustomStyleStore / LUT / ORT]
    G --> I[调节链]
    H --> I
    I --> J{导出档位}
    J --> K[工作版 JPEG]
    J --> L[全像素条带 / 24MP 整图]
    K --> M[EXIF / 徽章 / HDR 装配]
    L --> M
    M --> N[MediaStore 保存]

导入与工作图归一

入口 onImagePicked 的处理顺序值得单独说,因为它定下了后面所有环节的边界:

  1. 读原字节;Android 10+ 走 MediaStore.setRequireOriginal 保留 GPS。
  2. 按扩展名识别 RAW、按 MIME 与 JPEG 魔数识别 JPEG;动态照片截断到主 JPEG,丢掉 EOI 之后的视频数据。
  3. 重编码前提取 Ultra HDR 增益图。
  4. RAW 走 LibRaw JNI,普通图走 ImageDecoder / BitmapFactory。
  5. 工作图压到引擎可接受的横置口径:长边 ≤ 4096、短边 ≤ 3072。
  6. 归一 EXIF 方向,落一份工作 JPEG、预览 Bitmap、全像素源与 HDR 信息。

这里最重要的工程约束是:预览图和导出图不是一个对象。预览只服务屏幕,导出必须用工作字节或全像素源。工作图还有硬上限——超过上限的原始 JPG 必须先缩放,否则某些厂商风格链会直接原生崩溃。

内置风格与降级链

LeicaEngine.process 是内置风格的统一入口,但不同风格不是同一套算法:

风格 处理路径
M3 完整链 + 黑白后处理,失败退旧链 + CPU 黑白兜底
M9 厂商完整链 → QNN CPU 补丁 → 蒸馏微网 → ORT 重链 → 纯 LUT
M9+ / M10R 在 M9/M10R 链基础上做颜色后推、暗角与颗粒
ARRI / F / M4 / LP9 主要是 3D LUT 查表,再叠颗粒 / 暗角或局部算子
CCD 家族 SepLUT 与档位参数,共用 ONNX session
M9D 蒸馏微网优先,再退 ORT 或 LUT
LM9 统计式链优先,再试学习式微网,最后回退 M9 正常链

所以 UI 不能把风格当成"一个枚举 + 一张缩略图"。它至少要能表达:正常完成、降级完成、缺资产输出原图、等待重链、失败但保留上一张结果、以及"当前风格与超分 / 完美成片互斥"。

自定义色彩与模型色彩

CustomStyleStore 存两类色彩:QUICK(少量参考图生成的"我的色彩")和 MODEL(参考图更多、带版本与候选簇的"模型色彩")。关键在 CustomStyleDef 与 StyleVersion,一个版本要记录 LUT 文件、seed 池前缀长度、theta 快照、参数文件、模型仿射参数 aff(12)、曲线节点 knots(27)、来源与强度。

模型色彩有两条渲染路线:

  • 旧 LUT / 参数路线:LUT 缺失时用 theta 快照重烘,渲染主要调 ModelColor 与 AlexaLut。
  • M7 两段网络 + aff/knots 路线:m7_stage1.onnx 从参考图提取统计指纹,m7_stage2.onnx 聚合出风格仿射与曲线节点,运行时在 native/Kotlin 里套 aff + knots;多风格簇时先出 640px 候选预览供挑选。

因此"版本选择"和"候选选择"是两个必须保留的状态,而不是只留一个 selectedStyleId。

调节链

八个离散档位,每档 -10..+10:亮度、曝光、高光、阴影、黑色阶、饱和度、对比度、柔光 / 清晰化。处理不是"每个滑杆都重跑整条引擎":先拿滤镜结果,再用 Shadows.applyStackToJpeg 把多档参数一次烘焙;RAW 提亮档且存在 16-bit 线性基准时,先在 16-bit 域重放再送颜色引擎。交互上必须把拖动中的草稿值和提交后触发渲染的值分开,否则每动一下手指就是一次解码 / 编码。

导出链

export() 按源图、风格与内存预算选路:能直通时保留原始像素;LUT / 模型走条带处理(LutBandOps / ModelNetBandOps / DistillBandOps);M9/M9D 高分辨率改走约 24MP 整图链,避开条带边界归一化伪影;失败回退工作版,绝不产出"尺寸正确但没套色"的成片。最后统一处理 EXIF、徕卡兼容元数据、HDR 增益图并写回 MediaStore。导出 / 超分放在前台服务里,UI 只观察状态——Compose 页面退出不能顺带取消已经开始保存的长任务。

分享码:LMC1

ColorShareCode 当前格式 LMC1、版本 2:

  • MODE_RECIPE:带参考图指纹和版本链,接收端可重烘焙。
  • MODE_LUT:带 17³ 量化 LUT,兼容性更强。
  • 轻量文本码用差分 + gzip + Base64Url 压缩;文件备份 .lmc 则携带完整元数据与版本链。

这是兼容协议:字段顺序、魔数、CRC、版本语义都不能在重构里动,搬家之前先补往返回归测试。

为什么搬到 Miuix Compose

承接自原项目 L-Moment 的早期实现:旧版是 AppCompatActivity + setContentView + 一堆 LinearLayout/SeekBar 的多 Activity 结构,继续在 Activity 里直接管几十个 View 字段;「来咔!」在保留这层已验证的业务真源(JNI/ONNX 调用、调节与导出逻辑)的同时,把可见界面整体迁到 Miuix Compose,迁移后不会同时存在 View 与 Compose 两套真源。迁移思路是先拆状态、再做视觉,核心组件映射:

原功能 Miuix 目标
顶栏 + 导入 / 导出 SmallTopAppBar + IconButton
滤镜卡片 LazyRow + Card
调节面板 OverlayBottomSheet + SliderPreference
设置页 SmallTitle + Card + Preference 家族
模型库 / 详情 LazyVerticalGrid + Card / 分组 Card
画质进度 LinearProgressIndicator + 前台通知

主题用语义 token(MiuixTheme.colorScheme.*),不再散落硬编码色值;模糊效果 miuix-blur 默认不加,Android 12 及更早走非模糊回退。

工具链这道坎

Miuix 0.9.4 的 AAR 元数据要求 AGP ≥ 9.1.0 且 compileSdk 37,原项目的 AGP 8.2.2 只能用于纯 View/XML 版,无法与当前 Miuix 依赖共存。所以工具链整体前移:AGP 9.4.1、Kotlin / Compose 2.4.20、compileSdk 37,同时把 targetSdk 保持与原业务语义一致的 34。NDK、CMake、ABI 与 ONNX Runtime 全程不动。

风险与规避

风险 规避
工具链断裂 先做独立兼容分支,锁定版本再合
UI 重写破坏算法 保留 RenderGateway 接口与 golden tests
长任务被回收 导出 / 超分留 App/Service 层,UI 只观察
Bitmap 泄漏 大对象进 ImageSession,版本号与释放集中管理
滑动卡顿 草稿值 / 提交值分离
分享协议不兼容 先测试后移动,保持格式版本

落地与后续

当前采用增量接入:保留 MainActivity 里已验证的业务状态与 JNI/ONNX 调用,用 Miuix Compose 重写可见界面,通过 AndroidView 桥接复用原有预览与调节面板,滑动提交、曝光重算与 Bitmap 生命周期不变;Debug 包加 .miuixdev 后缀,与正式包并存互不覆盖。

后续不应为了"纯 Compose"去搬大对象,而是在已有行为基线之上,逐步引入不可序列化的 ImageSession、显式的 RenderGateway 与更细的 ViewModel。慢一点,但每一步都可回滚、可验证。

声明

本项目(「来咔!/ leicaMoments」)是基于 L-Moment(作者:昔白 @a307608689-afk,MIT 许可证)的二次开发衍生版本,在其基础上进行了 Miuix 风格化改造及色彩管线、构建工具链等优化;原项目的著作权归原作者所有。本衍生版本为个人学习与实验项目,与 Leica、小米及其关联公司无任何隶属、授权或背书关系;依 MIT 许可证要求,衍生使用须保留原作者署名与许可声明。暂不开源,也不分发二进制构建产物,仅供个人学习研究。

相 关 文 章

3 篇
移动开发

来咔!把一整间暗房装进一台离线手机

「来咔!」的出发点很简单:想要一个 彻底不联网 的色彩风格处理 App。照片从导入到出片全程留在手机里,不上传、不同步、不鉴权。它甚至没有申请 权限——这不是宣传语,翻开 就能逐条核实。 项目来源与署名 :本文所涉「来咔!(leicaMoments)」是基于开源项目 L Moment 的二次开发版本…

4 min

移动开发

来咔!把一整间暗房装进一台离线手机

「来咔!」的出发点很简单:想要一个 彻底不联网 的色彩风格处理 App。照片从导入到出片全程留在手机里,不上传、不同步、不鉴权。它甚至没有申请 权限——这不是宣传语,翻开 就能逐条核实。 项目来源与署名 :本文所涉「来咔!(leicaMoments)」是基于开源项目 L Moment 的二次开发版本…

4 min

移动开发

来咔!把一整间暗房装进一台离线手机

「来咔!」的出发点很简单:想要一个 彻底不联网 的色彩风格处理 App。照片从导入到出片全程留在手机里,不上传、不同步、不鉴权。它甚至没有申请 权限——这不是宣传语,翻开 就能逐条核实。 项目来源与署名 :本文所涉「来咔!(leicaMoments)」是基于开源项目 L Moment 的二次开发版本…

4 min

已运行 —北京时间 --:--:--

© 2026 风起|永远相信美好的事情即将发生!