从JavaScript到Kotlin:移植Hilbert曲线图片混淆到安卓的经历

我把小番茄图片混淆做成了安卓App

为什么不用网页,非要做个App
用过“小番茄图片混淆”的人应该知道,这东西在贴吧、论坛传图的时候挺常见的。它的逻辑很简单:一张正常图片,点一下加密,就变成雪花屏一样的东西,别人看不懂原图;但拿着同样的算法,又能把雪花屏还原成原图。
听起来很实用对吧?

网站地址
https://ranzt.com/xiaofanqiehunxiao
网页版。每次想解一张图,流程是这样的:保存图片 → 打开浏览器 → 搜索“小番茄图片混淆” → 找到那个网站 → 上传图片 → 点解混淆。网站能打开还好,打不开就直接卡住。

所以我干脆做了一个安卓App,把小番茄的混淆算法搬到了手机上。所有处理都在本地跑,不上传,不联网,不需要每次去搜那个网页。

混淆到底在干什么
先搞清楚原理,不然代码没法写。
小番茄的核心是空间填充曲线,具体用的是Hilbert曲线。这东西说白了就是一条能“填满”整个二维平面的线,它会经过平面上的每一个点,而且每个点只经过一次。

混淆的过程其实就两步:
用Hilbert曲线把图片里所有像素的位置串成一串
把这一串像素整体偏移一定的步数,然后按偏移后的顺序重新排列

解密就是反向操作,把偏移的步数减回去。
因为每个像素的位置都变了,所以看起来像乱码;但位置变化的规律是固定的,所以能完全还原。这里有个关键点:小番茄做的是像素位置置乱,不是改变像素颜色值,所以还原后的图片质量跟原图完全一样,不会有压缩损失。
市面上有些混淆工具是做像素值变换的,那种还原后画质会下降,小番茄不会。

大概花了几天从拆解算法到打包成APK,断断续续大概两天。大部分时间不是在写代码,而是在解决“安卓为啥不让我直接存图片”这类问题。
把JavaScript改成Kotlin
网页版小番茄是用JavaScript写的,像素操作基于Canvas的ImageData。安卓这边用的是Bitmap,操作方式不太一样。
最大的区别在于:JavaScript的ImageData可以直接操作一个一维数组,通过索引访问像素;安卓的Bitmap需要用getPixel(x, y)和setPixel(x, y, color),按坐标访问。

把算法从JavaScript翻译成Kotlin,主要做了三件事:
生成Hilbert曲线坐标:这部分逻辑完全照搬,递归算法跟语言无关,直接用Kotlin重写
像素重排:JavaScript用数组索引,安卓换成坐标映射
批量读写优化:安卓的getPixel()单次调用开销不小,大图会慢。改成用getPixels()一次性读到数组里,处理完再用setPixels()写回去

核心代码大概长这样:
kotlin
// 生成Hilbert曲线坐标序列
val curve = generateHilbertCurve(order)

// 加密:按曲线偏移重排像素
for (i in curve.indices) {
val (x, y) = curve[i]
val (newX, newY) = curve[(i + offset) % curve.size]
pixels[newX][newY] = originalPixels[x][y]
}
适配安卓的几个关键点
算法移植本身不复杂,但让它在安卓上稳定跑起来,有几个地方需要专门处理。

1. 大图内存管理
Hilbert曲线需要操作整张图片的所有像素。一张手机拍的照片(4000x3000),如果完整加载到内存,光一个Bitmap就要占48MB。处理过程中还会创建新的Bitmap,内存峰值可能超过100MB,低端手机直接崩。
解决办法是做采样加载:处理时把图片缩放到合适尺寸(最长边不超过2048像素),这样内存占用降到原来的1/4左右。保存的时候再保存原始尺寸——因为算法操作的是像素位置,缩放后再还原会丢失细节,所以保存必须用原图。

2. 保存到相册的适配
Android 10 之后的分区存储,不能直接用文件路径在相册里创建文件了。必须用 MediaStore API,系统才会把图片纳入相册索引。
实现方式很简单:创建 ContentValues,指定文件名、MIME类型和存储路径,然后通过 ContentResolver 插入并写入数据。保存后图片会自动出现在相册的 Pictures/NoiseVault 文件夹里。

3. 异步处理避免卡界面
图片处理是CPU密集型操作,在Android主线程里跑几百毫秒以上就会掉帧,超过5秒会触发ANR(应用无响应)。
用协程把处理逻辑放到 Dispatchers.IO 线程池,主线程只负责更新UI。这样用户点击“加密”按钮后,界面不会卡死,进度反馈正常。
kotlin
viewModelScope.launch(Dispatchers.IO) {
val processed = processBitmap(originalBitmap)
withContext(Dispatchers.Main) {
previewImageView.setImageBitmap(processed)
}
}
4. 权限最小化
App只申请两个权限:读取存储和写入存储。不需要网络权限,不需要定位权限,不需要相机权限。用户安装的时候可以看到权限列表。
成品长什么样

App界面就一个页面:上面显示图片预览,中间是模式切换(加密/解密)和次数调节(1-99次),下面是三个按钮(选图、处理、保存)。
深色模式跟随系统,没有广告,不需要联网权限。整个安装包不到5MB。

关于核心算法内容
需要说明一下:小番茄基于空间填充曲线的混淆方案,在学术和开源领域已经存在很久了,并不是我发明的。我做的只是把网页版的实现方式移植到安卓本地,让用户可以在手机上离线使用,不用依赖网页和网络。

这个App里不包含原项目的任何代码、图片或品牌元素,只保留了算法实现。如果你在贴吧或论坛里收到小番茄混淆图,可以直接用这个App解,不用再开网页。

下载地址
源码和APK都在GitHub上:
https://github.com/Bologna-Zaicek/Tools-NoiseVault
Releases里有APK文件,下载安装后直接用。安装包不5MB,权限只有读取和保存图片。