Publish the app code that runs these models (app/)
Browse files- README.md +5 -0
- app/README-HF.md +70 -0
- app/assets/bench/unet0_latent.bin +3 -0
- app/assets/bench/unet0_noise.bin +3 -0
- app/assets/context.bin +3 -0
- app/assets/models.json +414 -0
- app/build.gradle.kts +92 -0
- app/src/main/AndroidManifest.xml +66 -0
- app/src/main/kotlin/com/yboom/moebius/BitmapIo.kt +102 -0
- app/src/main/kotlin/com/yboom/moebius/Engine.kt +382 -0
- app/src/main/kotlin/com/yboom/moebius/Imaging.kt +298 -0
- app/src/main/kotlin/com/yboom/moebius/Inpainter.kt +221 -0
- app/src/main/kotlin/com/yboom/moebius/ModelStore.kt +258 -0
- app/src/main/kotlin/com/yboom/moebius/Pipeline.kt +182 -0
- app/src/test/kotlin/com/yboom/moebius/ImagingTest.kt +113 -0
- app/src/test/kotlin/com/yboom/moebius/PipelineTest.kt +67 -0
- app/src/test/kotlin/com/yboom/moebius/Vectors.kt +65 -0
- app/src/test/resources/vectors.txt +107 -0
- app/versions.env +43 -0
README.md
CHANGED
|
@@ -21,3 +21,8 @@ for the native Android app in the `onnx-android` project. Files are downloaded b
|
|
| 21 |
| `cpu` | `unet.einsum.onnx`, `vae_encoder.static.onnx`, `vae_decoder.static.onnx` | Any device, CPU EP, fp32. Static shapes (UNet batch 2), Einsum rewritten to MatMul, condition embedding replaced by a `context` input (2×10×3072, shipped in the APK) |
|
| 22 |
|
| 23 |
Changes from the original graphs: static shapes, `input_ids` → `context` input, Einsum → Transpose/Reshape/MatMul, normalization pre-scaling (HTP fp16 range), graph splitting. Weights are unchanged except for the fp16 conversion of `unet.fp16x.onnx`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
| `cpu` | `unet.einsum.onnx`, `vae_encoder.static.onnx`, `vae_decoder.static.onnx` | Any device, CPU EP, fp32. Static shapes (UNet batch 2), Einsum rewritten to MatMul, condition embedding replaced by a `context` input (2×10×3072, shipped in the APK) |
|
| 22 |
|
| 23 |
Changes from the original graphs: static shapes, `input_ids` → `context` input, Einsum → Transpose/Reshape/MatMul, normalization pre-scaling (HTP fp16 range), graph splitting. Weights are unchanged except for the fp16 conversion of `unet.fp16x.onnx`.
|
| 24 |
+
|
| 25 |
+
## `app/` — Android code that runs these models
|
| 26 |
+
|
| 27 |
+
[`app/`](https://huggingface.co/luweigen/moebius-onnx-android/tree/main/app) holds the parts of the Android app that load and run the models above: the inpainting pipeline (noise, DDIM, classifier-free guidance, VAE scaling), image preparation and paste-back, ONNX Runtime / QNN session setup (HTP context binaries, the hybrid NPU+CPU chain, the GPU context cache, the parallel CFG branches), the download/sha256 manifest, and the small inputs the graphs need that are not model files (`context.bin`, benchmark tensors).
|
| 28 |
+
It is reference code, not a complete buildable project (the UI, resources and build tooling are left out). See [app/README-HF.md](https://huggingface.co/luweigen/moebius-onnx-android/blob/main/app/README-HF.md) for how the pieces fit together and the file list.
|
app/README-HF.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Android code for the Moebius ONNX models
|
| 2 |
+
|
| 3 |
+
This folder holds the code from the `onnx-android` project's Android app (`com.yboom.moebius`, Kotlin, no AndroidX) that loads and runs the models in this repository.
|
| 4 |
+
It is published as **reference code**, not as a buildable project: the UI (activities, mask view, foreground service), string resources and the host-side model preparation scripts are left out.
|
| 5 |
+
The code still refers to things that are not included here: user-facing error messages use `R.string.*`, and `Engine` uses `BuildConfig.QNN_VERSION`.
|
| 6 |
+
Comments in the source are in Chinese.
|
| 7 |
+
|
| 8 |
+
The models were prepared from [simonw/Moebius-ONNX](https://huggingface.co/simonw/Moebius-ONNX) (ONNX export of [hustvl/Moebius](https://github.com/hustvl/Moebius)).
|
| 9 |
+
`Pipeline.kt` and the noise / masking parts of `Imaging.kt` are a Kotlin port of the browser demo [simonw/moebius-web](https://github.com/simonw/moebius-web) (`ddim.ts`, `pipeline.ts`, `imaging.ts`). For a given seed, the initial noise is bit-identical to that demo.
|
| 10 |
+
|
| 11 |
+
## How one inpainting run works
|
| 12 |
+
|
| 13 |
+
1. **Window** (`Imaging.window`): by default a square around the mask's bounding box (2.5× its size, at least 512 px, clamped to the photo) is cropped and scaled to 512×512 (`crop`). The whole photo can be letterboxed to 512 instead (`letterbox`, as in the web demo).
|
| 14 |
+
2. **Encode**: RGB is mapped to [-1, 1] as NCHW (1,3,512,512) and fed to the VAE encoder. The first 4 of the 8 output channels (the mean) are multiplied by **0.13025**. This is the model's own VAE scaling factor, not Stable Diffusion's 0.18215.
|
| 15 |
+
3. **Noise**: mulberry32 + Box–Muller, plus a noise offset of 0.0357 times a per-channel randn (`Noise.initLatents`).
|
| 16 |
+
4. **UNet**: DDIM with scaled-linear betas (0.00085–0.012, 1000 train steps). 20 steps at strength 0.99 run 19 timesteps, 900 → 0.
|
| 17 |
+
The UNet input is `latent` (B,9,64,64) = [noisy latent 4, mask 1, masked-image latent 4], plus `timesteps` (int64, B) and `context` (B,10,3072).
|
| 18 |
+
Classifier-free guidance uses B = 2 ([uncond, cond]) with guidance 2.0 by default.
|
| 19 |
+
`context` is the result of the original graph's embedding lookup for the fixed `input_ids`, stored in [`assets/context.bin`](assets/context.bin) (float32, 2×10×3072, little-endian).
|
| 20 |
+
5. **Decode**: latents ÷ 0.13025 go to the VAE decoder.
|
| 21 |
+
6. **Paste back** (`Imaging.compose`): the 512 result is scaled back to the window. Only pixels inside the feathered mask are replaced; every other pixel of the full-resolution photo is left bit-identical.
|
| 22 |
+
|
| 23 |
+
## Model sets ("profiles")
|
| 24 |
+
|
| 25 |
+
[`assets/models.json`](assets/models.json) is the manifest compiled into the APK. It lists, for each profile, its files with size and sha256, which UNet/VAE files to use, and the download base URL (this repository).
|
| 26 |
+
`ModelStore` downloads with HTTP Range resume and accepts a file only when its sha256 matches the manifest. `Engine.candidates` picks the first profile the device supports whose files are all present, and `Inpainter` falls back to the next profile on failure.
|
| 27 |
+
|
| 28 |
+
| Profile | Runs on | How `Engine` runs it |
|
| 29 |
+
|---|---|---|
|
| 30 |
+
| `gpu+htp` | Snapdragon 8 Elite (SM8750) | The two CFG branches run in parallel, each with batch 1: uncond on the NPU+CPU hybrid chain `hyb1.json`, cond on `unet.b1.fp16x.onnx` with the QNN GPU backend. The VAE uses HTP context binaries |
|
| 31 |
+
| `gpu` | SM8750 | UNet `unet.fp16x.onnx` on the QNN GPU backend. The GPU context binary is generated on the device on first run (`ep.context_enable`) and cached, keyed by build fingerprint and QNN version |
|
| 32 |
+
| `htp` | SM8750 | UNet as a 13-segment chain (`hyb.json`): HTP context binaries plus CPU segments. Tensors are passed by name and freed after their last use |
|
| 33 |
+
| `cpu` | any arm64 device | `unet.einsum.onnx` and fp32 VAE on the ORT CPU EP |
|
| 34 |
+
| `cpu-int8` | any arm64 device | Same as `cpu`, but the UNet convolutions are int8 (QDQ) |
|
| 35 |
+
|
| 36 |
+
Runtime: `com.microsoft.onnxruntime:onnxruntime-android-qnn` 1.29.0 with `com.qualcomm.qti:qnn-runtime` 2.51 (the same QNN version that compiled the HTP context binaries on the host).
|
| 37 |
+
For the QNN EPs, `session.disable_cpu_ep_fallback` is set to 1. HTP sessions use `htp_performance_mode` (burst by default) and `enable_htp_fp16_precision`.
|
| 38 |
+
`ADSP_LIBRARY_PATH` must point at the app's native library directory before ORT is loaded.
|
| 39 |
+
The manifest declares `libcdsprpc.so` and `libOpenCL.so` as `uses-native-library`; without them, QNN fails on Android 12+.
|
| 40 |
+
|
| 41 |
+
Measured speed: about 21 s per inpainting run on a realme RMX5090 (SM8750, `gpu+htp`); about 6–10 minutes on a OnePlus 6 (SDM845, `cpu-int8` / `cpu`).
|
| 42 |
+
|
| 43 |
+
`assets/bench/unet0_latent.bin` → `unet0_noise.bin` is one UNet call (batch 2, timestep 900) with its reference output from ORT CPU fp32 on the host. `Engine.benchmark()` uses it to check every profile on the device.
|
| 44 |
+
|
| 45 |
+
## Files
|
| 46 |
+
|
| 47 |
+
The publishing script (`tools/upload-app-code.py` in the project) uploads exactly the files in this table to `app/` on Hugging Face, along with this README.
|
| 48 |
+
The first column is the path under `app/` here; the second is the path in the project.
|
| 49 |
+
|
| 50 |
+
<!-- files -->
|
| 51 |
+
| File | Project path | Contents |
|
| 52 |
+
|---|---|---|
|
| 53 |
+
| `src/main/kotlin/com/yboom/moebius/Pipeline.kt` | `app/src/main/kotlin/com/yboom/moebius/Pipeline.kt` | Constants, mulberry32 / Box–Muller noise, DDIM, CFG, pipeline orchestration (no Android dependencies) |
|
| 54 |
+
| `src/main/kotlin/com/yboom/moebius/Imaging.kt` | `app/src/main/kotlin/com/yboom/moebius/Imaging.kt` | Crop / letterbox window, PIL-compatible resampling, mask binarization and 64×64 downsampling, feathering, full-resolution paste-back (no Android dependencies) |
|
| 55 |
+
| `src/main/kotlin/com/yboom/moebius/Engine.kt` | `app/src/main/kotlin/com/yboom/moebius/Engine.kt` | Device capability checks, ORT / QNN session options, HTP context sessions, hybrid chain, parallel CFG branches, GPU context cache, benchmark |
|
| 56 |
+
| `src/main/kotlin/com/yboom/moebius/ModelStore.kt` | `app/src/main/kotlin/com/yboom/moebius/ModelStore.kt` | `models.json` parsing, sha256 verification, resumable download from this repository, import |
|
| 57 |
+
| `src/main/kotlin/com/yboom/moebius/Inpainter.kt` | `app/src/main/kotlin/com/yboom/moebius/Inpainter.kt` | One end-to-end run: decode photo, mask, window, pipeline, profile fallback, compose, save |
|
| 58 |
+
| `src/main/kotlin/com/yboom/moebius/BitmapIo.kt` | `app/src/main/kotlin/com/yboom/moebius/BitmapIo.kt` | Photo decoding (ImageDecoder: HEIF, EXIF orientation) and saving |
|
| 59 |
+
| `src/main/AndroidManifest.xml` | `app/src/main/AndroidManifest.xml` | `uses-native-library` for QNN, `extractNativeLibs`, foreground service declaration |
|
| 60 |
+
| `build.gradle.kts` | `app/build.gradle.kts` | ORT / QNN dependencies (QNN runtime pinned to 2.51), arm64 only, native libraries extracted to disk at install (`useLegacyPackaging`) |
|
| 61 |
+
| `versions.env` | `versions.env` | Versions used by the build (ORT, QNN, Gradle) and the sha256 of the original models |
|
| 62 |
+
| `assets/models.json` | `out/app-assets/models.json` | Manifest of all profiles: files, sizes, sha256, download base |
|
| 63 |
+
| `assets/context.bin` | `out/app-assets/context.bin` | The UNet `context` input (float32, 2×10×3072) |
|
| 64 |
+
| `assets/bench/unet0_latent.bin` | `out/app-assets/bench/unet0_latent.bin` | Benchmark input: UNet `latent` (float32, 2×9×64×64), timestep 900 |
|
| 65 |
+
| `assets/bench/unet0_noise.bin` | `out/app-assets/bench/unet0_noise.bin` | Its reference output (float32, 2×4×64×64) |
|
| 66 |
+
| `src/test/kotlin/com/yboom/moebius/PipelineTest.kt` | `app/src/test/kotlin/com/yboom/moebius/PipelineTest.kt` | JVM tests: noise, DDIM and CFG against Python reference values |
|
| 67 |
+
| `src/test/kotlin/com/yboom/moebius/ImagingTest.kt` | `app/src/test/kotlin/com/yboom/moebius/ImagingTest.kt` | JVM tests: resampling byte-identical to PIL, windows, paste-back |
|
| 68 |
+
| `src/test/kotlin/com/yboom/moebius/Vectors.kt` | `app/src/test/kotlin/com/yboom/moebius/Vectors.kt` | Test vector loader |
|
| 69 |
+
| `src/test/resources/vectors.txt` | `app/src/test/resources/vectors.txt` | Reference values generated by the Python pipeline and PIL |
|
| 70 |
+
<!-- /files -->
|
app/assets/bench/unet0_latent.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:adc3686d85ae9590f0443ee98b10736a8f289b69e00806cd2226be77d985a271
|
| 3 |
+
size 294912
|
app/assets/bench/unet0_noise.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ef8cc8cd230385b9f7a8926c2455aefffe68bc31b48b264b006f7fe9db3f2f9f
|
| 3 |
+
size 131072
|
app/assets/context.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1b46b6251205fd7473f9edb834073848df00fe83e3224ee1f4a2f1bc4bb52c8e
|
| 3 |
+
size 245760
|
app/assets/models.json
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": 1,
|
| 3 |
+
"profiles": [
|
| 4 |
+
{
|
| 5 |
+
"id": "gpu+htp",
|
| 6 |
+
"label": {
|
| 7 |
+
"en": "GPU + NPU in parallel (one per CFG branch)",
|
| 8 |
+
"zh": "GPU + NPU 并行(CFG 两个分支各用一个)",
|
| 9 |
+
"fi": "GPU + NPU rinnakkain (yksi kummallekin CFG-haaralle)"
|
| 10 |
+
},
|
| 11 |
+
"requires": "qnn-gpu-htp-v79",
|
| 12 |
+
"unet": {
|
| 13 |
+
"parallel": [
|
| 14 |
+
{
|
| 15 |
+
"chain": "hyb1.json"
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"file": "unet.b1.fp16x.onnx",
|
| 19 |
+
"ep": "qnn-gpu"
|
| 20 |
+
}
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
"encoder": "vae_encoder.static_ctx.onnx",
|
| 24 |
+
"decoder": "vae_decoder.static_ctx.onnx",
|
| 25 |
+
"url_base": "https://huggingface.co/luweigen/moebius-onnx-android/resolve/main/",
|
| 26 |
+
"files": [
|
| 27 |
+
{
|
| 28 |
+
"name": "vae_encoder.static_ctx.onnx",
|
| 29 |
+
"size": 730,
|
| 30 |
+
"sha256": "e5946bfbd701c8aa3878475c89a9e739e4750bbb84587e57931d7204e285db4a"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"name": "vae_decoder.static_ctx.onnx",
|
| 34 |
+
"size": 726,
|
| 35 |
+
"sha256": "290b0db465b37598ed12cb14eaa944b07f241018bb548d8085784186b047562d"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"name": "hyb1.json",
|
| 39 |
+
"size": 6135,
|
| 40 |
+
"sha256": "eb1d7882d6b9e48f1608427eaece4420b9394937053b433000e7e0bee9ff2999"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"name": "hyb1.s0.htp_ctx.onnx",
|
| 44 |
+
"size": 3052,
|
| 45 |
+
"sha256": "91985a21128c2582bcf2877bcd630135202f7fd0243d34b7f11e6d9f43baad5b"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"name": "hyb1.s0.htp_ctx_qnn.bin",
|
| 49 |
+
"size": 6180864,
|
| 50 |
+
"sha256": "343513a2331ae85214bb64aa7cec8c13494642366d5f64491a9addaf572bd80d"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"name": "hyb1.s1.cpu.onnx",
|
| 54 |
+
"size": 8207208,
|
| 55 |
+
"sha256": "8cd407f8073fb949fd874e7b95f2ef287c1004d1c909e6d93c6fffb6ce1a391c"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"name": "hyb1.s2.htp_ctx.onnx",
|
| 59 |
+
"size": 1311,
|
| 60 |
+
"sha256": "03166611ec067a812f70a278b9a4b1dce7e2a4ac79ac3d9b32397b30521a18ad"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"name": "hyb1.s2.htp_ctx_qnn.bin",
|
| 64 |
+
"size": 1515520,
|
| 65 |
+
"sha256": "e7eba347109cd89edf9f9473ce4859a31b5461a995b15b08946e3c263311d2a2"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"name": "hyb1.s3.cpu.onnx",
|
| 69 |
+
"size": 11533034,
|
| 70 |
+
"sha256": "6baf1df27655bac126204ddd167094e0547d7de5e9b284baa5340133ed7fef73"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"name": "hyb1.s4.htp_ctx.onnx",
|
| 74 |
+
"size": 1061,
|
| 75 |
+
"sha256": "4cce5444099e69bcbedb8e59546c60247487a5d0bbc9519bd26817b85a8634fc"
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"name": "hyb1.s4.htp_ctx_qnn.bin",
|
| 79 |
+
"size": 1986560,
|
| 80 |
+
"sha256": "0b344c01bd1ff69133197395e331f5cf141cc732f39fd52a8e64b578d9f3686f"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"name": "hyb1.s5.cpu.onnx",
|
| 84 |
+
"size": 11532280,
|
| 85 |
+
"sha256": "db44907ce577b4aea29d08d98def744c4274eb7d1a05bd4131862220fc2832fa"
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"name": "hyb1.s6.htp_ctx.onnx",
|
| 89 |
+
"size": 1174,
|
| 90 |
+
"sha256": "d957f277235b0dd77c972a77bbdec40ca916c73cb610aff34f73d062be41765a"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"name": "hyb1.s6.htp_ctx_qnn.bin",
|
| 94 |
+
"size": 477036544,
|
| 95 |
+
"sha256": "244f02a77d7ed7c5c2b0aa3fde79383f9f6dd08b36a6bd61c75a29f0adc65a7b"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"name": "hyb1.s7.cpu.onnx",
|
| 99 |
+
"size": 11531480,
|
| 100 |
+
"sha256": "d9f7bc36a2ddeba5ea1bacf803f5b2747e253b55ef591ce6f5ed98fda2db1b8a"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"name": "hyb1.s8.htp_ctx.onnx",
|
| 104 |
+
"size": 1192,
|
| 105 |
+
"sha256": "2efed95898a37b641cbe9a6d8220510e9ff405e782646fd780089c53a68119c4"
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"name": "hyb1.s8.htp_ctx_qnn.bin",
|
| 109 |
+
"size": 2977792,
|
| 110 |
+
"sha256": "13aa3a5bb1cf844a30c225e3c6f259996024798e4af879dce13cbc0799d902ed"
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"name": "hyb1.s9.cpu.onnx",
|
| 114 |
+
"size": 11531480,
|
| 115 |
+
"sha256": "e59def3ae2462b1ae377edd4b8da6b99358c45fe226878f2e75dba46712194d4"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"name": "hyb1.s10.htp_ctx.onnx",
|
| 119 |
+
"size": 1173,
|
| 120 |
+
"sha256": "d525f94362d5197c6125926e48820d5716e5b67dc349bf32660c5bbe4eb3a683"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"name": "hyb1.s10.htp_ctx_qnn.bin",
|
| 124 |
+
"size": 2973696,
|
| 125 |
+
"sha256": "9c7eaef319894e5dadcf1eaf91a41a5c24985d0c6b77c99cae68f1bddc3fa2db"
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"name": "hyb1.s11.cpu.onnx",
|
| 129 |
+
"size": 11532736,
|
| 130 |
+
"sha256": "7d7731106f56aedb62302440b40fc3e5a5f22f27f861ee338064cfe92294de7b"
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"name": "hyb1.s12.htp_ctx.onnx",
|
| 134 |
+
"size": 812,
|
| 135 |
+
"sha256": "c457b58101f84ffac3ac446e5925256db53d1f7caae4b3a9371d66cebad868d9"
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"name": "hyb1.s12.htp_ctx_qnn.bin",
|
| 139 |
+
"size": 413696,
|
| 140 |
+
"sha256": "c162e874a945ee139fa9efc57b13bf66dc4a0b6ff6cbde1275aa6c2af7ac3ccd"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"name": "unet.b1.fp16x.onnx",
|
| 144 |
+
"size": 507335889,
|
| 145 |
+
"sha256": "07fd4b9ceb171b1bac42764b31a9c5a9dd26f3324b45d9d86794234bc272de0e"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"name": "vae_encoder.static_ctx_qnn.bin",
|
| 149 |
+
"size": 76587008,
|
| 150 |
+
"sha256": "b233315cc79790ef62854b2fbe0323b39797829f20b2af81ec86d13ac5becd2e"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"name": "vae_decoder.static_ctx_qnn.bin",
|
| 154 |
+
"size": 111640576,
|
| 155 |
+
"sha256": "701d86e0529dc8cb004256cf0890a5ed71f1d7e090550081a76f5fa8215662e6"
|
| 156 |
+
}
|
| 157 |
+
],
|
| 158 |
+
"total_size": 1254533729
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"id": "gpu",
|
| 162 |
+
"label": {
|
| 163 |
+
"en": "GPU (QNN GPU, UNet fp16x) + NPU (VAE)",
|
| 164 |
+
"zh": "GPU(QNN GPU,UNet fp16x)+ NPU(VAE)",
|
| 165 |
+
"fi": "GPU (QNN GPU, UNet fp16x) + NPU (VAE)"
|
| 166 |
+
},
|
| 167 |
+
"requires": "qnn-gpu-htp-v79",
|
| 168 |
+
"unet": {
|
| 169 |
+
"file": "unet.fp16x.onnx",
|
| 170 |
+
"ep": "qnn-gpu"
|
| 171 |
+
},
|
| 172 |
+
"encoder": "vae_encoder.static_ctx.onnx",
|
| 173 |
+
"decoder": "vae_decoder.static_ctx.onnx",
|
| 174 |
+
"url_base": "https://huggingface.co/luweigen/moebius-onnx-android/resolve/main/",
|
| 175 |
+
"files": [
|
| 176 |
+
{
|
| 177 |
+
"name": "vae_encoder.static_ctx.onnx",
|
| 178 |
+
"size": 730,
|
| 179 |
+
"sha256": "e5946bfbd701c8aa3878475c89a9e739e4750bbb84587e57931d7204e285db4a"
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"name": "vae_decoder.static_ctx.onnx",
|
| 183 |
+
"size": 726,
|
| 184 |
+
"sha256": "290b0db465b37598ed12cb14eaa944b07f241018bb548d8085784186b047562d"
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"name": "unet.fp16x.onnx",
|
| 188 |
+
"size": 507340022,
|
| 189 |
+
"sha256": "50b8273e45d5772c74acee90d745a4ea5ca47aadea10ecdcebdeb786e794d71a"
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"name": "vae_encoder.static_ctx_qnn.bin",
|
| 193 |
+
"size": 76587008,
|
| 194 |
+
"sha256": "b233315cc79790ef62854b2fbe0323b39797829f20b2af81ec86d13ac5becd2e"
|
| 195 |
+
},
|
| 196 |
+
{
|
| 197 |
+
"name": "vae_decoder.static_ctx_qnn.bin",
|
| 198 |
+
"size": 111640576,
|
| 199 |
+
"sha256": "701d86e0529dc8cb004256cf0890a5ed71f1d7e090550081a76f5fa8215662e6"
|
| 200 |
+
}
|
| 201 |
+
],
|
| 202 |
+
"total_size": 695569062
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"id": "htp",
|
| 206 |
+
"label": {
|
| 207 |
+
"en": "NPU (QNN HTP) + CPU hybrid",
|
| 208 |
+
"zh": "NPU(QNN HTP)+ CPU 混合",
|
| 209 |
+
"fi": "NPU (QNN HTP) + CPU yhdistelmä"
|
| 210 |
+
},
|
| 211 |
+
"requires": "qnn-htp-v79",
|
| 212 |
+
"unet": {
|
| 213 |
+
"chain": "hyb.json"
|
| 214 |
+
},
|
| 215 |
+
"encoder": "vae_encoder.static_ctx.onnx",
|
| 216 |
+
"decoder": "vae_decoder.static_ctx.onnx",
|
| 217 |
+
"url_base": "https://huggingface.co/luweigen/moebius-onnx-android/resolve/main/",
|
| 218 |
+
"files": [
|
| 219 |
+
{
|
| 220 |
+
"name": "vae_encoder.static_ctx.onnx",
|
| 221 |
+
"size": 730,
|
| 222 |
+
"sha256": "e5946bfbd701c8aa3878475c89a9e739e4750bbb84587e57931d7204e285db4a"
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"name": "vae_decoder.static_ctx.onnx",
|
| 226 |
+
"size": 726,
|
| 227 |
+
"sha256": "290b0db465b37598ed12cb14eaa944b07f241018bb548d8085784186b047562d"
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"name": "hyb.json",
|
| 231 |
+
"size": 6119,
|
| 232 |
+
"sha256": "efe327a942fe5218f298c4aecd23098a1bcf9a471a200b5eba65182bcb92d792"
|
| 233 |
+
},
|
| 234 |
+
{
|
| 235 |
+
"name": "hyb.s0.htp_ctx.onnx",
|
| 236 |
+
"size": 3048,
|
| 237 |
+
"sha256": "85a38fd492685bc234f39c63271e0aab6988574f885b1a46e0eb32773376cfff"
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"name": "hyb.s0.htp_ctx_qnn.bin",
|
| 241 |
+
"size": 6656000,
|
| 242 |
+
"sha256": "14d594fc7eb8f1962c721ac9a7c08102ae76399112458e23c8f4740c4c5a3505"
|
| 243 |
+
},
|
| 244 |
+
{
|
| 245 |
+
"name": "hyb.s1.cpu.onnx",
|
| 246 |
+
"size": 8207345,
|
| 247 |
+
"sha256": "52ca5f2d075e78bc817abc3b1a529a678cb8ba31761c096a9b11f68e9ceb8cdd"
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"name": "hyb.s2.htp_ctx.onnx",
|
| 251 |
+
"size": 1307,
|
| 252 |
+
"sha256": "e2c445d0c4a107dc3c77c6922525bb131d315cfc6acc579d4d7b559e609836e9"
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"name": "hyb.s2.htp_ctx_qnn.bin",
|
| 256 |
+
"size": 1716224,
|
| 257 |
+
"sha256": "9969dfaad79c0da3108bdb9acd0931a99a861d91f7b4a62178093234c6f918fe"
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"name": "hyb.s3.cpu.onnx",
|
| 261 |
+
"size": 11533031,
|
| 262 |
+
"sha256": "c44e3a8d70a000193710f852eb64cbf3bc3c39c43b186d61558ba5e3b6d0c9bd"
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"name": "hyb.s4.htp_ctx.onnx",
|
| 266 |
+
"size": 1059,
|
| 267 |
+
"sha256": "b53c90a674718939cb5526fa80d393085856ab807bf0182d74e8fe04093cf13c"
|
| 268 |
+
},
|
| 269 |
+
{
|
| 270 |
+
"name": "hyb.s4.htp_ctx_qnn.bin",
|
| 271 |
+
"size": 2347008,
|
| 272 |
+
"sha256": "b2c1f0521e39506f17f215801b802f6c398fd6c6d3351353c31152718345bba3"
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
"name": "hyb.s5.cpu.onnx",
|
| 276 |
+
"size": 11532277,
|
| 277 |
+
"sha256": "5ccf1ab479d093f8fa7200ce48cf4a1a18383e3e078827dc7f53b3b018ea4253"
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"name": "hyb.s6.htp_ctx.onnx",
|
| 281 |
+
"size": 1168,
|
| 282 |
+
"sha256": "125e0ead3c788560982ef4af6473297adf91d751504fef1a3c8e2f3ba8d48491"
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"name": "hyb.s6.htp_ctx_qnn.bin",
|
| 286 |
+
"size": 492933120,
|
| 287 |
+
"sha256": "d25a2b3958ff43467559f09d1bc7ec34ad63b93b006f135e83b66190eb138557"
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"name": "hyb.s7.cpu.onnx",
|
| 291 |
+
"size": 11531477,
|
| 292 |
+
"sha256": "37e835ac7a7ae833b2919b80e7abd92e234d9f04f15e9731061025d28afc661a"
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"name": "hyb.s8.htp_ctx.onnx",
|
| 296 |
+
"size": 1188,
|
| 297 |
+
"sha256": "f4b0e53b99ca29db870b8a912bb0ffc3db5e19e1185028a6648d2d5e499b7136"
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"name": "hyb.s8.htp_ctx_qnn.bin",
|
| 301 |
+
"size": 3502080,
|
| 302 |
+
"sha256": "2f680e82df385db1f4e248043a0afcf1c9bfe7185759494d04e8a670f9e81cec"
|
| 303 |
+
},
|
| 304 |
+
{
|
| 305 |
+
"name": "hyb.s9.cpu.onnx",
|
| 306 |
+
"size": 11531477,
|
| 307 |
+
"sha256": "b98768bda6f9d8cecd8b9dee343e62f5e406278f213d4788c23c412653bb8482"
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"name": "hyb.s10.htp_ctx.onnx",
|
| 311 |
+
"size": 1171,
|
| 312 |
+
"sha256": "71b793201d91088165e5c78125798846b1bc8a5cfc50c470044e4e8015304596"
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"name": "hyb.s10.htp_ctx_qnn.bin",
|
| 316 |
+
"size": 3497984,
|
| 317 |
+
"sha256": "3191c169c31045a0fcae347a54a5d8cb5914abee0dac005f79d79fe7ef08133e"
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
"name": "hyb.s11.cpu.onnx",
|
| 321 |
+
"size": 11532733,
|
| 322 |
+
"sha256": "161a6fba36c0dd3501d90bae60dd5fe29d7ca56294a4f1c2889825fb6752eae1"
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"name": "hyb.s12.htp_ctx.onnx",
|
| 326 |
+
"size": 808,
|
| 327 |
+
"sha256": "e3c27c58e9451041ad12367ed3c8dc130b5c478190220bf0a9a17a55e30b6952"
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"name": "hyb.s12.htp_ctx_qnn.bin",
|
| 331 |
+
"size": 540672,
|
| 332 |
+
"sha256": "72b2f694a27711da6e4dad140a234d1106801e0f5db0fb0cc76cc0b3eab28a3a"
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"name": "vae_encoder.static_ctx_qnn.bin",
|
| 336 |
+
"size": 76587008,
|
| 337 |
+
"sha256": "b233315cc79790ef62854b2fbe0323b39797829f20b2af81ec86d13ac5becd2e"
|
| 338 |
+
},
|
| 339 |
+
{
|
| 340 |
+
"name": "vae_decoder.static_ctx_qnn.bin",
|
| 341 |
+
"size": 111640576,
|
| 342 |
+
"sha256": "701d86e0529dc8cb004256cf0890a5ed71f1d7e090550081a76f5fa8215662e6"
|
| 343 |
+
}
|
| 344 |
+
],
|
| 345 |
+
"total_size": 765306336
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"id": "cpu",
|
| 349 |
+
"label": {
|
| 350 |
+
"en": "CPU (Einsum rewritten, static shapes)",
|
| 351 |
+
"zh": "CPU(Einsum 改写、静态形状)",
|
| 352 |
+
"fi": "CPU (Einsum uudelleenkirjoitettu, kiinteät muodot)"
|
| 353 |
+
},
|
| 354 |
+
"requires": "cpu",
|
| 355 |
+
"unet": {
|
| 356 |
+
"file": "unet.einsum.onnx"
|
| 357 |
+
},
|
| 358 |
+
"encoder": "vae_encoder.static.onnx",
|
| 359 |
+
"decoder": "vae_decoder.static.onnx",
|
| 360 |
+
"url_base": "https://huggingface.co/luweigen/moebius-onnx-android/resolve/main/",
|
| 361 |
+
"files": [
|
| 362 |
+
{
|
| 363 |
+
"name": "vae_encoder.static.onnx",
|
| 364 |
+
"size": 136758234,
|
| 365 |
+
"sha256": "390dfee84e3b3361b4eb290a829f42984f1e129c583c08abc917accb7be0f5b6"
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"name": "vae_decoder.static.onnx",
|
| 369 |
+
"size": 198090837,
|
| 370 |
+
"sha256": "20e26ae7333e4374e685932afa4986441b3c14712e256fc74e9555fee5957e2f"
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"name": "unet.einsum.onnx",
|
| 374 |
+
"size": 904522395,
|
| 375 |
+
"sha256": "4b1d9c08d9b38a97171dc72d68c01b8caee5c6fa5ddc0ef42d5b3b7914820b6a"
|
| 376 |
+
}
|
| 377 |
+
],
|
| 378 |
+
"total_size": 1239371466
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"id": "cpu-int8",
|
| 382 |
+
"label": {
|
| 383 |
+
"en": "CPU int8 (twice as fast, smoother texture)",
|
| 384 |
+
"zh": "CPU int8(快一倍,纹理较平滑)",
|
| 385 |
+
"fi": "CPU int8 (kaksi kertaa nopeampi, pehmeämpi tekstuuri)"
|
| 386 |
+
},
|
| 387 |
+
"requires": "cpu",
|
| 388 |
+
"unet": {
|
| 389 |
+
"file": "unet.int8r.onnx"
|
| 390 |
+
},
|
| 391 |
+
"encoder": "vae_encoder.static.onnx",
|
| 392 |
+
"decoder": "vae_decoder.static.onnx",
|
| 393 |
+
"url_base": "https://huggingface.co/luweigen/moebius-onnx-android/resolve/main/",
|
| 394 |
+
"files": [
|
| 395 |
+
{
|
| 396 |
+
"name": "vae_encoder.static.onnx",
|
| 397 |
+
"size": 136758234,
|
| 398 |
+
"sha256": "390dfee84e3b3361b4eb290a829f42984f1e129c583c08abc917accb7be0f5b6"
|
| 399 |
+
},
|
| 400 |
+
{
|
| 401 |
+
"name": "vae_decoder.static.onnx",
|
| 402 |
+
"size": 198090837,
|
| 403 |
+
"sha256": "20e26ae7333e4374e685932afa4986441b3c14712e256fc74e9555fee5957e2f"
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"name": "unet.int8r.onnx",
|
| 407 |
+
"size": 456653033,
|
| 408 |
+
"sha256": "73d61ce6537fc385f3cbb88c6f5c5f4b750993bc9b529fcd521d4ab51abfd501"
|
| 409 |
+
}
|
| 410 |
+
],
|
| 411 |
+
"total_size": 791502104
|
| 412 |
+
}
|
| 413 |
+
]
|
| 414 |
+
}
|
app/build.gradle.kts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Moebius 修补应用(TODO A1)。只打 arm64-v8a,只依赖 Kotlin 标准库和 ONNX Runtime。
|
| 2 |
+
//
|
| 3 |
+
// ORT 的选择(A1,2026-10-16):一个 APK 不能同时带 onnxruntime-android 和 onnxruntime-android-qnn(libonnxruntime.so 同名),
|
| 4 |
+
// 所以只用 QNN 版(ORT 1.29,含 CPU EP,没有 XNNPACK)。QNN 运行库换成 2.51(与主机离线编 HTP 上下文的 onnxruntime-qnn 2.7.0 同版本,R6)。
|
| 5 |
+
// realme 用 HTP 上下文 + CPU 混合链;OnePlus 6 用 CPU EP(比 XNNPACK 慢约 10%,docs/engine.md)。
|
| 6 |
+
import com.android.build.api.artifact.SingleArtifact
|
| 7 |
+
|
| 8 |
+
plugins {
|
| 9 |
+
id("com.android.application") version "9.4.1"
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
val repoDir: File = rootDir.parentFile
|
| 13 |
+
val outDir = File(repoDir, "out")
|
| 14 |
+
// tools/stage-app-assets.py 生成:context.bin、基准 fixture(bench/unet0_latent.bin、bench/unet0_noise.bin)、models.json(模型清单)
|
| 15 |
+
val stagedAssets = File(outDir, "app-assets")
|
| 16 |
+
|
| 17 |
+
val versionsEnv: Map<String, String> = File(repoDir, "versions.env").readLines()
|
| 18 |
+
.map { it.trim() }
|
| 19 |
+
.filter { it.isNotEmpty() && !it.startsWith("#") && '=' in it }
|
| 20 |
+
.associate { it.substringBefore('=') to it.substringAfter('=') }
|
| 21 |
+
|
| 22 |
+
android {
|
| 23 |
+
namespace = "com.yboom.moebius"
|
| 24 |
+
compileSdk = 36
|
| 25 |
+
|
| 26 |
+
defaultConfig {
|
| 27 |
+
applicationId = "com.yboom.moebius"
|
| 28 |
+
minSdk = 29
|
| 29 |
+
targetSdk = 36
|
| 30 |
+
versionCode = (findProperty("moebius.versionCode") as String?)?.toInt() ?: 1
|
| 31 |
+
versionName = (findProperty("moebius.versionName") as String?) ?: "0.1.0"
|
| 32 |
+
ndk { abiFilters += "arm64-v8a" }
|
| 33 |
+
buildConfigField("String", "ORT_VERSION", "\"${versionsEnv.getValue("ORT_ANDROID_QNN_VERSION")}\"")
|
| 34 |
+
buildConfigField("String", "QNN_VERSION", "\"${versionsEnv.getValue("QNN_RUNTIME_HOST_VERSION")}\"")
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
buildFeatures { buildConfig = true }
|
| 38 |
+
|
| 39 |
+
sourceSets {
|
| 40 |
+
getByName("main") {
|
| 41 |
+
assets.directories.add(stagedAssets.path)
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
packaging {
|
| 46 |
+
jniLibs {
|
| 47 |
+
// QNN HTP 的 skel 由 FastRPC 从文件系统加载(ADSP_LIBRARY_PATH),要求 .so 解压到 nativeLibraryDir
|
| 48 |
+
useLegacyPackaging = true
|
| 49 |
+
// 只保留 HTP V79(realme)。HTP 上下文全部在主机离线编好,设备上不编图,不需要 libQnnHtpPrepare(88 MB);
|
| 50 |
+
// GPU 后端(libQnnGpu.so,6 MB)给 realme 的 gpu 配置用(P6;OnePlus 6 的 Adreno 630 驱动会中止,docs/engine.md)
|
| 51 |
+
excludes += listOf(
|
| 52 |
+
"**/libQnnHtpV68*", "**/libQnnHtpV69*", "**/libQnnHtpV73*", "**/libQnnHtpV75*", "**/libQnnHtpV81*",
|
| 53 |
+
"**/libQnnDsp*", "**/libQnnHtpPrepare.so",
|
| 54 |
+
)
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
compileOptions {
|
| 59 |
+
sourceCompatibility = JavaVersion.VERSION_17
|
| 60 |
+
targetCompatibility = JavaVersion.VERSION_17
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
lint { abortOnError = false }
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
androidComponents {
|
| 67 |
+
onVariants { variant ->
|
| 68 |
+
val cap = variant.name.replaceFirstChar { it.uppercase() }
|
| 69 |
+
val copy = tasks.register<Copy>("copy${cap}ApkToOut") {
|
| 70 |
+
from(variant.artifacts.get(SingleArtifact.APK)) { include("*.apk") }
|
| 71 |
+
into(File(outDir, "apk"))
|
| 72 |
+
}
|
| 73 |
+
tasks.matching { it.name == "assemble$cap" }.configureEach { finalizedBy(copy) }
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// 资产由主机脚本生成,缺了就停下来提示,而不是打出一个运行时才失败的 APK
|
| 78 |
+
tasks.matching { it.name.startsWith("merge") && it.name.endsWith("Assets") }.configureEach {
|
| 79 |
+
doFirst {
|
| 80 |
+
val need = listOf("context.bin", "sample.png", "bench/unet0_latent.bin", "bench/unet0_noise.bin", "models.json")
|
| 81 |
+
val missing = need.filter { !File(stagedAssets, it).isFile }
|
| 82 |
+
if (missing.isNotEmpty()) throw GradleException("$stagedAssets 中缺少 $missing:先运行 python -I tools/stage-app-assets.py")
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
dependencies {
|
| 87 |
+
implementation("com.microsoft.onnxruntime:onnxruntime-android-qnn:${versionsEnv.getValue("ORT_ANDROID_QNN_VERSION")}") {
|
| 88 |
+
exclude(group = "com.qualcomm.qti", module = "qnn-runtime")
|
| 89 |
+
}
|
| 90 |
+
implementation("com.qualcomm.qti:qnn-runtime:${versionsEnv.getValue("QNN_RUNTIME_HOST_VERSION")}")
|
| 91 |
+
testImplementation("junit:junit:4.13.2")
|
| 92 |
+
}
|
app/src/main/AndroidManifest.xml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
| 3 |
+
|
| 4 |
+
<uses-permission android:name="android.permission.INTERNET" />
|
| 5 |
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
| 6 |
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
| 7 |
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
| 8 |
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
| 9 |
+
<!-- 关屏后继续运行(P5):OxygenOS 在关屏后杀掉忙碌的后台进程,电池优化豁免可以避免 -->
|
| 10 |
+
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
| 11 |
+
|
| 12 |
+
<!-- 预测性返回:不拦截返回键,系统显示返回桌面的预测动画(gitea docs/app-shell.md W4) -->
|
| 13 |
+
<application
|
| 14 |
+
android:label="Moebius"
|
| 15 |
+
android:localeConfig="@xml/locales_config"
|
| 16 |
+
android:icon="@android:drawable/ic_menu_gallery"
|
| 17 |
+
android:theme="@style/Theme.Moebius"
|
| 18 |
+
android:enableOnBackInvokedCallback="true"
|
| 19 |
+
android:extractNativeLibs="true"
|
| 20 |
+
android:largeHeap="true">
|
| 21 |
+
|
| 22 |
+
<!-- Android 12+:应用访问厂商公开的本地库要声明;QNN HTP 经 libcdsprpc.so(FastRPC)与 NPU 通信。设备上没有时不影响安装 -->
|
| 23 |
+
<uses-native-library android:name="libcdsprpc.so" android:required="false" />
|
| 24 |
+
<!-- QNN GPU 后端(gpu 配置,P6):Android 12+ 访问厂商的 OpenCL 要声明,否则 QNN 报 PLATFORM_NOT_SUPPORTED -->
|
| 25 |
+
<uses-native-library android:name="libOpenCL.so" android:required="false" />
|
| 26 |
+
|
| 27 |
+
<!-- 修补在前台服务中运行:几十秒到几分钟,应用在后台时 ColorOS / OxygenOS 会冻结没有前台服务的进程 -->
|
| 28 |
+
<service
|
| 29 |
+
android:name=".InpaintService"
|
| 30 |
+
android:exported="false"
|
| 31 |
+
android:foregroundServiceType="specialUse">
|
| 32 |
+
<property
|
| 33 |
+
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
| 34 |
+
android:value="On-device image inpainting with a diffusion model (user-started, 0.5-10 minutes)" />
|
| 35 |
+
</service>
|
| 36 |
+
|
| 37 |
+
<activity
|
| 38 |
+
android:name=".MainActivity"
|
| 39 |
+
android:exported="true"
|
| 40 |
+
android:launchMode="singleTop"
|
| 41 |
+
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|uiMode|density">
|
| 42 |
+
<intent-filter>
|
| 43 |
+
<action android:name="android.intent.action.MAIN" />
|
| 44 |
+
<category android:name="android.intent.category.LAUNCHER" />
|
| 45 |
+
</intent-filter>
|
| 46 |
+
<intent-filter>
|
| 47 |
+
<action android:name="android.intent.action.SEND" />
|
| 48 |
+
<category android:name="android.intent.category.DEFAULT" />
|
| 49 |
+
<data android:mimeType="image/*" />
|
| 50 |
+
</intent-filter>
|
| 51 |
+
<!-- 测试入口(test/run-app-test.sh,只在 debug 构建中生效,见 MainActivity.handleDebugIntent) -->
|
| 52 |
+
<intent-filter>
|
| 53 |
+
<action android:name="com.yboom.moebius.TEST" />
|
| 54 |
+
<action android:name="com.yboom.moebius.IMPORT" />
|
| 55 |
+
<action android:name="com.yboom.moebius.BENCH" />
|
| 56 |
+
<action android:name="com.yboom.moebius.DOWNLOAD" />
|
| 57 |
+
<category android:name="android.intent.category.DEFAULT" />
|
| 58 |
+
</intent-filter>
|
| 59 |
+
</activity>
|
| 60 |
+
|
| 61 |
+
<activity
|
| 62 |
+
android:name=".SettingsActivity"
|
| 63 |
+
android:exported="false"
|
| 64 |
+
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|uiMode|density" />
|
| 65 |
+
</application>
|
| 66 |
+
</manifest>
|
app/src/main/kotlin/com/yboom/moebius/BitmapIo.kt
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import android.content.ContentValues
|
| 4 |
+
import android.content.Context
|
| 5 |
+
import android.graphics.Bitmap
|
| 6 |
+
import android.graphics.ColorSpace
|
| 7 |
+
import android.graphics.ImageDecoder
|
| 8 |
+
import android.net.Uri
|
| 9 |
+
import android.os.Environment
|
| 10 |
+
import android.provider.MediaStore
|
| 11 |
+
import java.io.File
|
| 12 |
+
import java.nio.ByteBuffer
|
| 13 |
+
|
| 14 |
+
/**
|
| 15 |
+
* Bitmap 与 [Rgba] / [Gray] 之间的转换、解码和保存。
|
| 16 |
+
* 照片用 ImageDecoder 解码(HEIF、EXIF 方向都由它处理),像素保持照片自己的色彩空间(iPhone 的 HEIF 是 Display P3):
|
| 17 |
+
* 用 copyPixelsToBuffer 取原始值,结果按同一个色彩空间写回,所以蒙版外的像素逐位不变(getPixels 会转换到 sRGB)。
|
| 18 |
+
*/
|
| 19 |
+
object BitmapIo {
|
| 20 |
+
fun decode(ctx: Context, uri: Uri, maxSide: Int = 0): Bitmap {
|
| 21 |
+
val src = if (uri.scheme == "file") ImageDecoder.createSource(File(uri.path!!)) else ImageDecoder.createSource(ctx.contentResolver, uri)
|
| 22 |
+
return ImageDecoder.decodeBitmap(src) { d, info, _ ->
|
| 23 |
+
d.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
|
| 24 |
+
d.isMutableRequired = false
|
| 25 |
+
val w = info.size.width; val h = info.size.height
|
| 26 |
+
if (maxSide in 1 until maxOf(w, h)) {
|
| 27 |
+
val s = maxSide.toDouble() / maxOf(w, h)
|
| 28 |
+
d.setTargetSize(maxOf(1, (w * s).toInt()), maxOf(1, (h * s).toInt()))
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/** 解码预览图(长边 ≤ maxSide),同时返回原图尺寸(已按 EXIF 方向旋转,与 [decode] 不缩放时的尺寸相同)。 */
|
| 34 |
+
fun decodePreview(ctx: Context, uri: Uri, maxSide: Int): Pair<Bitmap, android.util.Size> {
|
| 35 |
+
var size: android.util.Size? = null
|
| 36 |
+
val src = if (uri.scheme == "file") ImageDecoder.createSource(File(uri.path!!)) else ImageDecoder.createSource(ctx.contentResolver, uri)
|
| 37 |
+
val b = ImageDecoder.decodeBitmap(src) { d, info, _ ->
|
| 38 |
+
size = info.size
|
| 39 |
+
val w = info.size.width; val h = info.size.height
|
| 40 |
+
if (maxSide < maxOf(w, h)) {
|
| 41 |
+
val s = maxSide.toDouble() / maxOf(w, h)
|
| 42 |
+
d.setTargetSize(maxOf(1, (w * s).toInt()), maxOf(1, (h * s).toInt()))
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
return b to size!!
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/** ARGB_8888 的原始字节(R、G、B、A);其他格式(例如 10 位 HEIF 的 RGBA_F16)先转换。 */
|
| 49 |
+
fun toRgba(b: Bitmap): Rgba {
|
| 50 |
+
val bm = if (b.config == Bitmap.Config.ARGB_8888) b else b.copy(Bitmap.Config.ARGB_8888, false)
|
| 51 |
+
val r = Rgba(bm.width, bm.height)
|
| 52 |
+
bm.copyPixelsToBuffer(ByteBuffer.wrap(r.px))
|
| 53 |
+
return r
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
fun fromRgba(r: Rgba, cs: ColorSpace? = null): Bitmap {
|
| 57 |
+
val b = Bitmap.createBitmap(r.w, r.h, Bitmap.Config.ARGB_8888, true, cs ?: ColorSpace.get(ColorSpace.Named.SRGB))
|
| 58 |
+
b.copyPixelsFromBuffer(ByteBuffer.wrap(r.px))
|
| 59 |
+
return b
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/** 蒙版图:取红色通道(白 = 修补;灰度 PNG 解码后 R=G=B)。 */
|
| 63 |
+
fun toGray(b: Bitmap): Gray {
|
| 64 |
+
val r = toRgba(b)
|
| 65 |
+
val g = Gray(r.w, r.h)
|
| 66 |
+
for (i in 0 until r.w * r.h) g.px[i] = r.px[i * 4]
|
| 67 |
+
return g
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
fun fromGray(g: Gray): Bitmap {
|
| 71 |
+
val r = Rgba(g.w, g.h)
|
| 72 |
+
for (i in 0 until g.w * g.h) { val v = g.px[i]; r.px[i * 4] = v; r.px[i * 4 + 1] = v; r.px[i * 4 + 2] = v; r.px[i * 4 + 3] = -1 }
|
| 73 |
+
return fromRgba(r)
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
fun save(b: Bitmap, f: File, format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG, quality: Int = 100) {
|
| 77 |
+
f.parentFile?.mkdirs()
|
| 78 |
+
val tmp = File(f.path + ".tmp")
|
| 79 |
+
tmp.outputStream().use { b.compress(format, quality, it) }
|
| 80 |
+
check(tmp.renameTo(f)) { "无法写入 $f" }
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/** 保存到相册 Pictures/Moebius/(MediaStore,API 29 起不需要存储权限)。 */
|
| 84 |
+
fun saveToGallery(ctx: Context, b: Bitmap, name: String): Uri {
|
| 85 |
+
val cr = ctx.contentResolver
|
| 86 |
+
val values = ContentValues().apply {
|
| 87 |
+
put(MediaStore.Images.Media.DISPLAY_NAME, name)
|
| 88 |
+
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
| 89 |
+
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Moebius")
|
| 90 |
+
put(MediaStore.Images.Media.IS_PENDING, 1)
|
| 91 |
+
}
|
| 92 |
+
val uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: error("MediaStore 拒绝插入")
|
| 93 |
+
try {
|
| 94 |
+
cr.openOutputStream(uri)!!.use { b.compress(Bitmap.CompressFormat.JPEG, 95, it) }
|
| 95 |
+
cr.update(uri, ContentValues().apply { put(MediaStore.Images.Media.IS_PENDING, 0) }, null, null)
|
| 96 |
+
} catch (e: Throwable) {
|
| 97 |
+
cr.delete(uri, null, null)
|
| 98 |
+
throw e
|
| 99 |
+
}
|
| 100 |
+
return uri
|
| 101 |
+
}
|
| 102 |
+
}
|
app/src/main/kotlin/com/yboom/moebius/Engine.kt
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import ai.onnxruntime.OnnxTensor
|
| 4 |
+
import ai.onnxruntime.OrtEnvironment
|
| 5 |
+
import ai.onnxruntime.OrtException
|
| 6 |
+
import ai.onnxruntime.OrtLoggingLevel
|
| 7 |
+
import ai.onnxruntime.OrtSession
|
| 8 |
+
import android.content.Context
|
| 9 |
+
import android.os.Build
|
| 10 |
+
import android.os.SystemClock
|
| 11 |
+
import android.system.Os
|
| 12 |
+
import android.util.Log
|
| 13 |
+
import org.json.JSONObject
|
| 14 |
+
import java.io.File
|
| 15 |
+
import java.nio.ByteBuffer
|
| 16 |
+
import java.nio.ByteOrder
|
| 17 |
+
import java.nio.FloatBuffer
|
| 18 |
+
import java.nio.LongBuffer
|
| 19 |
+
import kotlin.math.abs
|
| 20 |
+
|
| 21 |
+
/** 设备能力(决定哪些模型配置可用,README 第 3 节“决定”第 1 条)。 */
|
| 22 |
+
object Device {
|
| 23 |
+
val soc: String get() = if (Build.VERSION.SDK_INT >= 31) Build.SOC_MODEL else Build.HARDWARE
|
| 24 |
+
|
| 25 |
+
/** QNN HTP v79:主机离线编好的上下文只适用于 SM8750(soc_model 69),并且要有 FastRPC。 */
|
| 26 |
+
val htpV79: Boolean get() = soc == "SM8750" && listOf("/vendor/lib64/libcdsprpc.so", "/system/vendor/lib64/libcdsprpc.so").any { File(it).exists() }
|
| 27 |
+
|
| 28 |
+
fun supports(requires: String) = when (requires) {
|
| 29 |
+
"cpu" -> true
|
| 30 |
+
"qnn-htp-v79" -> htpV79
|
| 31 |
+
"qnn-gpu-htp-v79" -> htpV79 && gpuOpenCl
|
| 32 |
+
else -> false
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/** QNN GPU 后端用厂商的 OpenCL。只在 8 Elite 上验证过(P1);Adreno 630 有 OpenCL 但驱动会中止进程,所以 gpu 配置还要求 HTP v79。 */
|
| 36 |
+
val gpuOpenCl: Boolean get() = listOf("/vendor/lib64/libOpenCL.so", "/system/vendor/lib64/libOpenCL.so").any { File(it).exists() }
|
| 37 |
+
|
| 38 |
+
/** 大核数:最高频率不低于最快核心 75% 的核(骁龙 845:4 个 A75;8 Elite:全部 8 个)。读不到时用一半的核。 */
|
| 39 |
+
val bigCores: Int by lazy {
|
| 40 |
+
val freqs = (0 until Runtime.getRuntime().availableProcessors()).mapNotNull { i ->
|
| 41 |
+
runCatching { File("/sys/devices/system/cpu/cpu$i/cpufreq/cpuinfo_max_freq").readText().trim().toLong() }.getOrNull()
|
| 42 |
+
}
|
| 43 |
+
if (freqs.isEmpty()) maxOf(1, Runtime.getRuntime().availableProcessors() / 2)
|
| 44 |
+
else freqs.count { it >= freqs.max() * 0.75 }
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
object Ort {
|
| 49 |
+
/** QNN HTP:FastRPC 按 ADSP_LIBRARY_PATH 查找 libQnnHtpV79Skel.so,必须在第一次加载 ORT 之前设置。 */
|
| 50 |
+
fun env(ctx: Context): OrtEnvironment {
|
| 51 |
+
if (System.getenv("ADSP_LIBRARY_PATH")?.startsWith(ctx.applicationInfo.nativeLibraryDir) != true) {
|
| 52 |
+
Os.setenv("ADSP_LIBRARY_PATH", "${ctx.applicationInfo.nativeLibraryDir};/vendor/dsp/cdsp;/vendor/lib/rfsa/adsp;/system/lib/rfsa/adsp;/dsp", true)
|
| 53 |
+
}
|
| 54 |
+
return OrtEnvironment.getEnvironment(OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING, "moebius")
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
data class EngineOptions(
|
| 59 |
+
/** HTP 性能模式:burst、sustained_high_performance、high_performance、balanced。 */
|
| 60 |
+
val htpPerf: String = "burst",
|
| 61 |
+
/** CPU 线程数,0 = 大核数。 */
|
| 62 |
+
val threads: Int = 0,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
data class BenchResult(val ok: Boolean, val createS: Double, val runS: Double, val maxAbs: Double, val meanAbs: Double, val error: String? = null)
|
| 66 |
+
|
| 67 |
+
/**
|
| 68 |
+
* 推理引擎(TODO A3):按一套模型配置(ModelProfile)建会话、运行三个网络。
|
| 69 |
+
* - htp(realme):VAE 编解码是 HTP 上下文(_ctx.onnx,主机离线编好);UNet 是 13 段的 HTP / CPU 混合链(hyb.json,R7),张量按名字在段间传递,参考探针 Probe.runChain。
|
| 70 |
+
* - gpu(realme,P6):VAE 同 htp;UNet 是单个 fp16x 模型,在 QNN GPU 上运行。GPU 上下文与驱动有关,不能在主机上编:第一次建会话时用 ep.context_enable
|
| 71 |
+
* 生成到 filesDir/qnn-cache/(约 16 s、527 MB),之后直接加载(约 1.1 s)。缓存按模型 sha256、系统指纹和 QNN 版本命名,任何一个变了就重新生成。
|
| 72 |
+
* - cpu(OnePlus 6 等):三个 fp32 模型都用 CPU EP(这个 ORT 构建没有 XNNPACK)。
|
| 73 |
+
* 会话按需创建:VAE 用完即释放;UNet 在第一步创建、所有步数结束(unetDone)后释放。HTP 运行出错(例如 DSP 子系统重启)时重建 UNet 会话重试一次。
|
| 74 |
+
*/
|
| 75 |
+
class Engine(private val ctx: Context, val store: ModelStore, val profile: ModelProfile, private val opt: EngineOptions = EngineOptions()) : MoebiusModels, AutoCloseable {
|
| 76 |
+
private val env = Ort.env(ctx)
|
| 77 |
+
val threads = if (opt.threads > 0) opt.threads else Device.bigCores
|
| 78 |
+
private val context: FloatArray by lazy { assetFloats("context.bin") }
|
| 79 |
+
private var unet: Unet? = null
|
| 80 |
+
/** 各阶段耗时(秒):create_*、encode、decode、unet_step_*。 */
|
| 81 |
+
val timing = LinkedHashMap<String, Double>()
|
| 82 |
+
var log: (String) -> Unit = { Log.i(TAG, it) }
|
| 83 |
+
|
| 84 |
+
private fun options(ep: String, contextOut: File? = null): OrtSession.SessionOptions = OrtSession.SessionOptions().apply {
|
| 85 |
+
setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT)
|
| 86 |
+
when (ep) {
|
| 87 |
+
"htp" -> {
|
| 88 |
+
addConfigEntry("session.disable_cpu_ep_fallback", "1")
|
| 89 |
+
addQnn(mapOf("backend_type" to "htp", "htp_performance_mode" to opt.htpPerf, "enable_htp_fp16_precision" to "1"))
|
| 90 |
+
}
|
| 91 |
+
"qnn-gpu" -> {
|
| 92 |
+
addConfigEntry("session.disable_cpu_ep_fallback", "1")
|
| 93 |
+
if (contextOut != null) {
|
| 94 |
+
addConfigEntry("ep.context_enable", "1")
|
| 95 |
+
addConfigEntry("ep.context_file_path", contextOut.path)
|
| 96 |
+
addConfigEntry("ep.context_embed_mode", "0")
|
| 97 |
+
}
|
| 98 |
+
addQnn(mapOf("backend_type" to "gpu"))
|
| 99 |
+
}
|
| 100 |
+
else -> setIntraOpNumThreads(threads)
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
private fun open(name: String, ep: String = if (name.endsWith("_ctx.onnx")) "htp" else "cpu"): OrtSession {
|
| 105 |
+
val f = store.file(name)
|
| 106 |
+
require(f.isFile) { "缺少模型文件 $name" }
|
| 107 |
+
val t = SystemClock.elapsedRealtimeNanos()
|
| 108 |
+
val s = if (ep == "qnn-gpu") openGpu(name, f) else env.createSession(f.path, options(ep))
|
| 109 |
+
timing["create_$name"] = secs(t)
|
| 110 |
+
return s
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
/** QNN GPU:有缓存的上下文就加载它,否则从原模型建会话并同时写出上下文。缓存加载失败时删掉重新生成。 */
|
| 114 |
+
private fun openGpu(name: String, f: File): OrtSession {
|
| 115 |
+
val sha = profile.files.firstOrNull { it.name == name }?.sha256 ?: error("清单中没有 $name")
|
| 116 |
+
val sys = (Build.FINGERPRINT + "|" + BuildConfig.QNN_VERSION).hashCode().toUInt().toString(16)
|
| 117 |
+
val stem = name.removeSuffix(".onnx")
|
| 118 |
+
val dir = File(ctx.filesDir, "qnn-cache").apply { mkdirs() }
|
| 119 |
+
val ctxOnnx = File(dir, stem + "_gpu_" + sha.take(16) + "_" + sys + "_ctx.onnx")
|
| 120 |
+
val ctxBin = File(dir, ctxOnnx.name.removeSuffix(".onnx") + "_qnn.bin")
|
| 121 |
+
fun clear() = dir.listFiles()?.filter { it.name.startsWith(stem + "_gpu_") }?.forEach { it.delete() }
|
| 122 |
+
if (ctxOnnx.isFile && ctxBin.isFile) {
|
| 123 |
+
try {
|
| 124 |
+
return env.createSession(ctxOnnx.path, options("qnn-gpu")).also { timing["gpu_cache_hit"] = 1.0 }
|
| 125 |
+
} catch (e: OrtException) {
|
| 126 |
+
log("GPU 上下文缓存加载失败,重新生成:$e")
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
// 旧的缓存(模型、系统或 QNN 版本变了)和没写完的文件:目标文件已存在时 ORT 拒绝生成
|
| 130 |
+
clear()
|
| 131 |
+
log("生成 $name 的 GPU 上下文(第一次运行,约 20 s)")
|
| 132 |
+
timing["gpu_cache_hit"] = 0.0
|
| 133 |
+
return try {
|
| 134 |
+
env.createSession(f.path, options("qnn-gpu", ctxOnnx))
|
| 135 |
+
} catch (e: Throwable) {
|
| 136 |
+
clear()
|
| 137 |
+
throw e
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
override fun encode(image: FloatArray): FloatArray = open(profile.encoder).use { s ->
|
| 142 |
+
val t = SystemClock.elapsedRealtimeNanos()
|
| 143 |
+
val m = run1(s, mapOf(s.inputNames.first() to tensor(image, 1, 3, 512, 512)))
|
| 144 |
+
timing["encode"] = secs(t)
|
| 145 |
+
// moments (1,8,64,64):前 4 个通道是均值
|
| 146 |
+
FloatArray(4 * Moebius.PLANE) { m[it] * Moebius.SCALING_FACTOR }
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
override fun decode(latents: FloatArray): FloatArray = open(profile.decoder).use { s ->
|
| 150 |
+
val t = SystemClock.elapsedRealtimeNanos()
|
| 151 |
+
val y = run1(s, mapOf(s.inputNames.first() to tensor(FloatArray(latents.size) { latents[it] / Moebius.SCALING_FACTOR }, 1, 4, 64, 64)))
|
| 152 |
+
timing["decode"] = secs(t)
|
| 153 |
+
y
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
override fun unet(latent: FloatArray, t: Int): FloatArray {
|
| 157 |
+
val u = unet ?: openUnet()
|
| 158 |
+
return try {
|
| 159 |
+
u.run(latent, t)
|
| 160 |
+
} catch (e: OrtException) {
|
| 161 |
+
if (profile.unetChain == null && profile.unetEp == "cpu" && profile.unetParallel == null) throw e
|
| 162 |
+
// HTP 出错(例如 cDSP 子系统重启,QNN 错误 1003 等):重建所有段再试一次
|
| 163 |
+
log("UNet 运行出错,重建会话后重试:$e")
|
| 164 |
+
unetDone()
|
| 165 |
+
openUnet().run(latent, t)
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
override fun unetDone() {
|
| 170 |
+
unet?.close()
|
| 171 |
+
unet = null
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
override fun close() = unetDone()
|
| 175 |
+
|
| 176 |
+
fun openUnet(): Unet {
|
| 177 |
+
val t = SystemClock.elapsedRealtimeNanos()
|
| 178 |
+
val par = profile.unetParallel
|
| 179 |
+
val u = when {
|
| 180 |
+
par != null -> ParallelUnet(par)
|
| 181 |
+
profile.unetChain != null -> ChainUnet(store.file(profile.unetChain))
|
| 182 |
+
else -> SingleUnet(profile.unetFile!!, profile.unetEp)
|
| 183 |
+
}
|
| 184 |
+
timing["create_unet"] = secs(t)
|
| 185 |
+
unet = u
|
| 186 |
+
return u
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
interface Unet : AutoCloseable { fun run(latent: FloatArray, t: Int): FloatArray }
|
| 190 |
+
|
| 191 |
+
/** row < 0:batch 2([uncond, cond]);row = 0 / 1:batch 1,latent 只有这一行,context 取对应的一行(P4)。 */
|
| 192 |
+
private fun baseFeeds(latent: FloatArray, t: Int, inputs: Set<String>, row: Int = -1): Map<String, OnnxTensor> = buildMap {
|
| 193 |
+
val b = if (row < 0) 2L else 1L
|
| 194 |
+
put("latent", tensor(latent, b, 9, 64, 64))
|
| 195 |
+
put("timesteps", OnnxTensor.createTensor(env, LongBuffer.wrap(LongArray(b.toInt()) { t.toLong() }), longArrayOf(b)))
|
| 196 |
+
val ctxRow = 10 * 3072
|
| 197 |
+
if ("context" in inputs) put("context", if (row < 0) tensor(context, 2, 10, 3072) else tensor(context.copyOfRange(row * ctxRow, (row + 1) * ctxRow), 1, 10, 3072))
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
private inner class SingleUnet(name: String, ep: String, private val row: Int = -1) : Unet {
|
| 201 |
+
private val s = open(name, ep)
|
| 202 |
+
|
| 203 |
+
override fun run(latent: FloatArray, t: Int): FloatArray {
|
| 204 |
+
val feeds = baseFeeds(latent, t, s.inputNames, row)
|
| 205 |
+
try { return run1(s, feeds) } finally { feeds.values.forEach { it.close() } }
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
override fun close() = s.close()
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
/** R7 的混合链:段按顺序运行;一段的全部输出都不再被后面的段使用时立即释放(中间张量合计数百 MB)。 */
|
| 212 |
+
private inner class ChainUnet(manifest: File, private val row: Int = -1, private val tag: String = "seg") : Unet {
|
| 213 |
+
private val segs = ArrayList<Pair<String, OrtSession>>()
|
| 214 |
+
private val final: String
|
| 215 |
+
private val lastUse = HashMap<String, Int>()
|
| 216 |
+
|
| 217 |
+
init {
|
| 218 |
+
val m = JSONObject(manifest.readText())
|
| 219 |
+
final = m.optString("final", "noise")
|
| 220 |
+
val arr = m.getJSONArray("segments")
|
| 221 |
+
try {
|
| 222 |
+
for (k in 0 until arr.length()) {
|
| 223 |
+
val seg = arr.getJSONObject(k)
|
| 224 |
+
val file = seg.getString("file").let { if (seg.getString("device") == "htp") it.removeSuffix(".onnx") + "_ctx.onnx" else it }
|
| 225 |
+
val s = open(file)
|
| 226 |
+
segs.add(file to s)
|
| 227 |
+
for (n in s.inputNames) lastUse[n] = k
|
| 228 |
+
}
|
| 229 |
+
} catch (e: Throwable) {
|
| 230 |
+
close()
|
| 231 |
+
throw e
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
override fun run(latent: FloatArray, t: Int): FloatArray {
|
| 236 |
+
val base = baseFeeds(latent, t, setOf("context"), row)
|
| 237 |
+
val tensors = HashMap<String, OnnxTensor>(base)
|
| 238 |
+
val live = ArrayList<OrtSession.Result>()
|
| 239 |
+
try {
|
| 240 |
+
for ((k, pair) in segs.withIndex()) {
|
| 241 |
+
val (name, s) = pair
|
| 242 |
+
val ins = s.inputNames.associateWith { tensors[it] ?: error("段 $k($name)缺少输入 $it") }
|
| 243 |
+
val ts = SystemClock.elapsedRealtimeNanos()
|
| 244 |
+
val r = s.run(ins)
|
| 245 |
+
synchronized(timing) { timing.merge("${tag}_$k", secs(ts), Double::plus) }
|
| 246 |
+
for ((n, v) in r) tensors[n] = v as OnnxTensor
|
| 247 |
+
live.add(r)
|
| 248 |
+
if (k == segs.size - 1) {
|
| 249 |
+
val fb = (tensors[final] ?: error("混合链没有输出 $final")).floatBuffer
|
| 250 |
+
return FloatArray(fb.remaining()).also { fb.get(it) }
|
| 251 |
+
}
|
| 252 |
+
val it = live.iterator()
|
| 253 |
+
while (it.hasNext()) {
|
| 254 |
+
val res = it.next()
|
| 255 |
+
if (res.all { (n, _) -> (lastUse[n] ?: -1) <= k }) {
|
| 256 |
+
res.forEach { (n, _) -> tensors.remove(n) }
|
| 257 |
+
res.close()
|
| 258 |
+
it.remove()
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
error("混合链为空")
|
| 263 |
+
} finally {
|
| 264 |
+
live.forEach { it.close() }
|
| 265 |
+
base.values.forEach { it.close() }
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
override fun close() {
|
| 270 |
+
segs.forEach { it.second.close() }
|
| 271 |
+
segs.clear()
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/**
|
| 276 |
+
* CFG 的两个分支并行(P4):uncond(第 0 行)和 cond(第 1 行)各用一个 batch 1 的 UNet,例如混合链(HTP + CPU)和 QNN GPU,
|
| 277 |
+
* 同时运行后按 [uncond, cond] 拼回 batch 2 的输出。第二个分支在单独的线程上(GPU 只等驱动,不和混合链的 CPU 段抢多少 CPU)。
|
| 278 |
+
*/
|
| 279 |
+
private inner class ParallelUnet(parts: List<UnetPart>) : Unet {
|
| 280 |
+
private val units = ArrayList<Unet>()
|
| 281 |
+
private val pool = java.util.concurrent.Executors.newSingleThreadExecutor { r -> Thread(r, "unet-cond") }
|
| 282 |
+
|
| 283 |
+
init {
|
| 284 |
+
require(parts.size == 2) { "parallel 要两个分支" }
|
| 285 |
+
try {
|
| 286 |
+
for ((row, p) in parts.withIndex()) {
|
| 287 |
+
units.add(if (p.chain != null) ChainUnet(store.file(p.chain), row, "b${row}_seg") else SingleUnet(p.file!!, p.ep, row))
|
| 288 |
+
}
|
| 289 |
+
} catch (e: Throwable) {
|
| 290 |
+
close()
|
| 291 |
+
throw e
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
override fun run(latent: FloatArray, t: Int): FloatArray {
|
| 296 |
+
val half = latent.size / 2
|
| 297 |
+
val t0 = SystemClock.elapsedRealtimeNanos()
|
| 298 |
+
val cond = pool.submit<Pair<FloatArray, Double>> {
|
| 299 |
+
val y = units[1].run(latent.copyOfRange(half, latent.size), t)
|
| 300 |
+
y to secs(t0)
|
| 301 |
+
}
|
| 302 |
+
val u = units[0].run(latent.copyOfRange(0, half), t)
|
| 303 |
+
val uS = secs(t0)
|
| 304 |
+
val (c, cS) = try { cond.get() } catch (e: java.util.concurrent.ExecutionException) { throw e.cause ?: e }
|
| 305 |
+
synchronized(timing) {
|
| 306 |
+
timing.merge("branch_uncond", uS, Double::plus)
|
| 307 |
+
timing.merge("branch_cond", cS, Double::plus)
|
| 308 |
+
}
|
| 309 |
+
return u + c
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
override fun close() {
|
| 313 |
+
pool.shutdown()
|
| 314 |
+
units.forEach { it.close() }
|
| 315 |
+
units.clear()
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
/**
|
| 320 |
+
* 基准(A3):用内置的 fixture(assets/bench,第一步 t=900)跑一次 UNet,与主机 fp32 的输出比较。
|
| 321 |
+
* 合格:没有非有限值、mean|Δ| < 0.01、max|Δ| < 0.1(realme 混合链实测 max 0.0067 / mean 0.0012,CPU 约 1e-6)。
|
| 322 |
+
*/
|
| 323 |
+
fun benchmark(): BenchResult = try {
|
| 324 |
+
val latent = assetFloats("bench/unet0_latent.bin")
|
| 325 |
+
val ref = assetFloats("bench/unet0_noise.bin")
|
| 326 |
+
val t0 = SystemClock.elapsedRealtimeNanos()
|
| 327 |
+
val u = unet ?: openUnet()
|
| 328 |
+
val createS = secs(t0)
|
| 329 |
+
u.run(latent, 900) // 预热
|
| 330 |
+
val t1 = SystemClock.elapsedRealtimeNanos()
|
| 331 |
+
val y = u.run(latent, 900)
|
| 332 |
+
val runS = secs(t1)
|
| 333 |
+
var maxD = 0.0
|
| 334 |
+
var sum = 0.0
|
| 335 |
+
var bad = 0
|
| 336 |
+
for (i in ref.indices) {
|
| 337 |
+
if (!y[i].isFinite()) { bad++; continue }
|
| 338 |
+
val d = abs(y[i].toDouble() - ref[i]); if (d > maxD) maxD = d; sum += d
|
| 339 |
+
}
|
| 340 |
+
val mean = sum / ref.size
|
| 341 |
+
BenchResult(bad == 0 && mean < 0.01 && maxD < 0.1, createS, runS, maxD, mean, if (bad > 0) "$bad 个非有限值" else null)
|
| 342 |
+
} catch (e: Throwable) {
|
| 343 |
+
Log.e(TAG, "benchmark", e)
|
| 344 |
+
BenchResult(false, 0.0, 0.0, 0.0, 0.0, e.toString())
|
| 345 |
+
} finally {
|
| 346 |
+
unetDone()
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
private fun run1(s: OrtSession, feeds: Map<String, OnnxTensor>): FloatArray = try {
|
| 350 |
+
s.run(feeds).use { r ->
|
| 351 |
+
val fb = (r.get(0) as OnnxTensor).floatBuffer
|
| 352 |
+
FloatArray(fb.remaining()).also { fb.get(it) }
|
| 353 |
+
}
|
| 354 |
+
} finally {
|
| 355 |
+
feeds.values.forEach { it.close() }
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
private fun tensor(a: FloatArray, vararg shape: Long): OnnxTensor {
|
| 359 |
+
val bb = ByteBuffer.allocateDirect(a.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer()
|
| 360 |
+
bb.put(a).flip()
|
| 361 |
+
return OnnxTensor.createTensor(env, bb, shape)
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
private fun assetFloats(name: String): FloatArray {
|
| 365 |
+
val bytes = ctx.assets.open(name).use { it.readBytes() }
|
| 366 |
+
val fb: FloatBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer()
|
| 367 |
+
return FloatArray(fb.remaining()).also { fb.get(it) }
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
private fun secs(t0: Long) = (SystemClock.elapsedRealtimeNanos() - t0) / 1e9
|
| 371 |
+
|
| 372 |
+
companion object {
|
| 373 |
+
const val TAG = "Moebius"
|
| 374 |
+
|
| 375 |
+
/** 按优先级排出可用的配置:设备支持且文件齐全;preferred 不为空时把它放在最前面(其余作为回退)。 */
|
| 376 |
+
fun candidates(store: ModelStore, preferred: String?): List<ModelProfile> {
|
| 377 |
+
val ok = store.profiles.filter { Device.supports(it.requires) && store.installed(it) }
|
| 378 |
+
val p = ok.firstOrNull { it.id == preferred } ?: return ok
|
| 379 |
+
return listOf(p) + (ok - p)
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
}
|
app/src/main/kotlin/com/yboom/moebius/Imaging.kt
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import kotlin.math.PI
|
| 4 |
+
import kotlin.math.abs
|
| 5 |
+
import kotlin.math.ceil
|
| 6 |
+
import kotlin.math.exp
|
| 7 |
+
import kotlin.math.max
|
| 8 |
+
import kotlin.math.min
|
| 9 |
+
import kotlin.math.sin
|
| 10 |
+
|
| 11 |
+
/** RGBA 8 位像素(与 Bitmap.copyPixelsToBuffer 的 ARGB_8888 内存布局相同:R、G、B、A)。 */
|
| 12 |
+
class Rgba(val w: Int, val h: Int, val px: ByteArray = ByteArray(w * h * 4)) {
|
| 13 |
+
init { require(px.size == w * h * 4) }
|
| 14 |
+
operator fun get(x: Int, y: Int, c: Int): Int = px[(y * w + x) * 4 + c].toInt() and 0xff
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/** 8 位单通道(蒙版:255 = 修补)。 */
|
| 18 |
+
class Gray(val w: Int, val h: Int, val px: ByteArray = ByteArray(w * h)) {
|
| 19 |
+
init { require(px.size == w * h) }
|
| 20 |
+
operator fun get(x: Int, y: Int): Int = px[y * w + x].toInt() and 0xff
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/** 照片中送进模型的区域,以及它在 512 画布中的位置。 */
|
| 24 |
+
data class Window(val sx: Int, val sy: Int, val sw: Int, val sh: Int, val dx: Int, val dy: Int, val dw: Int, val dh: Int)
|
| 25 |
+
|
| 26 |
+
/** 外接框,右下不含。 */
|
| 27 |
+
data class Box(val x0: Int, val y0: Int, val x1: Int, val y1: Int)
|
| 28 |
+
|
| 29 |
+
/**
|
| 30 |
+
* 图像前后处理(TODO A2、A10):imaging.ts 的移植(CHW 转换、蒙版二值化与降采样、3 px 高斯羽化贴回),
|
| 31 |
+
* 再加上原生应用的改进:以蒙版为中心截正方形窗口(crop)送进模型,结果放大回原图、只在蒙版内羽化替换。
|
| 32 |
+
* 缩放与 tools/photo_test.py 用的 PIL 相同(同样的卷积核和 22 位定点数),所以画布与主机基线几乎逐位一致。不依赖 Android。
|
| 33 |
+
*/
|
| 34 |
+
object Imaging {
|
| 35 |
+
// ---------------- 窗口 ----------------
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* letterbox(web 版 toSquareCanvas):整张照片等比缩到 512 以内、黑边居中。
|
| 39 |
+
* crop(默认,A10):以蒙版外接框为中心的正方形,边长 = max(外接框长边 × 2.5, 512),不超出照片。与 photo_test.py window() 相同(round 为四舍六入五成双)。
|
| 40 |
+
*/
|
| 41 |
+
fun window(mode: String, w: Int, h: Int, mask: Box?): Window {
|
| 42 |
+
if (mode == "letterbox" || mask == null) {
|
| 43 |
+
val s = min(Moebius.IMG.toDouble() / w, Moebius.IMG.toDouble() / h)
|
| 44 |
+
val dw = Math.rint(w * s).toInt()
|
| 45 |
+
val dh = Math.rint(h * s).toInt()
|
| 46 |
+
return Window(0, 0, w, h, (Moebius.IMG - dw) / 2, (Moebius.IMG - dh) / 2, dw, dh)
|
| 47 |
+
}
|
| 48 |
+
require(mode == "crop") { "未知的模式 $mode" }
|
| 49 |
+
val side = minOf(max(ceil(max(mask.x1 - mask.x0, mask.y1 - mask.y0) * 2.5).toInt(), Moebius.IMG), w, h)
|
| 50 |
+
val cx = (mask.x0 + mask.x1) / 2.0
|
| 51 |
+
val cy = (mask.y0 + mask.y1) / 2.0
|
| 52 |
+
val sx = Math.rint(cx - side / 2.0).toInt().coerceIn(0, w - side)
|
| 53 |
+
val sy = Math.rint(cy - side / 2.0).toInt().coerceIn(0, h - side)
|
| 54 |
+
return Window(sx, sy, side, side, 0, 0, Moebius.IMG, Moebius.IMG)
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
fun bbox(m: Gray, threshold: Int = 1): Box? {
|
| 58 |
+
var x0 = m.w; var y0 = m.h; var x1 = 0; var y1 = 0
|
| 59 |
+
for (y in 0 until m.h) {
|
| 60 |
+
val row = y * m.w
|
| 61 |
+
for (x in 0 until m.w) if ((m.px[row + x].toInt() and 0xff) >= threshold) {
|
| 62 |
+
if (x < x0) x0 = x
|
| 63 |
+
if (x >= x1) x1 = x + 1
|
| 64 |
+
if (y < y0) y0 = y
|
| 65 |
+
y1 = y + 1
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
return if (x1 > x0 && y1 > y0) Box(x0, y0, x1, y1) else null
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/** 照片 → 512 画布(Lanczos),蒙版 → 512(BOX 缩放后 ≥128 二值化为 0/255)。 */
|
| 72 |
+
fun canvas(photo: Rgba, mask: Gray, win: Window): Pair<Rgba, Gray> {
|
| 73 |
+
val img = Rgba(Moebius.IMG, Moebius.IMG)
|
| 74 |
+
for (i in 3 until img.px.size step 4) img.px[i] = -1
|
| 75 |
+
val part = resample(photo.px, photo.w, photo.h, 4, 3, win.sx, win.sy, win.sw, win.sh, win.dw, win.dh, Filter.LANCZOS)
|
| 76 |
+
blit(part, win.dw, win.dh, 4, img.px, img.w, win.dx, win.dy)
|
| 77 |
+
val m = Gray(Moebius.IMG, Moebius.IMG)
|
| 78 |
+
val mp = resample(mask.px, mask.w, mask.h, 1, 1, win.sx, win.sy, win.sw, win.sh, win.dw, win.dh, Filter.BOX)
|
| 79 |
+
for (i in mp.indices) mp[i] = if ((mp[i].toInt() and 0xff) >= 128) -1 else 0
|
| 80 |
+
blit(mp, win.dw, win.dh, 1, m.px, m.w, win.dx, win.dy)
|
| 81 |
+
return img to m
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
private fun blit(src: ByteArray, sw: Int, sh: Int, ch: Int, dst: ByteArray, dstW: Int, dx: Int, dy: Int) {
|
| 85 |
+
for (y in 0 until sh) System.arraycopy(src, y * sw * ch, dst, ((dy + y) * dstW + dx) * ch, sw * ch)
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// ---------------- 张量 ----------------
|
| 89 |
+
|
| 90 |
+
/** 512×512 RGBA → (1,3,512,512),[-1,1](x/255·2−1,float32 运算,与 moebius.py image_to_chw 相同)。 */
|
| 91 |
+
fun toChw(img: Rgba): FloatArray {
|
| 92 |
+
val n = img.w * img.h
|
| 93 |
+
val out = FloatArray(3 * n)
|
| 94 |
+
for (p in 0 until n) for (c in 0 until 3) {
|
| 95 |
+
out[c * n + p] = (img.px[p * 4 + c].toInt() and 0xff).toFloat() / 255f * 2f - 1f
|
| 96 |
+
}
|
| 97 |
+
return out
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/** 蒙版二值化:≥128 为 1(要修补)。 */
|
| 101 |
+
fun maskBinary(m: Gray): FloatArray = FloatArray(m.px.size) { if ((m.px[it].toInt() and 0xff) >= 128) 1f else 0f }
|
| 102 |
+
|
| 103 |
+
fun maskedChw(chw: FloatArray, mask: FloatArray): FloatArray {
|
| 104 |
+
val n = mask.size
|
| 105 |
+
return FloatArray(chw.size) { chw[it] * (1f - mask[it % n]) }
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/** 每个 8×8 块取左上角(PyTorch nearest)→ (1,1,64,64)。 */
|
| 109 |
+
fun maskToLatent(mask: FloatArray): FloatArray =
|
| 110 |
+
FloatArray(Moebius.PLANE) { mask[(it / Moebius.LAT) * 8 * Moebius.IMG + (it % Moebius.LAT) * 8] }
|
| 111 |
+
|
| 112 |
+
/** (1,3,512,512) → RGBA:clip((x+1)/2)·255 四舍六入五成双(np.round)。 */
|
| 113 |
+
fun fromChw(chw: FloatArray, w: Int = Moebius.IMG, h: Int = Moebius.IMG): Rgba {
|
| 114 |
+
val n = w * h
|
| 115 |
+
val img = Rgba(w, h)
|
| 116 |
+
for (p in 0 until n) {
|
| 117 |
+
for (c in 0 until 3) {
|
| 118 |
+
val v = ((chw[c * n + p].toDouble() + 1) / 2).coerceIn(0.0, 1.0)
|
| 119 |
+
img.px[p * 4 + c] = Math.rint(v * 255).toInt().toByte()
|
| 120 |
+
}
|
| 121 |
+
img.px[p * 4 + 3] = -1
|
| 122 |
+
}
|
| 123 |
+
return img
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
// ---------------- 贴回 ----------------
|
| 127 |
+
|
| 128 |
+
/** 512 画布上的贴回(web 版):result·blur(mask) + orig·(1−blur(mask)),σ = 3 px。 */
|
| 129 |
+
fun pasteBack512(decoded: Rgba, orig: Rgba, mask: Gray, sigma: Double = 3.0): Rgba {
|
| 130 |
+
val m = gaussianBlur(mask, sigma)
|
| 131 |
+
val out = Rgba(decoded.w, decoded.h)
|
| 132 |
+
for (p in 0 until decoded.w * decoded.h) {
|
| 133 |
+
val a = (m.px[p].toInt() and 0xff) / 255.0
|
| 134 |
+
for (c in 0 until 3) {
|
| 135 |
+
val r = decoded.px[p * 4 + c].toInt() and 0xff
|
| 136 |
+
val o = orig.px[p * 4 + c].toInt() and 0xff
|
| 137 |
+
out.px[p * 4 + c] = Math.rint(r * a + o * (1 - a)).toInt().toByte()
|
| 138 |
+
}
|
| 139 |
+
out.px[p * 4 + 3] = -1
|
| 140 |
+
}
|
| 141 |
+
return out
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/**
|
| 145 |
+
* 全分辨率合成(A10):512 的解码结果中照片所在的部分放大回窗口大小(Lanczos),按全分辨率蒙版的高斯羽化(σ = 3 px × 放大倍数)
|
| 146 |
+
* 只在窗口内替换,其余像素不动。直接改写 [photo]。pasteBack = false 时整个窗口替换为模型输出(web 版不勾“贴回”)。
|
| 147 |
+
*/
|
| 148 |
+
fun composeFull(photo: Rgba, decoded: Rgba, win: Window, mask: Gray, pasteBack: Boolean = true) {
|
| 149 |
+
val up = resample(decoded.px, decoded.w, decoded.h, 4, 3, win.dx, win.dy, win.dw, win.dh, win.sw, win.sh, Filter.LANCZOS)
|
| 150 |
+
if (!pasteBack) {
|
| 151 |
+
for (y in 0 until win.sh) for (x in 0 until win.sw) for (c in 0 until 3) {
|
| 152 |
+
photo.px[((win.sy + y) * photo.w + win.sx + x) * 4 + c] = up[(y * win.sw + x) * 4 + c]
|
| 153 |
+
}
|
| 154 |
+
return
|
| 155 |
+
}
|
| 156 |
+
val sigma = 3.0 * win.sw / win.dw
|
| 157 |
+
val box = bbox(mask) ?: return
|
| 158 |
+
val r = ceil(3 * sigma).toInt()
|
| 159 |
+
// 只在蒙版外接框 + 3σ 内做模糊(其余位置羽化值为 0);模糊的输入再多取 3σ,边缘与对整张蒙版模糊相同
|
| 160 |
+
val rx0 = max(box.x0 - r, win.sx); val ry0 = max(box.y0 - r, win.sy)
|
| 161 |
+
val rx1 = min(box.x1 + r, win.sx + win.sw); val ry1 = min(box.y1 + r, win.sy + win.sh)
|
| 162 |
+
if (rx1 <= rx0 || ry1 <= ry0) return
|
| 163 |
+
val bx0 = max(box.x0 - 2 * r, 0); val by0 = max(box.y0 - 2 * r, 0)
|
| 164 |
+
val bx1 = min(box.x1 + 2 * r, mask.w); val by1 = min(box.y1 + 2 * r, mask.h)
|
| 165 |
+
val sub = Gray(bx1 - bx0, by1 - by0)
|
| 166 |
+
for (y in by0 until by1) System.arraycopy(mask.px, y * mask.w + bx0, sub.px, (y - by0) * sub.w, sub.w)
|
| 167 |
+
val f = gaussianBlur(sub, sigma)
|
| 168 |
+
for (y in ry0 until ry1) for (x in rx0 until rx1) {
|
| 169 |
+
val m = f.px[(y - by0) * f.w + (x - bx0)].toInt() and 0xff
|
| 170 |
+
if (m == 0) continue
|
| 171 |
+
val pi = (y * photo.w + x) * 4
|
| 172 |
+
val ui = ((y - win.sy) * win.sw + (x - win.sx)) * 4
|
| 173 |
+
for (c in 0 until 3) {
|
| 174 |
+
val a = up[ui + c].toInt() and 0xff
|
| 175 |
+
val b = photo.px[pi + c].toInt() and 0xff
|
| 176 |
+
photo.px[pi + c] = div255(a * m + b * (255 - m)).toByte() // PIL paste 的 BLEND
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
private fun div255(v: Int): Int { val t = v + 128; return (t + (t shr 8)) shr 8 }
|
| 182 |
+
|
| 183 |
+
/** 可分离的高斯模糊(半径 3σ,边缘取最近像素),结果四舍五入回 8 位。 */
|
| 184 |
+
fun gaussianBlur(m: Gray, sigma: Double): Gray {
|
| 185 |
+
val r = ceil(3 * sigma).toInt()
|
| 186 |
+
val k = DoubleArray(2 * r + 1) { exp(-((it - r) * (it - r)) / (2 * sigma * sigma)) }
|
| 187 |
+
val ks = k.sum()
|
| 188 |
+
for (i in k.indices) k[i] /= ks
|
| 189 |
+
val w = m.w; val h = m.h
|
| 190 |
+
val tmp = FloatArray(w * h)
|
| 191 |
+
for (y in 0 until h) {
|
| 192 |
+
val row = y * w
|
| 193 |
+
for (x in 0 until w) {
|
| 194 |
+
var s = 0.0
|
| 195 |
+
for (j in -r..r) s += k[j + r] * (m.px[row + (x + j).coerceIn(0, w - 1)].toInt() and 0xff)
|
| 196 |
+
tmp[row + x] = s.toFloat()
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
val out = Gray(w, h)
|
| 200 |
+
for (y in 0 until h) for (x in 0 until w) {
|
| 201 |
+
var s = 0.0
|
| 202 |
+
for (j in -r..r) s += k[j + r] * tmp[(y + j).coerceIn(0, h - 1) * w + x]
|
| 203 |
+
out.px[y * w + x] = Math.rint(s).toInt().coerceIn(0, 255).toByte()
|
| 204 |
+
}
|
| 205 |
+
return out
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
// ---------------- 缩放(PIL Resample.c 的移植) ----------------
|
| 209 |
+
|
| 210 |
+
enum class Filter(val support: Double) {
|
| 211 |
+
BOX(0.5), BILINEAR(1.0), LANCZOS(3.0);
|
| 212 |
+
|
| 213 |
+
fun f(x: Double): Double = when (this) {
|
| 214 |
+
BOX -> if (x > -0.5 && x <= 0.5) 1.0 else 0.0
|
| 215 |
+
BILINEAR -> { val a = abs(x); if (a < 1) 1 - a else 0.0 }
|
| 216 |
+
LANCZOS -> if (x > -3.0 && x < 3.0) sinc(x) * sinc(x / 3) else 0.0
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
private fun sinc(x: Double): Double { if (x == 0.0) return 1.0; val a = x * PI; return sin(a) / a }
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
private const val PRECISION_BITS = 32 - 8 - 2
|
| 223 |
+
|
| 224 |
+
private class Coeffs(val bounds: IntArray, val k: IntArray, val ksize: Int)
|
| 225 |
+
|
| 226 |
+
private fun coeffs(inSize: Int, outSize: Int, filter: Filter): Coeffs {
|
| 227 |
+
val scale = inSize.toDouble() / outSize
|
| 228 |
+
val filterscale = max(scale, 1.0)
|
| 229 |
+
val support = filter.support * filterscale
|
| 230 |
+
val ksize = ceil(support).toInt() * 2 + 1
|
| 231 |
+
val kk = IntArray(outSize * ksize)
|
| 232 |
+
val bounds = IntArray(outSize * 2)
|
| 233 |
+
val pre = DoubleArray(ksize)
|
| 234 |
+
val ss = 1.0 / filterscale
|
| 235 |
+
for (xx in 0 until outSize) {
|
| 236 |
+
val center = (xx + 0.5) * scale
|
| 237 |
+
val xmin = maxOf((center - support + 0.5).toInt(), 0)
|
| 238 |
+
val xmax = minOf((center + support + 0.5).toInt(), inSize) - xmin
|
| 239 |
+
var ww = 0.0
|
| 240 |
+
for (x in 0 until xmax) { val w = filter.f((x + xmin - center + 0.5) * ss); pre[x] = w; ww += w }
|
| 241 |
+
for (x in 0 until ksize) {
|
| 242 |
+
val v = if (x < xmax && ww != 0.0) pre[x] / ww else 0.0
|
| 243 |
+
val f = v * (1 shl PRECISION_BITS)
|
| 244 |
+
kk[xx * ksize + x] = (if (v < 0) f - 0.5 else f + 0.5).toInt()
|
| 245 |
+
}
|
| 246 |
+
bounds[xx * 2] = xmin
|
| 247 |
+
bounds[xx * 2 + 1] = xmax
|
| 248 |
+
}
|
| 249 |
+
return Coeffs(bounds, kk, ksize)
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
private fun clip8(v: Long): Byte = (v shr PRECISION_BITS).coerceIn(0, 255).toInt().toByte()
|
| 253 |
+
|
| 254 |
+
/**
|
| 255 |
+
* 把源图 (w,h,ch 通道) 中的区域 (sx,sy,sw,sh) 缩放到 dw×dh,与 PIL 的 `img.crop(box).resize((dw,dh), filter)` 相同:
|
| 256 |
+
* 先水平、后垂直,两遍之间存 8 位。只处理前 [colors] 个通道,其余通道(alpha)填 255。
|
| 257 |
+
*/
|
| 258 |
+
fun resample(src: ByteArray, w: Int, h: Int, ch: Int, colors: Int, sx: Int, sy: Int, sw: Int, sh: Int, dw: Int, dh: Int, filter: Filter): ByteArray {
|
| 259 |
+
require(sx >= 0 && sy >= 0 && sx + sw <= w && sy + sh <= h) { "区域超出图像" }
|
| 260 |
+
val horiz = if (sw == dw) null else coeffs(sw, dw, filter)
|
| 261 |
+
val vert = if (sh == dh) null else coeffs(sh, dh, filter)
|
| 262 |
+
// 水平这一遍只处理垂直卷积用得到的行
|
| 263 |
+
val rowFirst = vert?.bounds?.get(0) ?: 0
|
| 264 |
+
val rowLast = if (vert != null) vert.bounds[(dh - 1) * 2] + vert.bounds[(dh - 1) * 2 + 1] else sh
|
| 265 |
+
val th = rowLast - rowFirst
|
| 266 |
+
val tmp = ByteArray(dw * th * ch)
|
| 267 |
+
val half = 1L shl (PRECISION_BITS - 1)
|
| 268 |
+
for (y in 0 until th) {
|
| 269 |
+
val srow = (sy + rowFirst + y) * w + sx
|
| 270 |
+
if (horiz == null) { System.arraycopy(src, srow * ch, tmp, y * dw * ch, dw * ch); continue }
|
| 271 |
+
for (xx in 0 until dw) {
|
| 272 |
+
val o = (y * dw + xx) * ch
|
| 273 |
+
val xmin = horiz.bounds[xx * 2]; val xmax = horiz.bounds[xx * 2 + 1]; val kb = xx * horiz.ksize
|
| 274 |
+
for (c in 0 until ch) {
|
| 275 |
+
if (c >= colors) { tmp[o + c] = -1; continue }
|
| 276 |
+
var s = half
|
| 277 |
+
for (x in 0 until xmax) s += (src[(srow + xmin + x) * ch + c].toInt() and 0xff).toLong() * horiz.k[kb + x]
|
| 278 |
+
tmp[o + c] = clip8(s)
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
if (vert == null) return tmp
|
| 283 |
+
val out = ByteArray(dw * dh * ch)
|
| 284 |
+
for (yy in 0 until dh) {
|
| 285 |
+
val ymin = vert.bounds[yy * 2] - rowFirst; val ymax = vert.bounds[yy * 2 + 1]; val kb = yy * vert.ksize
|
| 286 |
+
for (x in 0 until dw) {
|
| 287 |
+
val o = (yy * dw + x) * ch
|
| 288 |
+
for (c in 0 until ch) {
|
| 289 |
+
if (c >= colors) { out[o + c] = -1; continue }
|
| 290 |
+
var s = half
|
| 291 |
+
for (y in 0 until ymax) s += (tmp[((ymin + y) * dw + x) * ch + c].toInt() and 0xff).toLong() * vert.k[kb + y]
|
| 292 |
+
out[o + c] = clip8(s)
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
return out
|
| 297 |
+
}
|
| 298 |
+
}
|
app/src/main/kotlin/com/yboom/moebius/Inpainter.kt
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import android.content.Context
|
| 4 |
+
import android.content.Intent
|
| 5 |
+
import android.graphics.Bitmap
|
| 6 |
+
import android.graphics.BitmapFactory
|
| 7 |
+
import android.net.Uri
|
| 8 |
+
import android.os.PowerManager
|
| 9 |
+
import android.os.SystemClock
|
| 10 |
+
import android.util.Log
|
| 11 |
+
import org.json.JSONArray
|
| 12 |
+
import org.json.JSONObject
|
| 13 |
+
import java.io.File
|
| 14 |
+
import java.nio.ByteBuffer
|
| 15 |
+
import java.nio.ByteOrder
|
| 16 |
+
|
| 17 |
+
/** 一次修补的参数。经 Intent extra 在界面、服务和测试脚本之间传递(键名见 [toIntent])。 */
|
| 18 |
+
data class InpaintRequest(
|
| 19 |
+
val image: Uri,
|
| 20 |
+
/** 与照片同尺寸的蒙版 PNG(白 = 修补);或者 [maskRect](原图坐标 x0,y0,x1,y1,右下不含)。 */
|
| 21 |
+
val maskFile: String? = null,
|
| 22 |
+
val maskRect: IntArray? = null,
|
| 23 |
+
val steps: Int = 20,
|
| 24 |
+
val guidance: Float = 2f,
|
| 25 |
+
val seed: Int = 0,
|
| 26 |
+
val pasteBack: Boolean = true,
|
| 27 |
+
/** crop(默认,A10)或 letterbox(web 版)。 */
|
| 28 |
+
val mode: String = "crop",
|
| 29 |
+
/** 优先使用的模型配置;null = 自动(按 models.json 的顺序)。 */
|
| 30 |
+
val profile: String? = null,
|
| 31 |
+
/** 测试:把中间结果写到这个目录(debug 构建,A7)。 */
|
| 32 |
+
val outDir: String? = null,
|
| 33 |
+
val tag: String = "run",
|
| 34 |
+
val saveToGallery: Boolean = true,
|
| 35 |
+
) {
|
| 36 |
+
fun toIntent(i: Intent): Intent = i.apply {
|
| 37 |
+
putExtra("image", image.toString())
|
| 38 |
+
maskFile?.let { putExtra("mask", it) }
|
| 39 |
+
maskRect?.let { putExtra("mask_rect", it.joinToString(",")) }
|
| 40 |
+
putExtra("steps", steps); putExtra("guidance", guidance); putExtra("seed", seed)
|
| 41 |
+
putExtra("paste_back", pasteBack); putExtra("mode", mode); putExtra("tag", tag); putExtra("save", saveToGallery)
|
| 42 |
+
profile?.let { putExtra("profile", it) }
|
| 43 |
+
outDir?.let { putExtra("out", it) }
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
companion object {
|
| 47 |
+
/** adb 传进来的参数:数值可以是字符串(am start --es),也可以是 --ei / --ef / --ez。 */
|
| 48 |
+
@Suppress("DEPRECATION")
|
| 49 |
+
fun from(i: Intent): InpaintRequest {
|
| 50 |
+
fun s(k: String) = i.extras?.get(k)?.toString()
|
| 51 |
+
val img = s("image") ?: error("缺少 image")
|
| 52 |
+
return InpaintRequest(
|
| 53 |
+
image = if (img.startsWith("/")) Uri.fromFile(File(img)) else Uri.parse(img),
|
| 54 |
+
maskFile = s("mask"),
|
| 55 |
+
maskRect = s("mask_rect")?.split(',')?.map { it.trim().toInt() }?.toIntArray()?.also { require(it.size == 4) { "mask_rect 要 4 个数" } },
|
| 56 |
+
steps = s("steps")?.toInt() ?: 20,
|
| 57 |
+
guidance = s("guidance")?.toFloat() ?: 2f,
|
| 58 |
+
seed = s("seed")?.toInt() ?: 0,
|
| 59 |
+
pasteBack = s("paste_back")?.toBoolean() ?: true,
|
| 60 |
+
mode = s("mode") ?: "crop",
|
| 61 |
+
profile = s("profile")?.takeIf { it.isNotEmpty() && it != "auto" },
|
| 62 |
+
outDir = s("out"),
|
| 63 |
+
tag = s("tag") ?: "run",
|
| 64 |
+
saveToGallery = s("save")?.toBoolean() ?: true,
|
| 65 |
+
)
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
data class InpaintResult(val resultFile: File, val galleryUri: Uri?, val profile: String, val totalS: Double, val meta: JSONObject)
|
| 71 |
+
|
| 72 |
+
/**
|
| 73 |
+
* 一次修补(A5 的服务、A7 的测试共用):解码照片和蒙版 → 窗口 → 512 画布 → Pipeline(Engine)→ 全分辨率合成 → 保存。
|
| 74 |
+
* 模型配置按 Engine.candidates 的顺序尝试,建会话或运行失败(例如 HTP 不可用)时换下一个并记录原因。
|
| 75 |
+
*/
|
| 76 |
+
class Inpainter(private val ctx: Context, private val store: ModelStore, private val engineOpt: EngineOptions) {
|
| 77 |
+
fun interface Progress { fun report(stage: String, step: Int, total: Int, etaS: Double) }
|
| 78 |
+
|
| 79 |
+
fun run(req: InpaintRequest, progress: Progress, cancelled: () -> Boolean): InpaintResult {
|
| 80 |
+
val t0 = SystemClock.elapsedRealtimeNanos()
|
| 81 |
+
val meta = JSONObject().put("tag", req.tag).put("steps", req.steps).put("guidance", req.guidance.toDouble()).put("seed", req.seed)
|
| 82 |
+
.put("mode", req.mode).put("paste_back", req.pasteBack).put("soc", Device.soc).put("big_cores", Device.bigCores)
|
| 83 |
+
val timing = JSONObject()
|
| 84 |
+
meta.put("timing_s", timing)
|
| 85 |
+
progress.report("prepare", 0, req.steps, -1.0)
|
| 86 |
+
|
| 87 |
+
var t = SystemClock.elapsedRealtimeNanos()
|
| 88 |
+
val bmp = BitmapIo.decode(ctx, req.image)
|
| 89 |
+
val cs = bmp.colorSpace
|
| 90 |
+
val photo = BitmapIo.toRgba(bmp)
|
| 91 |
+
bmp.recycle()
|
| 92 |
+
meta.put("source_size", JSONArray(listOf(photo.w, photo.h))).put("color_space", cs?.name)
|
| 93 |
+
val mask = loadMask(req, photo.w, photo.h)
|
| 94 |
+
val box = Imaging.bbox(mask) ?: error(ctx.getString(R.string.err_mask_empty))
|
| 95 |
+
val win = Imaging.window(req.mode, photo.w, photo.h, box)
|
| 96 |
+
val (canvas, m512) = Imaging.canvas(photo, mask, win)
|
| 97 |
+
timing.put("decode_prepare", secs(t))
|
| 98 |
+
meta.put("window", JSONArray(with(win) { listOf(sx, sy, sw, sh) })).put("canvas_rect", JSONArray(with(win) { listOf(dx, dy, dw, dh) }))
|
| 99 |
+
meta.put("mask_bbox", JSONArray(with(box) { listOf(x0, y0, x1, y1) }))
|
| 100 |
+
val chw = Imaging.toChw(canvas)
|
| 101 |
+
val mbin = Imaging.maskBinary(m512)
|
| 102 |
+
meta.put("mask_pixels_512", mbin.count { it > 0 })
|
| 103 |
+
if (meta.getInt("mask_pixels_512") == 0) error(ctx.getString(R.string.err_mask_too_small))
|
| 104 |
+
|
| 105 |
+
val candidates = Engine.candidates(store, req.profile)
|
| 106 |
+
if (candidates.isEmpty()) error(ctx.getString(R.string.err_no_models))
|
| 107 |
+
val notes = JSONArray()
|
| 108 |
+
meta.put("notes", notes)
|
| 109 |
+
var out: Pipeline.Output? = null
|
| 110 |
+
var used: Engine? = null
|
| 111 |
+
// 发热(P5):每个 UNet 步之后记录热状态和 10 秒后的预测余量(getThermalHeadroom 每秒最多调用一次,每步 ≥ 1.5 s)
|
| 112 |
+
val pm = ctx.getSystemService(PowerManager::class.java)
|
| 113 |
+
val thermalStatus = JSONArray()
|
| 114 |
+
val headroom = JSONArray()
|
| 115 |
+
meta.put("thermal_status_start", pm.currentThermalStatus)
|
| 116 |
+
for (p in candidates) {
|
| 117 |
+
val engine = Engine(ctx, store, p, engineOpt)
|
| 118 |
+
engine.log = { msg -> Log.i(TAG, msg); notes.put(msg) }
|
| 119 |
+
try {
|
| 120 |
+
var stepStart = 0L
|
| 121 |
+
out = Pipeline(engine).run(chw, mbin, Pipeline.Params(req.steps, req.guidance, req.seed), { stage, step, total ->
|
| 122 |
+
if (stage == "unet" && step == 1) stepStart = SystemClock.elapsedRealtimeNanos()
|
| 123 |
+
if (stage == "unet" && step > 1) {
|
| 124 |
+
thermalStatus.put(pm.currentThermalStatus)
|
| 125 |
+
if (android.os.Build.VERSION.SDK_INT >= 30) pm.getThermalHeadroom(10).let { headroom.put(if (it.isNaN()) JSONObject.NULL else Math.round(it * 1000) / 1000.0) }
|
| 126 |
+
}
|
| 127 |
+
val eta = if (stage == "unet" && step > 1) secs(stepStart) / (step - 1) * (total - step + 1) else -1.0
|
| 128 |
+
progress.report(stage, step, total, eta)
|
| 129 |
+
}, cancelled)
|
| 130 |
+
used = engine
|
| 131 |
+
break
|
| 132 |
+
} catch (e: CancelledException) {
|
| 133 |
+
engine.close()
|
| 134 |
+
throw e
|
| 135 |
+
} catch (e: Throwable) {
|
| 136 |
+
// OOM 也在这里:换下一个配置(例如 HTP 失败时回退 CPU)
|
| 137 |
+
Log.e(TAG, "配置 ${p.id} 失败", e)
|
| 138 |
+
notes.put("${p.id} 失败:$e")
|
| 139 |
+
engine.close()
|
| 140 |
+
System.gc()
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
val o = out ?: error(ctx.getString(R.string.err_all_failed, (0 until notes.length()).joinToString("; ") { notes.getString(it) }))
|
| 144 |
+
val engine = used!!
|
| 145 |
+
engine.close()
|
| 146 |
+
meta.put("profile", engine.profile.id).put("threads", engine.threads).put("htp_perf", engineOpt.htpPerf)
|
| 147 |
+
meta.put("timesteps", JSONArray(o.timesteps.toList()))
|
| 148 |
+
meta.put("unet_step_s", JSONArray(o.stepSeconds.map { Math.round(it * 1000) / 1000.0 }))
|
| 149 |
+
meta.put("thermal_status", thermalStatus).put("thermal_headroom_10s", headroom).put("thermal_status_end", pm.currentThermalStatus)
|
| 150 |
+
engine.timing.forEach { (k, v) -> timing.put(k, Math.round(v * 1000) / 1000.0) }
|
| 151 |
+
timing.put("unet_total", o.stepSeconds.sum())
|
| 152 |
+
|
| 153 |
+
progress.report("compose", req.steps, req.steps, -1.0)
|
| 154 |
+
t = SystemClock.elapsedRealtimeNanos()
|
| 155 |
+
val decoded = Imaging.fromChw(o.decoded)
|
| 156 |
+
val orig = if (req.outDir != null) photo.px.copyOf() else null
|
| 157 |
+
Imaging.composeFull(photo, decoded, win, mask, req.pasteBack)
|
| 158 |
+
val full = BitmapIo.fromRgba(photo, cs)
|
| 159 |
+
timing.put("compose", secs(t))
|
| 160 |
+
if (orig != null) meta.put("check", outsideCheck(orig, photo, mask, win))
|
| 161 |
+
|
| 162 |
+
t = SystemClock.elapsedRealtimeNanos()
|
| 163 |
+
val resultFile = File(ctx.cacheDir, "result.jpg")
|
| 164 |
+
BitmapIo.save(full, resultFile, Bitmap.CompressFormat.JPEG, 95)
|
| 165 |
+
val gallery = if (req.saveToGallery) BitmapIo.saveToGallery(ctx, full, "moebius_${System.currentTimeMillis()}.jpg") else null
|
| 166 |
+
req.outDir?.let { writeTestOutputs(File(it), o, decoded, canvas, m512, full) }
|
| 167 |
+
full.recycle()
|
| 168 |
+
timing.put("save", secs(t))
|
| 169 |
+
val total = secs(t0)
|
| 170 |
+
timing.put("total", total)
|
| 171 |
+
// 峰值内存(12 MP 照片 + 模型,A9)
|
| 172 |
+
runCatching { File("/proc/self/status").readLines().filter { it.startsWith("VmHWM") || it.startsWith("VmRSS") }.forEach { meta.put(it.substringBefore(':'), it.substringAfter(':').trim()) } }
|
| 173 |
+
req.outDir?.let { File(it, "meta.json").writeText(meta.toString(2)) }
|
| 174 |
+
return InpaintResult(resultFile, gallery, engine.profile.id, total, meta)
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
private fun loadMask(req: InpaintRequest, w: Int, h: Int): Gray {
|
| 178 |
+
val m = Gray(w, h)
|
| 179 |
+
req.maskRect?.let { r ->
|
| 180 |
+
for (y in r[1].coerceIn(0, h) until r[3].coerceIn(0, h)) java.util.Arrays.fill(m.px, y * w + r[0].coerceIn(0, w), y * w + r[2].coerceIn(0, w), (-1).toByte())
|
| 181 |
+
return m
|
| 182 |
+
}
|
| 183 |
+
val f = req.maskFile ?: error("缺少蒙版")
|
| 184 |
+
val b = BitmapFactory.decodeFile(f) ?: error("无法读取蒙版 $f")
|
| 185 |
+
// 界面按预览图的笔画画出原图尺寸的蒙版;尺寸不一致(例如 EXIF 方向的处理不同)时按比例缩放,长宽比必须相同
|
| 186 |
+
require(kotlin.math.abs(b.width.toDouble() / b.height - w.toDouble() / h) < 0.01) { "蒙版 ${b.width}×${b.height} 与照片 $w×$h 的长宽比不一致" }
|
| 187 |
+
val sb = if (b.width == w && b.height == h) b else Bitmap.createScaledBitmap(b, w, h, true).also { b.recycle() }
|
| 188 |
+
return BitmapIo.toGray(sb).also { sb.recycle() }
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/** 测试(A9):蒙版外接框 + 羽化宽度(3σ)之外的像素必须逐位不变。 */
|
| 192 |
+
private fun outsideCheck(orig: ByteArray, now: Rgba, mask: Gray, win: Window): JSONObject {
|
| 193 |
+
val box = Imaging.bbox(mask)!!
|
| 194 |
+
val r = kotlin.math.ceil(3 * 3.0 * win.sw / win.dw).toInt()
|
| 195 |
+
var changedFar = 0
|
| 196 |
+
var changedOutsideMask = 0
|
| 197 |
+
for (y in 0 until now.h) for (x in 0 until now.w) {
|
| 198 |
+
val i = (y * now.w + x) * 4
|
| 199 |
+
if (orig[i] == now.px[i] && orig[i + 1] == now.px[i + 1] && orig[i + 2] == now.px[i + 2]) continue
|
| 200 |
+
if (mask[x, y] == 0) changedOutsideMask++
|
| 201 |
+
if (x < box.x0 - r || x >= box.x1 + r || y < box.y0 - r || y >= box.y1 + r) changedFar++
|
| 202 |
+
}
|
| 203 |
+
return JSONObject().put("changed_beyond_feather", changedFar).put("changed_in_feather_outside_mask", changedOutsideMask).put("feather_px", r)
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
private fun writeTestOutputs(dir: File, o: Pipeline.Output, decoded: Rgba, canvas: Rgba, m512: Gray, full: Bitmap) {
|
| 207 |
+
dir.mkdirs()
|
| 208 |
+
val bb = ByteBuffer.allocate(o.finalLatents.size * 4).order(ByteOrder.LITTLE_ENDIAN)
|
| 209 |
+
bb.asFloatBuffer().put(o.finalLatents)
|
| 210 |
+
File(dir, "final_latents.bin").writeBytes(bb.array())
|
| 211 |
+
BitmapIo.save(BitmapIo.fromRgba(decoded), File(dir, "decoded.png"))
|
| 212 |
+
BitmapIo.save(BitmapIo.fromRgba(canvas), File(dir, "image.png"))
|
| 213 |
+
BitmapIo.save(BitmapIo.fromGray(m512), File(dir, "mask.png"))
|
| 214 |
+
BitmapIo.save(BitmapIo.fromRgba(Imaging.pasteBack512(decoded, canvas, m512)), File(dir, "result.png"))
|
| 215 |
+
BitmapIo.save(full, File(dir, "result_full.png"))
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
private fun secs(t0: Long) = (SystemClock.elapsedRealtimeNanos() - t0) / 1e9
|
| 219 |
+
|
| 220 |
+
companion object { const val TAG = "Moebius" }
|
| 221 |
+
}
|
app/src/main/kotlin/com/yboom/moebius/ModelStore.kt
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import android.content.Context
|
| 4 |
+
import android.net.Uri
|
| 5 |
+
import android.provider.OpenableColumns
|
| 6 |
+
import android.util.Log
|
| 7 |
+
import org.json.JSONObject
|
| 8 |
+
import java.io.File
|
| 9 |
+
import java.io.InputStream
|
| 10 |
+
import java.net.HttpURLConnection
|
| 11 |
+
import java.net.URL
|
| 12 |
+
import java.security.MessageDigest
|
| 13 |
+
|
| 14 |
+
/** models.json(tools/stage-app-assets.py)中的一套模型配置。 */
|
| 15 |
+
data class ModelProfile(
|
| 16 |
+
val id: String,
|
| 17 |
+
val label: String,
|
| 18 |
+
/** qnn-htp-v79:需要 HTP v79(SM8750);qnn-gpu-htp-v79:另外要有 QNN GPU(OpenCL);cpu:任何设备。 */
|
| 19 |
+
val requires: String,
|
| 20 |
+
/** UNet:混合链清单(hyb.json)或单个模型文件。 */
|
| 21 |
+
val unetChain: String?,
|
| 22 |
+
val unetFile: String?,
|
| 23 |
+
/** 单个 UNet 模型文件用哪个 EP:cpu 或 qnn-gpu(设备上生成并缓存 GPU 上下文,P6)。 */
|
| 24 |
+
val unetEp: String,
|
| 25 |
+
/** CFG 的两个分支分开并行(P4):[uncond 的 batch 1 UNet, cond 的 batch 1 UNet],各自是混合链或单个模型。为 null 时用上面的 batch 2 UNet。 */
|
| 26 |
+
val unetParallel: List<UnetPart>? = null,
|
| 27 |
+
val encoder: String,
|
| 28 |
+
val decoder: String,
|
| 29 |
+
val files: List<ModelFile>,
|
| 30 |
+
/** 下载地址前缀(文件名直接拼在后面);null = 只能导入,或用设置中的自定义地址。 */
|
| 31 |
+
val urlBase: String?,
|
| 32 |
+
) {
|
| 33 |
+
val totalSize get() = files.sumOf { it.size }
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
data class ModelFile(val name: String, val size: Long, val sha256: String)
|
| 37 |
+
|
| 38 |
+
data class UnetPart(val chain: String?, val file: String?, val ep: String)
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* 模型文件(TODO A4):保存在 filesDir/models/(应用私有),每个文件校验 sha256 后才算可用;校验结果按(大小、修改时间)缓存在 verified.json。
|
| 42 |
+
* 来源:
|
| 43 |
+
* - adb push 到 getExternalFilesDir("models")(/sdcard/Android/data/com.yboom.moebius/files/models/),“导入”时移动进来;
|
| 44 |
+
* - SAF 选中的文件(按文件名匹配清单);
|
| 45 |
+
* - 下载:profile 自带的 urlBase(Hugging Face luweigen/moebius-onnx-android),或设置中的自定义地址;HTTP Range 断点续传,跟随重定向。
|
| 46 |
+
* 主机流水线生成的 htp / cpu 两套托管在 Hugging Face luweigen/moebius-onnx-android(tools/upload-models.py,A11)。
|
| 47 |
+
*/
|
| 48 |
+
class ModelStore(private val ctx: Context) {
|
| 49 |
+
val dir = File(ctx.filesDir, "models").apply { mkdirs() }
|
| 50 |
+
val pushDir: File? get() = ctx.getExternalFilesDir("models")
|
| 51 |
+
val profiles: List<ModelProfile> by lazy {
|
| 52 |
+
parse(ctx.assets.open("models.json").use { it.readBytes().decodeToString() }, ctx.resources.configuration.locales[0].language)
|
| 53 |
+
}
|
| 54 |
+
private val verifiedFile = File(dir, "verified.json")
|
| 55 |
+
|
| 56 |
+
fun interface Progress { fun report(file: String, done: Long, total: Long) }
|
| 57 |
+
|
| 58 |
+
fun profile(id: String) = profiles.firstOrNull { it.id == id }
|
| 59 |
+
|
| 60 |
+
fun file(name: String) = File(dir, name)
|
| 61 |
+
|
| 62 |
+
/** 已校验的文件。 */
|
| 63 |
+
@Synchronized fun isReady(f: ModelFile): Boolean {
|
| 64 |
+
val p = file(f.name)
|
| 65 |
+
if (!p.isFile || p.length() != f.size) return false
|
| 66 |
+
return loadVerified().optString(f.name) == stamp(p, f.sha256)
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
fun missing(p: ModelProfile): List<ModelFile> = p.files.filter { !isReady(it) }
|
| 70 |
+
|
| 71 |
+
fun installed(p: ModelProfile) = missing(p).isEmpty()
|
| 72 |
+
|
| 73 |
+
/** 把 adb push 目录里属于清单的文件移进来(复制时计算 sha256,一致才保留,然后删除源文件)。返回导入的文件数。 */
|
| 74 |
+
@Synchronized fun importPushed(progress: Progress? = null): Int {
|
| 75 |
+
val src = pushDir ?: return 0
|
| 76 |
+
val wanted = profiles.flatMap { it.files }.associateBy { it.name }
|
| 77 |
+
var n = 0
|
| 78 |
+
for (f in src.listFiles().orEmpty().sortedBy { it.name }) {
|
| 79 |
+
val mf = wanted[f.name] ?: continue
|
| 80 |
+
if (isReady(mf)) { f.delete(); continue }
|
| 81 |
+
if (f.length() != mf.size) { Log.w(TAG, "${f.name}:大小 ${f.length()},清单为 ${mf.size},跳过"); continue }
|
| 82 |
+
f.inputStream().use { install(mf, it, progress) }
|
| 83 |
+
f.delete()
|
| 84 |
+
n++
|
| 85 |
+
}
|
| 86 |
+
return n
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/** SAF 选中的文件:按显示名匹配清单。返回导入的文件数和不认识的文件名。 */
|
| 90 |
+
fun importUris(uris: List<Uri>, progress: Progress? = null): Pair<Int, List<String>> {
|
| 91 |
+
val wanted = profiles.flatMap { it.files }.associateBy { it.name }
|
| 92 |
+
var n = 0
|
| 93 |
+
val unknown = ArrayList<String>()
|
| 94 |
+
for (u in uris) {
|
| 95 |
+
val name = displayName(u) ?: u.lastPathSegment ?: continue
|
| 96 |
+
val mf = wanted[name]
|
| 97 |
+
if (mf == null) { unknown.add(name); continue }
|
| 98 |
+
ctx.contentResolver.openInputStream(u)!!.use { install(mf, it, progress) }
|
| 99 |
+
n++
|
| 100 |
+
}
|
| 101 |
+
return n to unknown
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/** 下载缺少的文件。base 为空时用 profile 自带的地址。 */
|
| 105 |
+
fun download(p: ModelProfile, base: String?, progress: Progress? = null, cancelled: () -> Boolean = { false }) {
|
| 106 |
+
val urlBase = (base?.takeIf { it.isNotBlank() } ?: p.urlBase) ?: error(ctx.getString(R.string.err_no_url, p.label))
|
| 107 |
+
for (f in missing(p)) {
|
| 108 |
+
if (cancelled()) throw CancelledException()
|
| 109 |
+
downloadOne(f, urlBase.trimEnd('/') + "/" + f.name, progress, cancelled)
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
fun delete(p: ModelProfile) {
|
| 114 |
+
val keep = profiles.filter { it.id != p.id && installed(it) }.flatMap { it.files }.map { it.name }.toSet()
|
| 115 |
+
for (f in p.files) if (f.name !in keep) file(f.name).delete()
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
private fun downloadOne(f: ModelFile, url: String, progress: Progress?, cancelled: () -> Boolean) {
|
| 119 |
+
val part = File(dir, f.name + ".part")
|
| 120 |
+
val md = MessageDigest.getInstance("SHA-256")
|
| 121 |
+
// 续传:已下载的部分先算进摘要
|
| 122 |
+
if (part.length() > f.size) part.delete()
|
| 123 |
+
if (part.isFile) part.inputStream().use { digest(it, md, null) }
|
| 124 |
+
var have = part.length()
|
| 125 |
+
Log.i(TAG, if (have > 0) "续传 ${f.name}:已有 $have / ${f.size} 字节" else "下载 ${f.name}(${f.size} 字节)")
|
| 126 |
+
if (have < f.size) {
|
| 127 |
+
var u = URL(url)
|
| 128 |
+
var conn: HttpURLConnection
|
| 129 |
+
var redirects = 0
|
| 130 |
+
while (true) {
|
| 131 |
+
conn = u.openConnection() as HttpURLConnection
|
| 132 |
+
conn.instanceFollowRedirects = false // 自己跟随:HF 会 302 到另一个主机(CDN),HttpURLConnection 不跨主机跟随
|
| 133 |
+
conn.connectTimeout = 20_000
|
| 134 |
+
conn.readTimeout = 60_000
|
| 135 |
+
if (have > 0) conn.setRequestProperty("Range", "bytes=$have-")
|
| 136 |
+
val code = conn.responseCode
|
| 137 |
+
if (code in 300..399 && redirects < 10) {
|
| 138 |
+
u = URL(u, conn.getHeaderField("Location"))
|
| 139 |
+
conn.disconnect()
|
| 140 |
+
redirects++
|
| 141 |
+
continue
|
| 142 |
+
}
|
| 143 |
+
if (code == 200 && have > 0) { // 服务器不支持 Range:从头开始
|
| 144 |
+
have = 0
|
| 145 |
+
md.reset()
|
| 146 |
+
part.delete()
|
| 147 |
+
} else if (code != 200 && code != 206) {
|
| 148 |
+
error(ctx.getString(R.string.err_http, f.name, code))
|
| 149 |
+
}
|
| 150 |
+
break
|
| 151 |
+
}
|
| 152 |
+
conn.inputStream.use { input ->
|
| 153 |
+
java.io.FileOutputStream(part, have > 0).use { out ->
|
| 154 |
+
val buf = ByteArray(1 shl 16)
|
| 155 |
+
var last = 0L
|
| 156 |
+
while (true) {
|
| 157 |
+
if (cancelled()) throw CancelledException()
|
| 158 |
+
val r = input.read(buf)
|
| 159 |
+
if (r < 0) break
|
| 160 |
+
out.write(buf, 0, r)
|
| 161 |
+
md.update(buf, 0, r)
|
| 162 |
+
have += r
|
| 163 |
+
if (have - last > (4 shl 20)) { progress?.report(f.name, have, f.size); last = have }
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
conn.disconnect()
|
| 168 |
+
}
|
| 169 |
+
val sha = md.digest().hex()
|
| 170 |
+
if (part.length() != f.size || sha != f.sha256) {
|
| 171 |
+
val got = part.length()
|
| 172 |
+
part.delete()
|
| 173 |
+
error(ctx.getString(R.string.err_checksum, f.name, got, f.size, sha))
|
| 174 |
+
}
|
| 175 |
+
commit(f, part, sha)
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
private fun install(f: ModelFile, input: InputStream, progress: Progress?) {
|
| 179 |
+
val part = File(dir, f.name + ".part")
|
| 180 |
+
val md = MessageDigest.getInstance("SHA-256")
|
| 181 |
+
part.outputStream().use { out -> digest(input, md, out) { done -> progress?.report(f.name, done, f.size) } }
|
| 182 |
+
val sha = md.digest().hex()
|
| 183 |
+
if (sha != f.sha256 || part.length() != f.size) {
|
| 184 |
+
val got = part.length()
|
| 185 |
+
part.delete()
|
| 186 |
+
error(ctx.getString(R.string.err_checksum, f.name, got, f.size, sha))
|
| 187 |
+
}
|
| 188 |
+
commit(f, part, sha)
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
@Synchronized private fun commit(f: ModelFile, part: File, sha: String) {
|
| 192 |
+
val dst = file(f.name)
|
| 193 |
+
dst.delete()
|
| 194 |
+
check(part.renameTo(dst)) { "无法改名 $part → $dst" }
|
| 195 |
+
val v = loadVerified()
|
| 196 |
+
v.put(f.name, stamp(dst, sha))
|
| 197 |
+
val tmp = File(dir, "verified.json.tmp")
|
| 198 |
+
tmp.writeText(v.toString())
|
| 199 |
+
tmp.renameTo(verifiedFile)
|
| 200 |
+
Log.i(TAG, "已安装 ${f.name}(${f.size} 字节)")
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
private fun loadVerified(): JSONObject = runCatching { JSONObject(verifiedFile.readText()) }.getOrElse { JSONObject() }
|
| 204 |
+
|
| 205 |
+
private fun stamp(p: File, sha: String) = "${p.length()}:${p.lastModified()}:$sha"
|
| 206 |
+
|
| 207 |
+
private fun digest(input: InputStream, md: MessageDigest, out: java.io.OutputStream?, onProgress: ((Long) -> Unit)? = null) {
|
| 208 |
+
val buf = ByteArray(1 shl 16)
|
| 209 |
+
var done = 0L
|
| 210 |
+
var last = 0L
|
| 211 |
+
while (true) {
|
| 212 |
+
val r = input.read(buf)
|
| 213 |
+
if (r < 0) break
|
| 214 |
+
md.update(buf, 0, r)
|
| 215 |
+
out?.write(buf, 0, r)
|
| 216 |
+
done += r
|
| 217 |
+
if (onProgress != null && done - last > (8 shl 20)) { onProgress(done); last = done }
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
private fun displayName(u: Uri): String? =
|
| 222 |
+
ctx.contentResolver.query(u, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { c ->
|
| 223 |
+
if (c.moveToFirst()) c.getString(0) else null
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
companion object {
|
| 227 |
+
const val TAG = "Moebius"
|
| 228 |
+
|
| 229 |
+
/** label 是 {"en": …, "zh": …, "fi": …}(A13),取 lang,没有时取 en;也接受旧清单的单个字符串。 */
|
| 230 |
+
fun parse(json: String, lang: String = "en"): List<ModelProfile> {
|
| 231 |
+
val arr = JSONObject(json).getJSONArray("profiles")
|
| 232 |
+
return (0 until arr.length()).map { i ->
|
| 233 |
+
val o = arr.getJSONObject(i)
|
| 234 |
+
val unet = o.getJSONObject("unet")
|
| 235 |
+
val files = o.getJSONArray("files")
|
| 236 |
+
ModelProfile(
|
| 237 |
+
id = o.getString("id"), label = o.optJSONObject("label")?.let { it.optString(lang).ifEmpty { it.getString("en") } } ?: o.getString("label"), requires = o.getString("requires"),
|
| 238 |
+
unetChain = unet.optString("chain").ifEmpty { null }, unetFile = unet.optString("file").ifEmpty { null },
|
| 239 |
+
unetEp = unet.optString("ep").ifEmpty { "cpu" },
|
| 240 |
+
unetParallel = unet.optJSONArray("parallel")?.let { arr ->
|
| 241 |
+
(0 until arr.length()).map { k ->
|
| 242 |
+
val u = arr.getJSONObject(k)
|
| 243 |
+
UnetPart(u.optString("chain").ifEmpty { null }, u.optString("file").ifEmpty { null }, u.optString("ep").ifEmpty { "cpu" })
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
encoder = o.getString("encoder"), decoder = o.getString("decoder"),
|
| 247 |
+
files = (0 until files.length()).map { k ->
|
| 248 |
+
val f = files.getJSONObject(k)
|
| 249 |
+
ModelFile(f.getString("name"), f.getLong("size"), f.getString("sha256"))
|
| 250 |
+
},
|
| 251 |
+
urlBase = o.optString("url_base").ifEmpty { null },
|
| 252 |
+
)
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
fun ByteArray.hex(): String = joinToString("") { "%02x".format(it) }
|
app/src/main/kotlin/com/yboom/moebius/Pipeline.kt
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import kotlin.math.PI
|
| 4 |
+
import kotlin.math.cos
|
| 5 |
+
import kotlin.math.floor
|
| 6 |
+
import kotlin.math.ln
|
| 7 |
+
import kotlin.math.sqrt
|
| 8 |
+
|
| 9 |
+
/**
|
| 10 |
+
* 修补管线(TODO A2):源项目 web/src/ddim.ts、pipeline.ts 和 imaging.ts 中噪声部分的 Kotlin 移植,
|
| 11 |
+
* 与主机参考 tools/moebius.py 逐行对应(给定 seed 时初始噪声与 web 版、Python 版逐位一致)。不依赖 Android,JVM 单元测试直接测。
|
| 12 |
+
*
|
| 13 |
+
* 张量都是 NCHW 的 FloatArray:潜变量 (1,4,64,64),UNet 输入 (2,9,64,64) = [uncond, cond] 两份相同的
|
| 14 |
+
* [噪声潜变量 4, 蒙版 1, 被遮图潜变量 4],输出 (2,4,64,64)。
|
| 15 |
+
*/
|
| 16 |
+
object Moebius {
|
| 17 |
+
const val IMG = 512
|
| 18 |
+
const val LAT = 64
|
| 19 |
+
const val PLANE = LAT * LAT
|
| 20 |
+
const val SCALING_FACTOR = 0.13025f // 自定义 VAE,不是 SD 的 0.18215
|
| 21 |
+
const val NOISE_OFFSET = 0.0357
|
| 22 |
+
const val NUM_TRAIN_TIMESTEPS = 1000
|
| 23 |
+
const val BETA_START = 0.00085
|
| 24 |
+
const val BETA_END = 0.012
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/** mulberry32(imaging.ts):32 位整数运算,与 JS 的 Math.imul / >>> 完全相同。 */
|
| 28 |
+
class Mulberry32(seed: Int) {
|
| 29 |
+
private var s = seed
|
| 30 |
+
|
| 31 |
+
fun next(): Double {
|
| 32 |
+
s += 0x6D2B79F5
|
| 33 |
+
var t = (s xor (s ushr 15)) * (1 or s)
|
| 34 |
+
t = (t + (t xor (t ushr 7)) * (61 or t)) xor t
|
| 35 |
+
return ((t xor (t ushr 14)).toLong() and 0xFFFFFFFFL) / 4294967296.0
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
object Noise {
|
| 40 |
+
/** Box–Muller,在 double 中计算、存为 float(与 web 版的 Float32Array 相同)。 */
|
| 41 |
+
fun randn(n: Int, seed: Int): FloatArray {
|
| 42 |
+
val rng = Mulberry32(seed)
|
| 43 |
+
return FloatArray(n) {
|
| 44 |
+
var u = 0.0
|
| 45 |
+
var v = 0.0
|
| 46 |
+
while (u == 0.0) u = rng.next()
|
| 47 |
+
while (v == 0.0) v = rng.next()
|
| 48 |
+
(sqrt(-2.0 * ln(u)) * cos(2.0 * PI * v)).toFloat()
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/** pipeline.ts run():latents = randn(4·64·64, seed) + NOISE_OFFSET · randn(4, seed ^ 0x9e3779b9)[c]。 */
|
| 53 |
+
fun initLatents(seed: Int): FloatArray {
|
| 54 |
+
val lat = randn(4 * Moebius.PLANE, seed)
|
| 55 |
+
val off = randn(4, seed xor 0x9E3779B9.toInt())
|
| 56 |
+
for (c in 0 until 4) for (i in 0 until Moebius.PLANE) {
|
| 57 |
+
val k = c * Moebius.PLANE + i
|
| 58 |
+
lat[k] = (lat[k].toDouble() + Moebius.NOISE_OFFSET * off[c].toDouble()).toFloat()
|
| 59 |
+
}
|
| 60 |
+
return lat
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
/** DDIM(ddim.ts):scaled_linear β、eta=0、clip_sample=false;按 strength 截掉开头的时间步。 */
|
| 65 |
+
class Ddim(numSteps: Int, strength: Double = 0.99) {
|
| 66 |
+
val alphasCumprod = DoubleArray(Moebius.NUM_TRAIN_TIMESTEPS)
|
| 67 |
+
val timesteps: IntArray
|
| 68 |
+
|
| 69 |
+
init {
|
| 70 |
+
require(numSteps in 1..Moebius.NUM_TRAIN_TIMESTEPS) { "步数 $numSteps 超出范围" }
|
| 71 |
+
val a = sqrt(Moebius.BETA_START)
|
| 72 |
+
val b = sqrt(Moebius.BETA_END)
|
| 73 |
+
var acc = 1.0
|
| 74 |
+
for (i in 0 until Moebius.NUM_TRAIN_TIMESTEPS) {
|
| 75 |
+
val beta = (a + (b - a) * (i.toDouble() / (Moebius.NUM_TRAIN_TIMESTEPS - 1))).let { it * it }
|
| 76 |
+
acc *= 1.0 - beta
|
| 77 |
+
alphasCumprod[i] = acc
|
| 78 |
+
}
|
| 79 |
+
val ratio = Moebius.NUM_TRAIN_TIMESTEPS / numSteps
|
| 80 |
+
val all = IntArray(numSteps) { (numSteps - 1 - it) * ratio }
|
| 81 |
+
val initT = minOf(floor(numSteps * strength).toInt(), numSteps)
|
| 82 |
+
timesteps = all.copyOfRange(maxOf(numSteps - initT, 0), numSteps)
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
fun step(eps: FloatArray, sample: FloatArray, t: Int, prevT: Int): FloatArray {
|
| 86 |
+
val acT = alphasCumprod[t]
|
| 87 |
+
val acPrev = if (prevT >= 0) alphasCumprod[prevT] else 1.0
|
| 88 |
+
val sa = sqrt(1 - acT)
|
| 89 |
+
val sq = sqrt(acT)
|
| 90 |
+
val pa = sqrt(acPrev)
|
| 91 |
+
val pb = sqrt(1 - acPrev)
|
| 92 |
+
return FloatArray(sample.size) {
|
| 93 |
+
val e = eps[it].toDouble()
|
| 94 |
+
val x0 = (sample[it].toDouble() - sa * e) / sq
|
| 95 |
+
(pa * x0 + pb * e).toFloat()
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/** 三个网络。实现(Engine)负责会话的创建与释放;Pipeline 只做编排。 */
|
| 101 |
+
interface MoebiusModels {
|
| 102 |
+
/** (1,3,512,512) [-1,1] → 被遮图潜变量 (1,4,64,64),已乘 scaling_factor。 */
|
| 103 |
+
fun encode(image: FloatArray): FloatArray
|
| 104 |
+
|
| 105 |
+
/** (2,9,64,64)、时间步 → 噪声预测 (2,4,64,64)(CFG 之前)。 */
|
| 106 |
+
fun unet(latent: FloatArray, t: Int): FloatArray
|
| 107 |
+
|
| 108 |
+
/** 所有步数跑完:可以释放 UNet 的会话。 */
|
| 109 |
+
fun unetDone() {}
|
| 110 |
+
|
| 111 |
+
/** 潜变量 (1,4,64,64)(未除 scaling_factor)→ 图像 (1,3,512,512) [-1,1]。 */
|
| 112 |
+
fun decode(latents: FloatArray): FloatArray
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
class CancelledException : RuntimeException("已取消")
|
| 116 |
+
|
| 117 |
+
class Pipeline(private val models: MoebiusModels) {
|
| 118 |
+
data class Params(val steps: Int = 20, val guidance: Float = 2f, val seed: Int = 0)
|
| 119 |
+
|
| 120 |
+
data class Output(val finalLatents: FloatArray, val decoded: FloatArray, val timesteps: IntArray, val stepSeconds: DoubleArray)
|
| 121 |
+
|
| 122 |
+
fun interface Progress {
|
| 123 |
+
/** stage:encode、unet、decode;step 从 1 开始(unet 阶段)。 */
|
| 124 |
+
fun report(stage: String, step: Int, total: Int)
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/**
|
| 128 |
+
* image:(1,3,512,512) [-1,1];mask:512×512 的 0/1(1 = 修补)。
|
| 129 |
+
* [cancelled] 在每步之前检查,返回 true 时抛 [CancelledException]。
|
| 130 |
+
*/
|
| 131 |
+
fun run(image: FloatArray, mask: FloatArray, p: Params, progress: Progress = Progress { _, _, _ -> }, cancelled: () -> Boolean = { false }): Output {
|
| 132 |
+
val ddim = Ddim(p.steps)
|
| 133 |
+
val ts = ddim.timesteps
|
| 134 |
+
progress.report("encode", 0, ts.size)
|
| 135 |
+
val maskedLat = models.encode(Imaging.maskedChw(image, mask))
|
| 136 |
+
val mask64 = Imaging.maskToLatent(mask)
|
| 137 |
+
var latents = Noise.initLatents(p.seed)
|
| 138 |
+
val stepS = DoubleArray(ts.size)
|
| 139 |
+
try {
|
| 140 |
+
for ((i, t) in ts.withIndex()) {
|
| 141 |
+
if (cancelled()) throw CancelledException()
|
| 142 |
+
progress.report("unet", i + 1, ts.size)
|
| 143 |
+
val t0 = System.nanoTime()
|
| 144 |
+
val noise = models.unet(unetLatent(latents, mask64, maskedLat), t)
|
| 145 |
+
val eps = cfg(noise, p.guidance)
|
| 146 |
+
latents = ddim.step(eps, latents, t, if (i + 1 < ts.size) ts[i + 1] else -1)
|
| 147 |
+
stepS[i] = (System.nanoTime() - t0) / 1e9
|
| 148 |
+
}
|
| 149 |
+
} finally {
|
| 150 |
+
models.unetDone()
|
| 151 |
+
}
|
| 152 |
+
if (cancelled()) throw CancelledException()
|
| 153 |
+
progress.report("decode", ts.size, ts.size)
|
| 154 |
+
val decoded = models.decode(latents)
|
| 155 |
+
return Output(latents, decoded, ts, stepS)
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
companion object {
|
| 159 |
+
/** [nine, nine],nine = [latents 4, mask64 1, maskedLat 4](pipeline.ts:uncond 与 cond 的输入相同,只有 context 不同)。 */
|
| 160 |
+
fun unetLatent(latents: FloatArray, mask64: FloatArray, maskedLat: FloatArray): FloatArray {
|
| 161 |
+
val p = Moebius.PLANE
|
| 162 |
+
val out = FloatArray(2 * 9 * p)
|
| 163 |
+
for (b in 0 until 2) {
|
| 164 |
+
val o = b * 9 * p
|
| 165 |
+
latents.copyInto(out, o)
|
| 166 |
+
mask64.copyInto(out, o + 4 * p)
|
| 167 |
+
maskedLat.copyInto(out, o + 5 * p)
|
| 168 |
+
}
|
| 169 |
+
return out
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/** u + g·(c − u),batch 0 为 uncond、1 为 cond。 */
|
| 173 |
+
fun cfg(noise: FloatArray, guidance: Float): FloatArray {
|
| 174 |
+
val n = noise.size / 2
|
| 175 |
+
val g = guidance.toDouble()
|
| 176 |
+
return FloatArray(n) {
|
| 177 |
+
val u = noise[it].toDouble()
|
| 178 |
+
(u + g * (noise[n + it].toDouble() - u)).toFloat()
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
}
|
app/src/test/kotlin/com/yboom/moebius/ImagingTest.kt
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import org.junit.Assert.assertArrayEquals
|
| 4 |
+
import org.junit.Assert.assertEquals
|
| 5 |
+
import org.junit.Assert.assertTrue
|
| 6 |
+
import org.junit.Assume.assumeTrue
|
| 7 |
+
import org.junit.Test
|
| 8 |
+
import kotlin.math.log10
|
| 9 |
+
|
| 10 |
+
class ImagingTest {
|
| 11 |
+
@Test fun resampleMatchesPil() {
|
| 12 |
+
for (i in 0 until Vectors.int("resample_count")) {
|
| 13 |
+
val k = "resample.$i"
|
| 14 |
+
val ch = Vectors.int("$k.ch")
|
| 15 |
+
val w = Vectors.int("$k.w"); val h = Vectors.int("$k.h")
|
| 16 |
+
val (sx, sy, sw, sh) = Vectors.ints("$k.box").toList()
|
| 17 |
+
val (dw, dh) = Vectors.ints("$k.out").toList()
|
| 18 |
+
val filter = Imaging.Filter.valueOf(Vectors.str("$k.filter"))
|
| 19 |
+
val srcCh = Vectors.ints("$k.src")
|
| 20 |
+
// 应用里彩色图是 RGBA:补上 alpha
|
| 21 |
+
val cc = if (ch == 3) 4 else 1
|
| 22 |
+
val src = ByteArray(w * h * cc)
|
| 23 |
+
for (p in 0 until w * h) {
|
| 24 |
+
for (c in 0 until ch) src[p * cc + c] = srcCh[p * ch + c].toByte()
|
| 25 |
+
if (cc == 4) src[p * 4 + 3] = -1
|
| 26 |
+
}
|
| 27 |
+
val out = Imaging.resample(src, w, h, cc, ch, sx, sy, sw, sh, dw, dh, filter)
|
| 28 |
+
val got = IntArray(dw * dh * ch) { out[(it / ch) * cc + it % ch].toInt() and 0xff }
|
| 29 |
+
assertArrayEquals("$k ($filter ${sw}x$sh → ${dw}x$dh)", Vectors.ints("$k.expected"), got)
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
@Test fun windowMatchesPhotoTest() {
|
| 34 |
+
for (i in 0 until Vectors.int("window_count")) {
|
| 35 |
+
val k = "window.$i"
|
| 36 |
+
val r = Vectors.ints("$k.rect")
|
| 37 |
+
val win = Imaging.window(Vectors.str("$k.mode"), Vectors.int("$k.w"), Vectors.int("$k.h"), Box(r[0], r[1], r[2], r[3]))
|
| 38 |
+
assertArrayEquals(k, Vectors.ints("$k.result"), with(win) { intArrayOf(sx, sy, sw, sh, dx, dy, dw, dh) })
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
@Test fun bbox() {
|
| 43 |
+
val g = Gray(10, 8)
|
| 44 |
+
g.px[3 * 10 + 2] = -1; g.px[5 * 10 + 6] = 100
|
| 45 |
+
assertEquals(Box(2, 3, 7, 6), Imaging.bbox(g))
|
| 46 |
+
assertEquals(null, Imaging.bbox(Gray(4, 4)))
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/** 蒙版降采样“每 8×8 取左上角”:块内只有左上角为 1 时整块为 1,左上角为 0 时整块为 0。 */
|
| 50 |
+
@Test fun maskToLatentRule() {
|
| 51 |
+
val m = FloatArray(512 * 512)
|
| 52 |
+
m[0] = 1f // 块 (0,0) 的左上角
|
| 53 |
+
m[8 * 512 + 9] = 1f // 块 (1,1) 内但不是左上角
|
| 54 |
+
m[16 * 512 + 16] = 1f // 块 (2,2) 的左上角
|
| 55 |
+
val l = Imaging.maskToLatent(m)
|
| 56 |
+
assertEquals(1f, l[0]); assertEquals(0f, l[64 + 1]); assertEquals(1f, l[2 * 64 + 2])
|
| 57 |
+
assertEquals(2f, l.sum())
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/** 样例图(512×512)经 canvas() 应原样不变(两种模式都不缩放),张量与 fixture 逐位一致。 */
|
| 61 |
+
@Test fun fixtureTensors() {
|
| 62 |
+
assumeTrue(Fixture.present)
|
| 63 |
+
val img = Fixture.rgba("image.png")
|
| 64 |
+
val mask = Fixture.gray("mask.png")
|
| 65 |
+
for (mode in listOf("crop", "letterbox")) {
|
| 66 |
+
val win = Imaging.window(mode, 512, 512, Imaging.bbox(mask))
|
| 67 |
+
assertEquals(Window(0, 0, 512, 512, 0, 0, 512, 512), win)
|
| 68 |
+
val (canvas, m512) = Imaging.canvas(img, mask, win)
|
| 69 |
+
assertArrayEquals(img.px, canvas.px)
|
| 70 |
+
val mbin = Imaging.maskBinary(m512)
|
| 71 |
+
assertEquals(Fixture.sha("mask64.bin"), sha256(Imaging.maskToLatent(mbin)))
|
| 72 |
+
assertEquals(Fixture.sha("masked_image.bin"), sha256(Imaging.maskedChw(Imaging.toChw(canvas), mbin)))
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/** decoded.png 贴回原图应接近主机的 result.png(PIL 的 GaussianBlur 是盒式近似,不逐位一致)。 */
|
| 77 |
+
@Test fun pasteBackCloseToFixture() {
|
| 78 |
+
assumeTrue(Fixture.present)
|
| 79 |
+
val out = Imaging.pasteBack512(Fixture.rgba("decoded.png"), Fixture.rgba("image.png"), Fixture.gray("mask.png"))
|
| 80 |
+
val ref = Fixture.rgba("result.png")
|
| 81 |
+
val p = psnr(out, ref)
|
| 82 |
+
assertTrue("PSNR $p", p > 40)
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
@Test fun composeFullOnlyTouchesMask() {
|
| 86 |
+
val w = 1200; val h = 900
|
| 87 |
+
val photo = Rgba(w, h)
|
| 88 |
+
for (i in photo.px.indices) photo.px[i] = (i * 31 % 251).toByte()
|
| 89 |
+
val orig = photo.px.copyOf()
|
| 90 |
+
val mask = Gray(w, h)
|
| 91 |
+
for (y in 300 until 500) for (x in 100 until 260) mask.px[y * w + x] = -1
|
| 92 |
+
val win = Imaging.window("crop", w, h, Imaging.bbox(mask))
|
| 93 |
+
val decoded = Rgba(512, 512).also { it.px.fill(-1) }
|
| 94 |
+
Imaging.composeFull(photo, decoded, win, mask)
|
| 95 |
+
val sigma = 3.0 * win.sw / win.dw
|
| 96 |
+
val r = kotlin.math.ceil(3 * sigma).toInt()
|
| 97 |
+
var changedInside = 0
|
| 98 |
+
for (y in 0 until h) for (x in 0 until w) {
|
| 99 |
+
val near = x >= 100 - r && x < 260 + r && y >= 300 - r && y < 500 + r
|
| 100 |
+
val changed = (0 until 3).any { photo.px[(y * w + x) * 4 + it] != orig[(y * w + x) * 4 + it] }
|
| 101 |
+
if (!near) assertTrue("($x,$y) 不应改变", !changed)
|
| 102 |
+
if (x in 120 until 240 && y in 320 until 480 && photo[x, y, 0] == 255) changedInside++
|
| 103 |
+
}
|
| 104 |
+
assertEquals(120 * 160, changedInside) // 蒙版内部(离边缘超过羽化宽度)完全替换为模型输出
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
private fun psnr(a: Rgba, b: Rgba): Double {
|
| 108 |
+
var se = 0.0
|
| 109 |
+
for (p in 0 until a.w * a.h) for (c in 0 until 3) { val d = a[p % a.w, p / a.w, c] - b[p % b.w, p / b.w, c]; se += d * d }
|
| 110 |
+
val mse = se / (a.w * a.h * 3)
|
| 111 |
+
return if (mse == 0.0) 999.0 else 10 * log10(255.0 * 255.0 / mse)
|
| 112 |
+
}
|
| 113 |
+
}
|
app/src/test/kotlin/com/yboom/moebius/PipelineTest.kt
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import org.junit.Assert.assertArrayEquals
|
| 4 |
+
import org.junit.Assert.assertEquals
|
| 5 |
+
import org.junit.Assume.assumeTrue
|
| 6 |
+
import org.junit.Test
|
| 7 |
+
|
| 8 |
+
class PipelineTest {
|
| 9 |
+
@Test fun randnMatchesReference() {
|
| 10 |
+
// 与 moebius.py(因而与 web 版 imaging.ts)逐位一致
|
| 11 |
+
assertArrayEquals(Vectors.floats("randn_seed0"), Noise.randn(16, 0), 0f)
|
| 12 |
+
assertArrayEquals(Vectors.floats("randn_seed12345"), Noise.randn(16, 12345), 0f)
|
| 13 |
+
assertArrayEquals(Vectors.floats("randn_seed_neg"), Noise.randn(16, -7), 0f)
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
@Test fun initLatentsMatchFixture() {
|
| 17 |
+
assumeTrue(Fixture.present)
|
| 18 |
+
assertEquals(Fixture.sha("init_latents.bin"), sha256(Noise.initLatents(0)))
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@Test fun ddimTimesteps() {
|
| 22 |
+
assertArrayEquals(Vectors.ints("ddim20_timesteps"), Ddim(20).timesteps)
|
| 23 |
+
assertArrayEquals(Vectors.ints("ddim7_timesteps"), Ddim(7).timesteps)
|
| 24 |
+
assertEquals(19, Ddim(20).timesteps.size) // strength 0.99:20 步实际 19 步(fixture meta.json)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
@Test fun ddimConstants() {
|
| 28 |
+
val d = Ddim(20)
|
| 29 |
+
for (i in listOf(0, 1, 500, 900, 999)) assertEquals(Vectors.str("alphas_cumprod.$i").toDouble(), d.alphasCumprod[i], 1e-15)
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@Test fun ddimStep() {
|
| 33 |
+
val d = Ddim(20)
|
| 34 |
+
val eps = Vectors.floats("ddim_step.eps")
|
| 35 |
+
val sample = Vectors.floats("ddim_step.sample")
|
| 36 |
+
assertArrayEquals(Vectors.floats("ddim_step.t900_850"), d.step(eps, sample, 900, 850), 1e-6f)
|
| 37 |
+
assertArrayEquals(Vectors.floats("ddim_step.t0_end"), d.step(eps, sample, 0, -1), 1e-6f)
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
@Test fun cfg() {
|
| 41 |
+
val noise = Vectors.floats("cfg.noise")
|
| 42 |
+
assertArrayEquals(Vectors.floats("cfg.g2"), Pipeline.cfg(noise, 2f), 0f)
|
| 43 |
+
assertArrayEquals(Vectors.floats("cfg.g3_5"), Pipeline.cfg(noise, 3.5f), 0f)
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
@Test fun unetLatentLayout() {
|
| 47 |
+
assumeTrue(Fixture.present)
|
| 48 |
+
val lat = Pipeline.unetLatent(Fixture.floats("init_latents.bin"), Fixture.floats("mask64.bin"), Fixture.floats("masked_lat.bin"))
|
| 49 |
+
assertEquals(Fixture.sha("unet0_latent.bin"), sha256(lat))
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/** 用假模型跑完整编排:确认步数、时间步顺序和 unetDone 的调用。 */
|
| 53 |
+
@Test fun pipelineOrchestration() {
|
| 54 |
+
val seen = ArrayList<Int>()
|
| 55 |
+
var done = 0
|
| 56 |
+
val models = object : MoebiusModels {
|
| 57 |
+
override fun encode(image: FloatArray) = FloatArray(4 * Moebius.PLANE)
|
| 58 |
+
override fun unet(latent: FloatArray, t: Int): FloatArray { seen.add(t); return FloatArray(2 * 4 * Moebius.PLANE) }
|
| 59 |
+
override fun unetDone() { done++ }
|
| 60 |
+
override fun decode(latents: FloatArray) = FloatArray(3 * Moebius.IMG * Moebius.IMG)
|
| 61 |
+
}
|
| 62 |
+
val out = Pipeline(models).run(FloatArray(3 * 512 * 512), FloatArray(512 * 512), Pipeline.Params(steps = 20))
|
| 63 |
+
assertArrayEquals(Vectors.ints("ddim20_timesteps"), seen.toIntArray())
|
| 64 |
+
assertEquals(1, done)
|
| 65 |
+
assertEquals(19, out.stepSeconds.size)
|
| 66 |
+
}
|
| 67 |
+
}
|
app/src/test/kotlin/com/yboom/moebius/Vectors.kt
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.yboom.moebius
|
| 2 |
+
|
| 3 |
+
import java.io.File
|
| 4 |
+
import java.security.MessageDigest
|
| 5 |
+
import java.nio.ByteBuffer
|
| 6 |
+
import java.nio.ByteOrder
|
| 7 |
+
import javax.imageio.ImageIO
|
| 8 |
+
|
| 9 |
+
/** tools/gen-test-vectors.py 生成的参考值(每行 `键=值 值 …`)。 */
|
| 10 |
+
object Vectors {
|
| 11 |
+
private val map: Map<String, String> by lazy {
|
| 12 |
+
val text = Vectors::class.java.classLoader!!.getResourceAsStream("vectors.txt")!!.bufferedReader().readText()
|
| 13 |
+
text.lines().filter { '=' in it }.associate { it.substringBefore('=') to it.substringAfter('=') }
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
fun str(key: String): String = map[key] ?: error("vectors.txt 中没有 $key")
|
| 17 |
+
fun int(key: String): Int = str(key).toInt()
|
| 18 |
+
fun ints(key: String): IntArray = str(key).split(' ').filter { it.isNotEmpty() }.map { it.toInt() }.toIntArray()
|
| 19 |
+
fun doubles(key: String): DoubleArray = str(key).split(' ').filter { it.isNotEmpty() }.map { it.toDouble() }.toDoubleArray()
|
| 20 |
+
fun floats(key: String): FloatArray = doubles(key).map { it.toFloat() }.toFloatArray()
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/** 主机流水线生成的 fixture(.toolchain/fixture,M1);不存在时相关测试跳过。 */
|
| 24 |
+
object Fixture {
|
| 25 |
+
val dir = File("../.toolchain/fixture")
|
| 26 |
+
val present get() = File(dir, "meta.json").isFile
|
| 27 |
+
|
| 28 |
+
/** meta.json 中某个张量的 sha256(不用 JSON 库:按键名找到后面第一个 "sha256")。 */
|
| 29 |
+
fun sha(name: String): String {
|
| 30 |
+
val meta = File(dir, "meta.json").readText()
|
| 31 |
+
val i = meta.indexOf("\"$name\"")
|
| 32 |
+
require(i >= 0) { "meta.json 中没有 $name" }
|
| 33 |
+
val j = meta.indexOf("sha256", i) + "sha256".length
|
| 34 |
+
return meta.substring(j).substringAfter('"').substringAfter('"').substringBefore('"')
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
fun rgba(name: String): Rgba {
|
| 38 |
+
val img = ImageIO.read(File(dir, name))
|
| 39 |
+
val r = Rgba(img.width, img.height)
|
| 40 |
+
val ras = img.raster
|
| 41 |
+
for (y in 0 until img.height) for (x in 0 until img.width) {
|
| 42 |
+
for (c in 0 until 3) r.px[(y * img.width + x) * 4 + c] = ras.getSample(x, y, if (ras.numBands >= 3) c else 0).toByte()
|
| 43 |
+
r.px[(y * img.width + x) * 4 + 3] = -1
|
| 44 |
+
}
|
| 45 |
+
return r
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
fun gray(name: String): Gray {
|
| 49 |
+
val img = ImageIO.read(File(dir, name))
|
| 50 |
+
val g = Gray(img.width, img.height)
|
| 51 |
+
for (y in 0 until img.height) for (x in 0 until img.width) g.px[y * img.width + x] = img.raster.getSample(x, y, 0).toByte()
|
| 52 |
+
return g
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
fun floats(name: String): FloatArray {
|
| 56 |
+
val b = ByteBuffer.wrap(File(dir, name).readBytes()).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer()
|
| 57 |
+
return FloatArray(b.remaining()).also { b.get(it) }
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
fun sha256(a: FloatArray): String {
|
| 62 |
+
val bb = ByteBuffer.allocate(a.size * 4).order(ByteOrder.LITTLE_ENDIAN)
|
| 63 |
+
bb.asFloatBuffer().put(a)
|
| 64 |
+
return MessageDigest.getInstance("SHA-256").digest(bb.array()).joinToString("") { "%02x".format(it) }
|
| 65 |
+
}
|
app/src/test/resources/vectors.txt
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
randn_seed0=1.6264322996139526 1.0509958267211914 -1.184387445449829 -0.5844050049781799 -1.0935255289077759 1.367569088935852 -0.3777708411216736 -0.43444937467575073 -0.4611024856567383 0.7676340341567993 0.6727240681648254 1.2793116569519043 0.5756888389587402 -1.0697171688079834 1.1758852005004883 -0.7439971566200256
|
| 2 |
+
randn_seed12345=-0.07064807415008545 0.49860718846321106 -0.6676633954048157 0.2348252683877945 0.036207668483257294 1.174871563911438 0.4724176228046417 0.007121287286281586 -0.4146225154399872 0.47584304213523865 0.17588676512241364 -0.39616549015045166 1.1270253658294678 0.41224339604377747 0.6073761582374573 1.4643034934997559
|
| 3 |
+
randn_seed_neg=-0.5902034044265747 -1.0944724082946777 2.376452922821045 0.44103097915649414 0.2569187879562378 1.9981882572174072 0.6464406251907349 -1.639702558517456 -1.8887988328933716 0.17428262531757355 1.4604644775390625 0.6563805937767029 0.15467703342437744 -0.6187875866889954 -1.3679134845733643 -3.234931468963623
|
| 4 |
+
ddim20_timesteps=900 850 800 750 700 650 600 550 500 450 400 350 300 250 200 150 100 50 0
|
| 5 |
+
ddim7_timesteps=710 568 426 284 142 0
|
| 6 |
+
alphas_cumprod.0=0.99915
|
| 7 |
+
alphas_cumprod.1=0.9982960278384514
|
| 8 |
+
alphas_cumprod.500=0.2763326838229746
|
| 9 |
+
alphas_cumprod.900=0.014151141969672461
|
| 10 |
+
alphas_cumprod.999=0.004660098513077236
|
| 11 |
+
ddim_step.eps=0.9659740328788757 1.123104214668274 -0.04922786355018616 -0.1808914989233017 1.3060548305511475 -1.2513703107833862 -1.6344149112701416 0.9436811208724976 1.0903569459915161 0.14630553126335144 0.5744938254356384 -2.254234552383423 0.30916333198547363 0.43158426880836487 -0.8766408562660217 0.26147910952568054 -0.7755794525146484 0.7218309044837952 0.07436791807413101 -0.8611394762992859 0.006577192805707455 -0.2902894616127014 0.7319475412368774 1.5547200441360474 0.6477386951446533 1.6893988847732544 -0.664939820766449 -0.6829274296760559 0.276081919670105 0.747948944568634 0.5504459738731384 2.01704740524292
|
| 12 |
+
ddim_step.sample=-0.3568333387374878 -1.5389859676361084 -0.3513500392436981 -0.7355382442474365 0.5294187664985657 0.2081037014722824 0.666608452796936 0.6588438153266907 -0.5964908599853516 0.2327454686164856 0.389261394739151 0.7234424948692322 2.92529559135437 -0.83537358045578 0.01646995358169079 -0.713308572769165 -2.4051289558410645 2.0264852046966553 0.09647177159786224 3.816502332687378 2.256387233734131 0.3847963511943817 0.8360540866851807 0.6020588278770447 -0.5805540680885315 0.2922411262989044 0.8142020106315613 -0.3732805550098419 1.111653208732605 0.3032839000225067 -0.41975003480911255 0.40766677260398865
|
| 13 |
+
ddim_step.t900_850=-0.7338899374008179 -2.2956554889678955 -0.43676427006721497 -0.8921087980270386 0.3061180114746094 0.6245999336242676 1.3223787546157837 0.5757351517677307 -1.0769457817077637 0.2568385899066925 0.3353172242641449 1.5724157094955444 3.6652188301086426 -1.1952239274978638 0.27162981033325195 -0.9899906516075134 -2.8644962310791016 2.3940019607543945 0.10253636538982391 5.143170356750488 2.8933792114257812 0.5766962766647339 0.8636226058006287 0.3282702565193176 -0.930020272731781 -0.10775287449359894 1.2347382307052612 -0.2838270962238312 1.3475162982940674 0.17543204128742218 -0.6958853602409363 -0.05326995253562927
|
| 14 |
+
ddim_step.t0_end=-0.38515982031822205 -1.5723981857299805 -0.35006362199783325 -0.7305749654769897 0.4915499985218048 0.24469111859798431 0.7145631909370422 0.631599485874176 -0.6285471320152283 0.2285771369934082 0.37267059087753296 0.7894997596740723 2.9175221920013428 -0.8483169078826904 0.04204608127474785 -0.7212385535240173 -2.383530378341675 2.006293296813965 0.09434369951486588 3.8432424068450928 2.257154941558838 0.39342692494392395 0.8150607943534851 0.556968092918396 -0.5996936559677124 0.2430904507637024 0.8339426517486572 -0.3535202443599701 1.104073405265808 0.2815973460674286 -0.43598347902297974 0.34900858998298645
|
| 15 |
+
cfg.noise=0.7863845825195312 1.1165721416473389 -0.7303775548934937 0.6315091252326965 -1.5453435182571411 1.5646580457687378 -0.812419056892395 0.49483102560043335 -0.5478596091270447 -0.7427659034729004 0.5565217137336731 1.1072603464126587 -0.18950186669826508 0.043859679251909256 -0.2896573543548584 -1.3048723936080933 -0.19867123663425446 1.5113557577133179 -1.7019460201263428 2.2318062782287598 -0.3540641665458679 -0.4943656921386719 0.5610008835792542 -0.04718242213129997 -1.311676263809204 0.7101010680198669 -0.20004472136497498 0.3444487154483795 0.02247726172208786 -0.6230816841125488 1.2505321502685547 -1.1644192934036255
|
| 16 |
+
cfg.g2=-1.1837270259857178 1.9061393737792969 -2.6735143661499023 3.8321034908294678 0.8372151851654053 -2.553389549255371 1.9344208240509033 -0.5891958475112915 -2.0754928588867188 2.162968158721924 -0.956611156463623 -0.41836291551589966 0.2344563901424408 -1.2900230884552002 2.7907216548919678 -1.0239661931991577
|
| 17 |
+
cfg.g3_5=-2.6613106727600098 2.49831485748291 -4.130867004394531 6.23254919052124 2.6241343021392822 -5.641924858093262 3.9945507049560547 -1.4022160768508911 -3.2212178707122803 4.342268466949463 -2.091460704803467 -1.5625803470611572 0.5524250864982605 -2.2904350757598877 5.101006031036377 -0.813286542892456
|
| 18 |
+
resample.0.ch=3
|
| 19 |
+
resample.0.w=37
|
| 20 |
+
resample.0.h=23
|
| 21 |
+
resample.0.box=0 0 37 23
|
| 22 |
+
resample.0.out=11 7
|
| 23 |
+
resample.0.filter=LANCZOS
|
| 24 |
+
resample.0.src=0 59 118 37 96 155 74 133 192 111 170 229 148 207 10 185 244 47 222 25 84 3 62 121 40 99 158 77 136 195 114 173 232 151 210 13 188 247 50 225 28 87 6 65 124 43 102 161 80 139 198 117 176 235 154 213 16 191 250 53 228 31 90 9 68 127 46 105 164 83 142 201 120 179 238 157 216 19 194 253 56 231 34 93 12 71 130 49 108 167 86 145 204 123 182 241 160 219 22 197 0 59 234 37 96 15 74 133 52 111 170 101 160 219 139 196 1 173 232 39 215 12 73 253 48 107 27 92 145 69 120 191 111 164 217 133 192 11 187 228 33 221 24 71 247 60 121 45 80 155 75 140 177 101 168 239 159 196 9 165 224 59 203 4 65 237 40 103 55 76 137 93 144 171 123 188 241 133 216 31 175 228 57 197 0 75 27 36 97 61 120 135 87 156 217 109 176 251 139 204 17 165 232 47 255 4 73 37 96 91 11 68 129 109 168 231 87 140 201 189 240 43 202 5 64 237 40 103 16 75 142 63 114 169 86 145 220 137 180 243 164 239 18 195 6 77 226 61 120 5 64 159 40 99 166 119 138 193 158 217 228 177 252 59 204 23 90 235 46 117 58 117 176 29 88 151 64 187 254 175 226 217 134 193 12 249 36 99 212 31 66 51 118 189 114 77 136 85 144 239 184 243 54 135 218 17 238 41 116 193 12 75 28 103 170 123 190 133 42 229 160 205 136 71 240 171 110 159 82 9 182 113 60 47 106 165 87 140 201 127 178 233 151 208 29 207 242 53 231 44 81 31 90 145 39 120 189 79 138 213 103 172 233 191 194 9 231 32 29 207 2 69 55 108 161 31 90 129 119 184 253 79 138 197 151 236 41 255 50 9 215 16 93 47 114 181 7 76 145 31 218 145 199 248 189 239 170 85 135 76 9 191 98 41 71 0 221 111 34 229 55 204 129 223 154 161 247 184 125 175 106 37 151 76 9 127 50 233 87 16 221 15 242 181 148 207 10 189 240 43 214 17 92 15 50 117 56 115 142 89 156 215 106 181 240 139 206 17 156 215 18 197 56 115 46 105 84 7 74 141 96 187 246 65 132 223 162 237 40 131 198 9 164 95 26 77 0 59 102 33 236 31 194 133 40 227 190 201 140 71 154 165 96 187 126 1 108 39 226 85 8 195 62 249 164 23 218 157 208 171 102 177 116 79 146 93 24 115 54 249 180 239 42 221 16 75 246 49 124 47 82 149 88 147 174 249 52 111 27 92 145 73 116 179 103 172 209 153 220 23 171 244 49 201 12 83 223 20 81 9 116 191 107 172 145 89 148 211 167 252 49 137 204 23 155 84 17 185 124 51 111 20 209 25 212 239 59 252 177 201 148 83 231 172 113 185 124 55 107 84 17 73 12 243 63 244 177 9 212 159 235 172 113 57 116 179 103 156 209 137 204 247 187 244 49 217 28 83 239 52 113 57 116 175 27 92 145 73 180 243 167 236 209 153 220 23 94 153 212 133 184 255 164 239 18 223 26 81 234 53 112 9 76 147 24 83 150 75 182 253 182 241 204 157 208 23 236 55 122 183 114 41 82 29 216 113 52 251 48 203 142 211 158 165 206 137 68 181 104 47 148 95 2 111 42 225 58 5 192 25 220 163 8 67 134 59 102 173 70 129 220 109 160 231 188 199 10 231 34 25 194 13 72 33 100 171 0 91 158 99 174 245 62 249 180 229 152 95 132 79 114 191 122 49 74 21 208 195 254 57 239 36 89 3 70 141 39 120 189 75 142 209 95 148 209 139 246 61 247 48 13 211 30 89 47 116 185 115 54 237 23 216 157 43 238 161 255 132 65 171 102 93 135 64 13 99 62 249 79 4 217 35 230 173 7 56 125 43 110 145 95 148 209 107 182 253 183 240 45 147 222 25 207 52 121 51 118 77 23 88 157 11 206 129 63 228 161 203 134 93 231 160 109 131 126 57 111 36 25 67 6 205 39 248 189 11 206 145 40 99 158 69 128 203 98 189 248 143 202 21 156 215 18 201 52 127 54 113 76 19 94 153 16 203 134 61 248 163 202 133 64 231 162 109 132 127 58 97 44 23 94 25 212 43 246 177 248 51 110 21 80 155 82 109 168 127 186 197 172 231 34 153 196 15 230 33 124 195 14 73 96 27 214 13 200 243 58 245 176 215 146 93 212 143 74 177 124 39 142 73 4 91 38 225 200 3 62 229 32 107 2 93 152 47 106 181 124 183 242 141 200 3 187 228 33 197 0 95 231 44 105 5 120 179 107 172 145 93 144 215 175 244 57 253 184 99 139 68 1 165 96 47 71 60 249 37 232 211 27 220 145 237 176 119 63 116 169 77 136 195 155 164 225 133 192 63 231 44 105 197 24 83 43 108 177 125 48 247 47 212 153 221 152 163 203 132 65 165 96 47 135 92 25 101 40 243 187 252 49 237 16 87 31 84 105 13 72 131 123 164 225 69 128 223 167 236 41 133 184 115 242 45 104 29 88 135 40 99 166 127 130 201 174 233 212 153 212 19 236 55 122 179 118 45 74 5 192 101 32 239 0 251 190 231 170 145 214 145 92 81 140 203 116 191 226 139 206 5 226 221 24 205 8 119 56 115 182 15 82 153 30 217 132 41 228 163 252 135 74 163 102 93 154 85 16 117 48 255 176 235 46 215 26 65 230 33 108 1 124 187 100 175 146 91 158 213 210 141 72 253 184 103 136 67 6 95 98 41 78 9 244 87 146 205 119 188 249 183 202 1 231 32 29 199 10 77 39 124 177 119 50 233 23 216 157 39 226 173 199 188 121 167 106 81 151 80 29 151 202 13 183 252 33 199 2 73 39 24 93 23 82 173 119 188 249 55 234 161 199 128 93 231 170 109 135 124 49 119 50 9 87 24 221 199 2 77 231 60 121 7 74 145 23 80 157 87 170 237 183 252 193 231 162 105 135 88 29 151 82 13 119 60 249 55 10 193 231 32 93 7 74 141 188 247 50 237 16 91 30 89 100 15 66 133 96 187 246 73 140 215 210 157 88 235 174 97 132 127 58 101 40 19 86 17 220 215 10 77 232 35 126 1 68 143 106 85 144 83 150 233 204 135 66 253 160 107 142 73 20 159 82 21 80 43 230 57 252 199 226 45 104 27 94 145 20 79 138 117 184 227 70 129 204 231 154 93 152 83 110 177 116 63 122 37 224 67 6 217 220 23 82 13 48 123 62 121 132 111 162 229 64 155 214 33 92 151 75 140 177 113 188 251 183 236 33 129 196 31 155 84 17 177 116 59 127 4 193 33 236 215 27 220 145 17 76 139 55 124 161 65 132 207 171 148 209 145 212 43 143 68 1 161 124 55 75 12 209 81 28 219 23 236 161 1 68 127 59 116 177 81 148 219 95 132 193 161 236 55 251 188 113 177 76 11 87 28 33 65 4 207 43 244 177 241 52 107 15 68 129 97 92 151 75 140 241 177 252 59 247 172 97 129 68 31 134 193 252 165 232 47 204 23 90 223 26 65 34 109 168 121 60 243 48 203 142 235 166 157 222 153 84 173 112 55 116 191 226 135 194 9 234 213 16 209 20 107 72 3 198 99 62 245 54 241 172 21 216 159 220 167 106 79 138 177 114 189 248 169 236 35 128 219 30 155 86 13 174 105 36 125 0 199 36 239 210 23 210 153 26 69 128 33 100 187 120 179 246 179 142 197 230 161 92 133 72 15 172 119 58 127 58 225 66 13 200 235 38 97 31 68 137 43 110 181 119 184 253 67 134 201 239 148 81 147 94 101 135 64 13 107 54 241 191 244 41 203 14 69 39 24 93 19 86 169 15 196 129 35 254 181 247 176 109 203 134 65 159 100 41 139 206 245 183 248 61 227 38 105 207 20 81 83 30 197 103 32 237 11 246 177 255 180 137 43 110 165 71 152 221 83 150 201 175 228 33 131 190 117 183 112 13 107 38 225 95 4 201 43 238 181 247 56 125 3 70 137 80 139 198 101 160 251 186 245 48 143 202 5 164 95 26 89 20 47 78 9 196 35 254 185 248 51 110 13 72 131 98 93 152 87 146 237 204 135 66 225 188 119 182 113 44 139 70 1 160 219 22 213 16 43 202 5 64 63 122 181 116 47 234 9 196 159 30 217 148 211 174 105 72 131 190 125 184 243 178 237 40 135 194 29 156 87 18 81 108 39 70 1 252 59 246 177 240 43 102 5 64 155 26 85 144 111 170 229 196 255 186 181 240 43 203 4 65 221 24 87 23 108 169 13 192 251 59 252 177 245 168 111 207 132 89 85 144 219 155 164 225 141 200 55 247 60 121 189 96 43 75 12 209 85 24 223 31 228 169 21 80 107 11 68 129 125 184 247 55 236 169 205 128 91 219 156 81 149 104 47 143 196 249 181 240 59 251 36 97 205 8 87 87 28 217 29 32 235 11 204 177 245 184 127 63 100 169 117 176 235 75 132 193 221 152 87 151 172 105 141 64 59 26 85 144 45 104 167 64 187 254 191 242 201 230 161 108 137 84 19 148 95 2 99 38 237 210 237 40 197 0 127 56 115 182 119 42 225 14 201 148 17 220 155 220 167 106 75 142 181 74 133 192 189 248 55 240 171 110 143 66 25 150 81 28 89 36 227 196 15 50 243 54 125 34 125 184 117 48 239 8 195 134 231 218 145 222 153 100 65 140 203 108 183 250 187 254 37 250 181 112 141 72 7 96 91 30 95 18 233 198 1 76 127 186 245 183 204 9 239 34 25 215 16 93 95 2 197 103 44 241 47 234 161 7 56 125 63 122 133 103 172 233 79 146 217 199 128 93 223 146 85 151 108 33 143 202 241 183 248 61 255 58 117 183 108 41 79 2 217 87 16 221 31 226 165 7 76 113 15 74 129 103 184 253 63 250 165 199 140 73 175 146 89 103 160 221 159 210 21 151 204 1 239 42 113 183 120 61 127 58 245 55 12 201 239 34 89 23 80 157 31 66 133 228 31 90 29 80 107 6 65 140 111 178 245 40 227 190 249 188 119 186 133 64 107 174 209 172 231 34 133 216 19 158 89 4 167 106 45 80 43 230 193 4 63 242 61 120 35 102 169 116 47 234 13 192 155 22 209 156 223 162 101 88 147 174 73 140 199 170 245 48 251 190 97 188 119 50 117 72 3 174 233 20 215 26 93 192 27 86 81 20 207 98 45 232 51 246 185 4 63 122 61 112 139 102 161 236 79 146 213 200 131 94 73 132 191 123 188 241 185 228 35 135 204 17 137 76 7 107 84 17 89 28 227 223 20 81 217 4 79 43 108 177 105 36 227 7 252 177 249 188 135 59 116 177 105 172 227 79 148 209 201 132 95 219 156 81 153 100 35 135 204 241 137 204 7 235 52 113 185 124 35 127 52 241 57 4 207 235 44 81 41 100 163 7 92 145 25 220 135 27 212 145 233 172 99 79 116 177 73 132 255 187 252 49 249 164 99 135 76 17 137 76 7 174 233 36 197 24 95 212 31 66 95 26 209 26 37 224 9 204 179 8 67 134 43 118 189 102 161 252 61 240 183 252 135 74 183 114 73 98 173 232 145 212 27 128 219 30 147 94 5 158 89 20 85 40 239 196 15 50 207 10 65 42 117 176 121 60 227 56 243 182 251 198 141 54 113 140 109 160 231 76 151 218 199 130 89 210 157 88 161 100 43 144 171 238 131 206 53 142 73 4 165 120 63 116 63 226 191 250 49 250 5 64
|
| 25 |
+
resample.0.expected=117 120 120 138 136 126 112 119 124 146 127 124 117 128 132 141 129 123 130 131 126 131 132 113 125 129 131 139 129 109 129 111 121 132 132 126 120 132 126 127 134 131 122 125 120 127 133 120 124 117 122 103 132 132 113 139 145 132 133 133 154 120 121 123 123 144 128 133 130 119 130 125 128 135 121 125 149 131 128 124 137 107 117 144 118 125 137 131 135 133 131 133 100 131 100 120 95 130 147 130 123 119 138 128 132 117 134 130 135 121 117 125 129 125 121 131 135 119 119 121 139 88 121 137 118 140 134 96 127 116 96 132 126 135 138 125 121 121 138 126 127 114 115 120 129 123 115 130 135 133 116 132 139 123 143 138 85 131 142 128 108 145 123 117 144 129 125 134 130 132 132 131 124 135 134 114 138 123 139 129 129 125 124 124 116 132 147 138 137 130 137 129 129 133 148 116 89 135 136 120 118 108 107 133 116 123 135 137 141 129 140 129 130 130 112 125 129 136 139 134 117 126 129 132 119 119 135 142 145 109 106
|
| 26 |
+
resample.1.ch=3
|
| 27 |
+
resample.1.w=37
|
| 28 |
+
resample.1.h=23
|
| 29 |
+
resample.1.box=3 2 30 20
|
| 30 |
+
resample.1.out=60 50
|
| 31 |
+
resample.1.filter=LANCZOS
|
| 32 |
+
resample.1.src=0 59 118 37 96 155 74 133 192 111 170 229 148 207 10 185 244 47 222 25 84 3 62 121 40 99 158 77 136 195 114 173 232 151 210 13 188 247 50 225 28 87 6 65 124 43 102 161 80 139 198 117 176 235 154 213 16 191 250 53 228 31 90 9 68 127 46 105 164 83 142 201 120 179 238 157 216 19 194 253 56 231 34 93 12 71 130 49 108 167 86 145 204 123 182 241 160 219 22 197 0 59 234 37 96 15 74 133 52 111 170 101 160 219 139 196 1 173 232 39 215 12 73 253 48 107 27 92 145 69 120 191 111 164 217 133 192 11 187 228 33 221 24 71 247 60 121 45 80 155 75 140 177 101 168 239 159 196 9 165 224 59 203 4 65 237 40 103 55 76 137 93 144 171 123 188 241 133 216 31 175 228 57 197 0 75 27 36 97 61 120 135 87 156 217 109 176 251 139 204 17 165 232 47 255 4 73 37 96 91 11 68 129 109 168 231 87 140 201 189 240 43 202 5 64 237 40 103 16 75 142 63 114 169 86 145 220 137 180 243 164 239 18 195 6 77 226 61 120 5 64 159 40 99 166 119 138 193 158 217 228 177 252 59 204 23 90 235 46 117 58 117 176 29 88 151 64 187 254 175 226 217 134 193 12 249 36 99 212 31 66 51 118 189 114 77 136 85 144 239 184 243 54 135 218 17 238 41 116 193 12 75 28 103 170 123 190 133 42 229 160 205 136 71 240 171 110 159 82 9 182 113 60 47 106 165 87 140 201 127 178 233 151 208 29 207 242 53 231 44 81 31 90 145 39 120 189 79 138 213 103 172 233 191 194 9 231 32 29 207 2 69 55 108 161 31 90 129 119 184 253 79 138 197 151 236 41 255 50 9 215 16 93 47 114 181 7 76 145 31 218 145 199 248 189 239 170 85 135 76 9 191 98 41 71 0 221 111 34 229 55 204 129 223 154 161 247 184 125 175 106 37 151 76 9 127 50 233 87 16 221 15 242 181 148 207 10 189 240 43 214 17 92 15 50 117 56 115 142 89 156 215 106 181 240 139 206 17 156 215 18 197 56 115 46 105 84 7 74 141 96 187 246 65 132 223 162 237 40 131 198 9 164 95 26 77 0 59 102 33 236 31 194 133 40 227 190 201 140 71 154 165 96 187 126 1 108 39 226 85 8 195 62 249 164 23 218 157 208 171 102 177 116 79 146 93 24 115 54 249 180 239 42 221 16 75 246 49 124 47 82 149 88 147 174 249 52 111 27 92 145 73 116 179 103 172 209 153 220 23 171 244 49 201 12 83 223 20 81 9 116 191 107 172 145 89 148 211 167 252 49 137 204 23 155 84 17 185 124 51 111 20 209 25 212 239 59 252 177 201 148 83 231 172 113 185 124 55 107 84 17 73 12 243 63 244 177 9 212 159 235 172 113 57 116 179 103 156 209 137 204 247 187 244 49 217 28 83 239 52 113 57 116 175 27 92 145 73 180 243 167 236 209 153 220 23 94 153 212 133 184 255 164 239 18 223 26 81 234 53 112 9 76 147 24 83 150 75 182 253 182 241 204 157 208 23 236 55 122 183 114 41 82 29 216 113 52 251 48 203 142 211 158 165 206 137 68 181 104 47 148 95 2 111 42 225 58 5 192 25 220 163 8 67 134 59 102 173 70 129 220 109 160 231 188 199 10 231 34 25 194 13 72 33 100 171 0 91 158 99 174 245 62 249 180 229 152 95 132 79 114 191 122 49 74 21 208 195 254 57 239 36 89 3 70 141 39 120 189 75 142 209 95 148 209 139 246 61 247 48 13 211 30 89 47 116 185 115 54 237 23 216 157 43 238 161 255 132 65 171 102 93 135 64 13 99 62 249 79 4 217 35 230 173 7 56 125 43 110 145 95 148 209 107 182 253 183 240 45 147 222 25 207 52 121 51 118 77 23 88 157 11 206 129 63 228 161 203 134 93 231 160 109 131 126 57 111 36 25 67 6 205 39 248 189 11 206 145 40 99 158 69 128 203 98 189 248 143 202 21 156 215 18 201 52 127 54 113 76 19 94 153 16 203 134 61 248 163 202 133 64 231 162 109 132 127 58 97 44 23 94 25 212 43 246 177 248 51 110 21 80 155 82 109 168 127 186 197 172 231 34 153 196 15 230 33 124 195 14 73 96 27 214 13 200 243 58 245 176 215 146 93 212 143 74 177 124 39 142 73 4 91 38 225 200 3 62 229 32 107 2 93 152 47 106 181 124 183 242 141 200 3 187 228 33 197 0 95 231 44 105 5 120 179 107 172 145 93 144 215 175 244 57 253 184 99 139 68 1 165 96 47 71 60 249 37 232 211 27 220 145 237 176 119 63 116 169 77 136 195 155 164 225 133 192 63 231 44 105 197 24 83 43 108 177 125 48 247 47 212 153 221 152 163 203 132 65 165 96 47 135 92 25 101 40 243 187 252 49 237 16 87 31 84 105 13 72 131 123 164 225 69 128 223 167 236 41 133 184 115 242 45 104 29 88 135 40 99 166 127 130 201 174 233 212 153 212 19 236 55 122 179 118 45 74 5 192 101 32 239 0 251 190 231 170 145 214 145 92 81 140 203 116 191 226 139 206 5 226 221 24 205 8 119 56 115 182 15 82 153 30 217 132 41 228 163 252 135 74 163 102 93 154 85 16 117 48 255 176 235 46 215 26 65 230 33 108 1 124 187 100 175 146 91 158 213 210 141 72 253 184 103 136 67 6 95 98 41 78 9 244 87 146 205 119 188 249 183 202 1 231 32 29 199 10 77 39 124 177 119 50 233 23 216 157 39 226 173 199 188 121 167 106 81 151 80 29 151 202 13 183 252 33 199 2 73 39 24 93 23 82 173 119 188 249 55 234 161 199 128 93 231 170 109 135 124 49 119 50 9 87 24 221 199 2 77 231 60 121 7 74 145 23 80 157 87 170 237 183 252 193 231 162 105 135 88 29 151 82 13 119 60 249 55 10 193 231 32 93 7 74 141 188 247 50 237 16 91 30 89 100 15 66 133 96 187 246 73 140 215 210 157 88 235 174 97 132 127 58 101 40 19 86 17 220 215 10 77 232 35 126 1 68 143 106 85 144 83 150 233 204 135 66 253 160 107 142 73 20 159 82 21 80 43 230 57 252 199 226 45 104 27 94 145 20 79 138 117 184 227 70 129 204 231 154 93 152 83 110 177 116 63 122 37 224 67 6 217 220 23 82 13 48 123 62 121 132 111 162 229 64 155 214 33 92 151 75 140 177 113 188 251 183 236 33 129 196 31 155 84 17 177 116 59 127 4 193 33 236 215 27 220 145 17 76 139 55 124 161 65 132 207 171 148 209 145 212 43 143 68 1 161 124 55 75 12 209 81 28 219 23 236 161 1 68 127 59 116 177 81 148 219 95 132 193 161 236 55 251 188 113 177 76 11 87 28 33 65 4 207 43 244 177 241 52 107 15 68 129 97 92 151 75 140 241 177 252 59 247 172 97 129 68 31 134 193 252 165 232 47 204 23 90 223 26 65 34 109 168 121 60 243 48 203 142 235 166 157 222 153 84 173 112 55 116 191 226 135 194 9 234 213 16 209 20 107 72 3 198 99 62 245 54 241 172 21 216 159 220 167 106 79 138 177 114 189 248 169 236 35 128 219 30 155 86 13 174 105 36 125 0 199 36 239 210 23 210 153 26 69 128 33 100 187 120 179 246 179 142 197 230 161 92 133 72 15 172 119 58 127 58 225 66 13 200 235 38 97 31 68 137 43 110 181 119 184 253 67 134 201 239 148 81 147 94 101 135 64 13 107 54 241 191 244 41 203 14 69 39 24 93 19 86 169 15 196 129 35 254 181 247 176 109 203 134 65 159 100 41 139 206 245 183 248 61 227 38 105 207 20 81 83 30 197 103 32 237 11 246 177 255 180 137 43 110 165 71 152 221 83 150 201 175 228 33 131 190 117 183 112 13 107 38 225 95 4 201 43 238 181 247 56 125 3 70 137 80 139 198 101 160 251 186 245 48 143 202 5 164 95 26 89 20 47 78 9 196 35 254 185 248 51 110 13 72 131 98 93 152 87 146 237 204 135 66 225 188 119 182 113 44 139 70 1 160 219 22 213 16 43 202 5 64 63 122 181 116 47 234 9 196 159 30 217 148 211 174 105 72 131 190 125 184 243 178 237 40 135 194 29 156 87 18 81 108 39 70 1 252 59 246 177 240 43 102 5 64 155 26 85 144 111 170 229 196 255 186 181 240 43 203 4 65 221 24 87 23 108 169 13 192 251 59 252 177 245 168 111 207 132 89 85 144 219 155 164 225 141 200 55 247 60 121 189 96 43 75 12 209 85 24 223 31 228 169 21 80 107 11 68 129 125 184 247 55 236 169 205 128 91 219 156 81 149 104 47 143 196 249 181 240 59 251 36 97 205 8 87 87 28 217 29 32 235 11 204 177 245 184 127 63 100 169 117 176 235 75 132 193 221 152 87 151 172 105 141 64 59 26 85 144 45 104 167 64 187 254 191 242 201 230 161 108 137 84 19 148 95 2 99 38 237 210 237 40 197 0 127 56 115 182 119 42 225 14 201 148 17 220 155 220 167 106 75 142 181 74 133 192 189 248 55 240 171 110 143 66 25 150 81 28 89 36 227 196 15 50 243 54 125 34 125 184 117 48 239 8 195 134 231 218 145 222 153 100 65 140 203 108 183 250 187 254 37 250 181 112 141 72 7 96 91 30 95 18 233 198 1 76 127 186 245 183 204 9 239 34 25 215 16 93 95 2 197 103 44 241 47 234 161 7 56 125 63 122 133 103 172 233 79 146 217 199 128 93 223 146 85 151 108 33 143 202 241 183 248 61 255 58 117 183 108 41 79 2 217 87 16 221 31 226 165 7 76 113 15 74 129 103 184 253 63 250 165 199 140 73 175 146 89 103 160 221 159 210 21 151 204 1 239 42 113 183 120 61 127 58 245 55 12 201 239 34 89 23 80 157 31 66 133 228 31 90 29 80 107 6 65 140 111 178 245 40 227 190 249 188 119 186 133 64 107 174 209 172 231 34 133 216 19 158 89 4 167 106 45 80 43 230 193 4 63 242 61 120 35 102 169 116 47 234 13 192 155 22 209 156 223 162 101 88 147 174 73 140 199 170 245 48 251 190 97 188 119 50 117 72 3 174 233 20 215 26 93 192 27 86 81 20 207 98 45 232 51 246 185 4 63 122 61 112 139 102 161 236 79 146 213 200 131 94 73 132 191 123 188 241 185 228 35 135 204 17 137 76 7 107 84 17 89 28 227 223 20 81 217 4 79 43 108 177 105 36 227 7 252 177 249 188 135 59 116 177 105 172 227 79 148 209 201 132 95 219 156 81 153 100 35 135 204 241 137 204 7 235 52 113 185 124 35 127 52 241 57 4 207 235 44 81 41 100 163 7 92 145 25 220 135 27 212 145 233 172 99 79 116 177 73 132 255 187 252 49 249 164 99 135 76 17 137 76 7 174 233 36 197 24 95 212 31 66 95 26 209 26 37 224 9 204 179 8 67 134 43 118 189 102 161 252 61 240 183 252 135 74 183 114 73 98 173 232 145 212 27 128 219 30 147 94 5 158 89 20 85 40 239 196 15 50 207 10 65 42 117 176 121 60 227 56 243 182 251 198 141 54 113 140 109 160 231 76 151 218 199 130 89 210 157 88 161 100 43 144 171 238 131 206 53 142 73 4 165 120 63 116 63 226 191 250 49 250 5 64
|
| 33 |
+
resample.1.expected=44 92 187 45 95 201 53 110 225 72 140 255 100 177 255 135 243 214 173 255 57 197 208 0 209 47 23 247 0 74 255 25 90 199 63 108 51 49 131 0 44 155 0 67 183 35 95 197 75 133 209 109 181 245 135 228 255 157 255 216 185 255 90 205 220 31 223 75 62 253 0 86 255 0 81 216 57 105 106 106 153 26 100 165 8 58 157 5 75 196 8 164 255 64 239 255 141 255 255 162 255 167 141 242 23 174 167 0 255 72 61 255 3 91 255 0 53 186 24 75 62 80 158 21 91 179 68 65 145 93 63 183 71 117 255 97 186 255 162 247 127 173 255 12 143 255 0 162 214 8 231 102 73 255 6 95 255 0 67 157 0 81 26 59 145 0 120 169 75 164 147 95 209 145 44 245 169 8 255 185 71 120 152 74 129 164 87 147 186 112 156 232 138 155 240 151 195 183 153 239 64 155 184 14 167 55 60 205 6 108 232 40 123 171 76 142 52 74 163 7 79 165 24 100 157 79 111 155 116 122 165 145 142 198 161 175 224 164 227 184 168 243 95 169 197 53 177 70 77 209 4 106 237 28 121 196 80 148 98 113 180 33 118 171 31 103 144 45 113 157 59 157 208 109 193 235 172 205 240 174 213 158 134 207 50 142 151 24 202 69 83 233 19 103 226 25 71 158 70 97 79 119 175 64 126 187 110 98 136 122 85 148 90 116 212 109 167 197 173 213 96 180 239 21 139 226 14 145 162 59 203 73 114 238 16 121 214 14 85 138 42 94 57 84 154 54 132 173 117 176 142 126 205 129 73 217 144 37 218 156 115 167 95 123 186 104 141 207 120 177 183 149 201 122 157 177 117 130 120 156 76 88 141 68 100 70 120 136 42 162 158 67 176 124 100 198 56 117 215 48 139 181 91 155 115 151 140 86 184 105 92 203 80 119 203 90 135 176 132 128 140 175 104 109 157 89 101 65 100 137 31 138 180 78 187 161 117 217 85 125 224 44 148 181 69 179 123 111 177 92 146 147 102 186 116 127 223 107 159 193 126 142 124 149 97 90 123 86 107 64 118 119 45 122 121 87 102 111 146 135 107 186 201 134 186 197 177 154 124 169 122 91 122 115 114 130 135 99 188 156 48 188 157 40 130 122 85 116 77 147 157 28 184 167 36 165 135 88 114 108 121 114 110 124 166 148 147 178 190 192 135 177 195 104 121 170 104 84 151 109 146 198 37 159 227 42 184 253 55 230 202 68 250 89 80 192 41 92 82 76 113 20 107 141 33 104 176 64 105 197 83 117 206 84 135 235 80 156 254 105 187 188 159 203 66 206 163 13 229 81 20 240 14 47 231 10 58 177 40 92 100 102 137 42 116 141 27 80 124 65 88 155 117 150 229 123 165 255 84 137 241 71 166 169 113 232 87 175 216 25 228 115 9 251 30 30 251 12 76 186 49 125 95 103 158 32 107 163 18 70 153 14 86 135 21 163 131 70 229 163 147 248 200 215 234 187 243 198 117 209 143 54 148 96 27 144 90 9 191 104 12 179 86 73 105 31 167 79 5 235 112 0 245 103 70 200 61 166 142 83 198 127 169 163 158 240 156 171 251 190 142 222 175 93 178 127 60 148 97 44 115 156 21 130 186 29 156 219 51 200 179 71 216 80 91 162 36 134 60 76 187 4 130 201 18 164 172 50 183 149 77 186 147 108 168 186 139 146 222 157 161 166 171 188 40 171 149 0 172 51 15 184 0 61 195 11 89 150 37 144 65 81 207 14 111 193 21 132 128 58 174 117 94 222 169 116 203 190 119 135 166 118 128 102 134 173 38 176 151 13 223 54 33 224 0 54 190 8 65 115 66 124 42 127 203 11 134 215 25 103 163 29 128 124 24 211 127 82 255 131 185 242 124 253 206 129 253 164 132 197 98 101 131 44 52 120 53 22 157 114 39 137 115 110 62 58 199 52 29 241 111 37 221 114 102 176 67 177 142 90 200 115 186 167 106 241 142 141 225 148 180 210 146 134 206 138 44 204 130 0 46 71 64 58 94 75 81 129 103 111 134 138 124 109 168 107 106 217 71 144 250 55 182 214 72 208 113 100 231 51 128 235 42 167 184 87 196 103 140 177 85 127 125 123 59 71 111 33 53 45 75 75 37 140 117 97 192 109 120 239 55 115 255 44 136 209 87 193 107 117 241 48 119 247 51 135 202 59 164 125 53 156 69 28 120 50 13 118 34 61 142 5 150 131 29 170 84 94 124 29 161 136 4 201 207 41 185 209 116 144 134 142 148 93 116 197 101 132 215 73 195 176 25 222 129 60 191 86 161 137 25 204 91 0 166 84 47 124 104 174 116 79 213 139 25 178 172 53 133 174 151 123 139 186 128 115 144 134 114 128 138 81 159 132 38 163 106 102 139 80 224 153 119 199 190 188 68 214 222 0 6 46 136 15 61 131 38 95 131 63 139 153 81 167 187 97 177 236 114 176 247 133 176 183 153 182 56 154 207 0 143 222 0 165 174 47 198 83 97 167 50 118 77 89 109 5 104 100 0 86 123 26 119 169 79 191 219 93 196 243 72 143 234 95 146 164 159 211 70 176 239 11 141 204 2 132 161 16 155 126 31 137 75 36 82 21 46 68 6 120 93 14 222 93 83 233 56 172 159 24 235 132 29 244 171 94 206 155 185 154 80 214 128 64 176 138 106 158 152 78 176 142 5 160 112 40 109 71 176 85 17 250 93 10 223 92 67 187 70 199 178 30 255 173 8 253 165 62 216 149 169 198 119 227 170 100 209 133 87 176 111 43 159 97 0 135 60 74 115 24 217 129 91 218 158 198 102 177 253 20 20 104 209 30 117 170 55 146 111 88 202 84 118 233 116 144 214 164 172 139 174 207 96 122 230 87 30 183 117 7 96 149 50 84 145 98 141 107 121 137 87 148 59 109 173 16 139 160 37 168 126 71 210 114 100 251 129 110 218 131 110 139 115 147 125 71 202 174 31 200 171 26 140 107 58 95 94 101 87 139 130 66 157 137 38 121 134 58 100 159 113 87 198 142 127 192 130 196 137 114 238 108 116 224 113 145 180 77 184 127 20 188 78 52 155 60 155 137 102 164 136 168 85 96 183 79 41 148 167 64 96 205 151 86 176 156 102 167 69 163 195 3 202 207 15 229 197 71 232 197 144 231 190 199 226 151 218 203 77 214 154 17 201 80 6 195 19 66 187 0 154 161 66 175 128 149 130 110 192 94 78 164 238 92 175 177 119 198 75 148 247 10 165 255 37 178 205 77 193 77 93 229 12 79 253 6 56 179 47 82 48 92 150 13 134 175 83 158 151 115 151 165 80 141 210 83 168 188 130 227 99 152 255 38 140 247 31 131 183 29 141 108 22 170 94 13 195 130 22 186 105 74 136 32 154 78 47 216 38 155 236 16 239 225 26 239 199 83 212 157 159 159 116 212 145 104 222 173 107 212 186 104 194 157 79 167 129 28 137 104 0 111 49 62 91 6 198 88 65 246 88 195 188 46 251 147 4 229 160 63 185 149 202 170 118 215 144 125 96 124 171 25 127 202 61 157 206 103 186 234 126 203 254 153 239 203 186 255 85 207 198 25 216 76 49 241 3 85 253 15 103 194 70 132 100 107 157 44 127 168 156 142 193 175 151 143 199 169 59 193 207 4 152 212 29 135 154 58 139 37 77 171 0 100 189 20 135 142 79 182 55 128 232 33 179 203 87 211 124 131 186 120 137 138 181 166 148 166 205 209 63 199 220 8 153 173 30 126 104 59 135 62 64 144 79 62 137 129 64 142 115 119 147 53 210 122 65 252 80 163 228 57 248 196 75 255 169 126 230 104 186 162 36 230 125 45 241 131 121 228 117 152 195 81 109 142 84 50 86 123 33 49 87 82 36 7 185 41 32 240 47 160 226 22 226 200 2 218 188 57 201 169 180 203 147 206 184 132 133 144 127 101 115 135 144 106 141 173 108 182 165 119 215 151 168 191 145 214 108 139 179 70 146 73 100 179 22 126 216 57 131 171 111 156 76 135 190 15 147 207 213 64 110 237 72 94 255 86 70 203 112 63 93 121 88 45 91 103 46 38 114 73 43 156 87 104 219 99 173 255 111 214 255 120 242 178 136 244 65 165 186 49 203 104 120 231 92 120 232 139 40 192 139 30 129 79 109 101 26 181 110 23 192 98 80 174 65 160 131 91 180 146 163 140 204 198 124 205 178 157 136 154 186 93 153 181 84 163 160 39 178 116 0 189 91 33 183 86 162 163 52 222 128 23 179 87 62 125 47 170 111 20 167 115 8 60 143 18 23 171 35 93 195 35 140 212 33 147 220 56 157 232 114 180 229 150 200 172 161 197 83 185 153 43 220 85 43 239 34 79 222 27 108 171 66 131 104 119 136 48 122 132 39 77 138 63 64 165 116 109 205 116 168 220 67 204 209 32 220 197 203 18 79 227 25 91 243 38 115 176 55 144 48 69 162 0 77 155 2 92 143 37 119 169 60 162 221 116 201 242 186 224 228 196 236 143 160 224 47 165 157 39 217 68 110 234 51 116 195 98 57 133 108 76 80 60 180 82 23 255 119 33 255 105 96 227 52 179 162 77 210 133 174 178 144 232 144 134 220 134 88 199 113 62 189 88 62 171 88 31 146 102 11 124 95 55 101 59 178 79 6 242 54 0 220 39 59 191 30 190 186 19 215 168 7 125 143 21 67 135 56 90 149 77 119 170 81 131 189 83 133 228 98 134 249 127 166 181 167 204 51 200 168 0 213 73 5 220 4 35 205 3 48 145 39 95 60 87 163 0 106 180 1 99 158 28 109 175 86 154 231 103 208 232 80 243 187 59 255 156 126 38 131 143 44 150 159 58 181 120 72 213 43 89 215 17 129 183 42 181 134 95 186 113 143 153 122 202 125 136 253 124 146 218 143 121 133 157 87 114 115 109 167 45 173 172 47 173 109 119 118 45 163 125 24 146 201 81 113 243 172 97 212 182 121 175 123 166 135 113 170 87 174 126 57 198 100 78 168 101 131 152 68 150 157 28 143 142 58 103 100 151 85 62 155 96 37 62 154 23 0 192 16 8 198 26 71 208 41 166 224 41 199 230 31 167 205 52 147 171 101 168 122 126 196 90 123 201 91 135 159 136 160 95 179 165 94 142 146 146 42 129 137 12 119 67 53 113 34 82 100 67 79 65 117 113 26 155 178 21 154 191 65 131 153 114 133 148 160 174 176 147 209 168 106 220 131 75 221 108 46 100 184 57 107 194 75 121 207 82 127 229 78 128 228 82 178 184 107 242 106 162 217 43 223 110 21 255 30 34 255 21 72 182 62 114 81 109 149 59 98 194 109 49 235 113 70 221 52 163 169 0 235 154 3 242 177 93 205 163 216 153 106 250 123 85 197 121 100 149 107 63 150 71 6 138 64 55 109 76 190 98 42 246 97 3 232 86 57 187 56 197 169 26 210 147 9 92 132 8 24 130 22 64 145 49 112 173 76 142 201 84 160 246 82 174 255 108 197 213 159 228 102 169 255 21 146 253 11 165 181 57 209 78 111 189 50 114 103 100 70 42 114 72 30 80 127 25 93 149 15 157 130 12 215 136 30 233 167 82 202 161 157 151 118 217 133 102 240 161 114 197 171 104 141 154 76 104 141 61 30 165 157 42 176 153 69 189 148 109 167 172 140 124 196 140 153 163 130 220 78 148 194 19 188 77 16 197 2 35 169 18 64 102 83 125 39 144 192 43 139 227 101 88 224 125 101 194 94 187 160 54 250 139 50 249 126 112 202 80 209 136 23 235 78 38 191 54 111 128 70 108 98 100 44 108 106 68 134 73 184 121 20 236 63 0 232 28 56 210 30 177 207 28 203 190 19 133 155 29 106 121 59 153 104 91 181 105 119 166 123 136 150 180 149 149 221 179 160 191 212 176 78 197 191 8 144 190 25 134 148 85 160 94 138 132 92 153 52 139 131 18 148 130 41 117 161 57 134 158 53 198 123 53 239 113 77 227 128 126 186 107 184 142 66 224 120 69 228 123 122 204 110 116 181 81 61 166 66 24 84 202 73 95 220 61 122 232 49 168 182 82 198 91 139 173 81 131 111 140 67 75 140 46 76 71 89 68 49 114 48 99 110 25 175 143 23 223 198 66 205 197 137 139 148 189 126 114 198 178 113 169 209 107 131 183 80 125 133 30 152 80 0 155 23 45 129 1 157 78 70 194 44 183 143 105 194 111 209 91 133 195 13 150 65 24 167 0 65 181 32 119 192 64 152 204 66 163 200 83 187 153 118 225 83 137 240 34 147 215 33 169 162 81 205 103 138 234 67 132 236 59 65 196 58 49 128 61 111 77 86 184 57 131 223 36 187 223 21 226 189 59 209 157 132 155 141 174 142 115 171 175 80 153 188 72 144 160 80 147 131 49 158 111 19 156 96 57 142 77 178 163 51 179 201 26 82 223 15 14 163 183 18 154 204 8 142 220 4 166 173 43 201 80 109 173 49 125 85 89 97 23 112 99 12 106 144 16 132 160 22 190 140 29 241 137 48 255 150 102 219 121 178 153 60 233 122 55 243 137 101 209 153 118 154 140 91 103 112 41 79 75 16 88 27 67 107 9 170 78 90 231 26 221 216 88 226 164 219 105 119 217 38 113 81 74 143 10 104 159 60 103 150 114 121 171 137 163 203 162 196 166 186 212 73 174 223 2 147 215 0 160 152 46 208 56 114 229 5 122 200 12 86 163 12 105 126 7 191 76 62 248 30 163 245 11 238 223 37 252 193 108 219 146 193 168 94 235 132 78 226 125 84 206 134 80 194 135 52 181 116 5 163 77 0 120 51 59 73 39 195 98 24 206 161 7 111 201 0 42 233 110 25 185 128 30 100 153 50 82 145 88 142 108 128 144 87 156 72 103 165 28 136 156 44 174 138 85 215 127 124 241 121 126 235 101 106 203 68 133 159 31 198 119 0 222 87 50 185 77 146 136 109 187 95 158 164 50 177 108 23 153 74 74 109 84 158 83 134 147 114 190 55 179 218 48 174 199 134 102 163 158 83 164 101 131 193 68 165 175 98 154 112 152 139 105 203 131 149 240 119 138 243 111 70 191 130 27 120 153 48 111 113 107 159 30 176 164 15 178 116 75 134 111 95 147 150 69 214 154 94 227 115 169 170 86 204 135 95 182 134 132 157 100 177 146 44 187 112 74 168 76 155 172 113 152 204 184 59 231 166 0 223 59 12 156 3 68 62 25 146 35 39 162 71 24 124 102 15 92 255 43 77 186 61 101 55 97 143 5 138 162 81 159 147 118 151 170 88 136 214 82 163 187 125 222 97 189 252 66 239 230 94 222 170 82 157 108 24 140 81 0 175 91 0 169 76 87 106 48 203 59 91 249 45 190 231 18 246 178 1 229 144 82 196 117 209 173 111 215 147 134 103 128 170 31 117 189 57 116 191 103 132 214 135 155 239 138 188 192 127 201 94 156 164 58 216 89 96 255 31 107 242 18 79 167 53 81 75 102 129 59 91 193 114 39 242 112 65 228 54 165 170 76 203 154 177 161 179 232 134 162 211 144 94 176 137 58 160 108 65 151 100 42 145 110 7 124 78 81 101 32 214 118 100 218 170 231 91 233 218 18 255 66 53 198 0 91 78 45 101 5 82 115 7 68 126 23 56 130 218 43 145 163 70 178 64 122 220 23 185 200 83 213 119 132 186 121 141 135 189 160 144 168 196 203 60 232 214 33 250 154 97 220 77 117 157 22 74 111 44 55 101 120 67 101 134 138 96 91 223 94 104 246 90 187 225 52 232 197 15 220 193 72 212 181 187 221 158 211 192 132 134 145 117 75 140 124 87 173 135 137 168 173 186 123 201 182 130 174 132 173 107 119 151 76 152 65 99 177 12 108 162 27 101 98 79 126 26 131 181 44 125 227 133 84 234 143 106 206 77 190 157 85 226 119 178 189 107 229 136 112 210 102 111 193 102 87 195 121 45 186 121 2 167 90 0 149 35 74 134 0 191 118 69 208 109 199 125 150 208 75 203 96 100 178 43 129 80 93 132 24 125 131 36 113 130 54 102 127 144 91 194 135 129 223 117 192 252 111 250 191 130 247 67 166 184 46 204 103 122 228 93 124 226 141 43 201 135 39 163 62 125 137 3 183 122 0 183 73 57 172 17 183 166 46 223 178 144 169 199 204 133 183 199 157 153 130 164 156 59 155 197 56 171 236 122 211 234 154 217 170 137 197 76 142 208 39 180 237 37 220 180 80 230 64 119 189 37 143 113 104 144 63 119 139 54 67 141 59 60 130 58 118 122 29 180 147 1 210 188 67 188 195 194 139 157 226 123 127 156 148 113 123 162 72 160 146 31 168 99 86 143 57 190 147 101 189 185 190 77 209 189 0 211 87 14 223 4 65 227 0 118 159 43 150 53 123 154 37 159 141 104 138 136 123 126 164 74 144 202 79 148 181 132 138 124 166 131 86 124 118 182 146 147 201 181 194 216 182 241 154 150 234 45 164 163 31 216 68 105 234 52 119 194 104 64 124 106 72 64 44 156 65 12 230 108 16 252 80 102 238 7 219 206 41 255 171 178 205 145 255 153 106 250 147 72 183 144 92 118 140 153 87 150 223 105 166 246 120 182 173 124 186 47 154 215 0 200 235 0 231 165 53 220 43 99 162 16 150 82 94 187 26 124 190 12 91 169 22 113 141 37 196 127 32 250 138 27 244 153 98 204 128 216 156 70 252 115 62 197 91 101 153 83 71 157 81 2 147 53 66 124 22 221 123 93 240 147 212 110 174 209 29 187 81 57 225 3 86 250 22 95 175 68 135 41 106 189 3 149 189 73 181 149 111 185 156 93 170 206 127 150 175 194 142 93 234 139 41 186 94 107 206 96 115 228 102 125 193 141 106 116 176 72 112 134 96 167 47 164 175 41 171 110 121 122 37 157 121 6 127 180 50 111 231 134 117 235 144 155 206 86 200 159 88 200 114 169 165 84 212 147 52 205 166 23 181 199 37 168 203 82 162 181 139 160 113 167 138 92 121 103 93 37 92 132 16 109 157 55 141 134 121 157 84 170 120 96 204 56 164 215 30 173 195 60 127 158 101 139 128 126 211 112 119 239 108 97 200 99 116 154 56 171 121 13 186 87 45 159 50 143 151 33 135 177 31 39 197 21 49 191 13 165 154 70 196 104 154 120 86 148 76 85 66 104 127 32 135 168 79 154 146 138 191 82 175 225 79 201 203 143 209 138 163 189 107 133 157 118 139 131 95 180 125 45 207 126 17 233 49 35 239 31 39 232 11 59 163 47 93 67 119 133 55 113 185 110 51 228 116 63 216 52 164 169 0 227 153 4 223 176 72 209 185 166 198 159 203 174 127 173 143 97 146 107 70 152 88 44 152 113 25 150 170 12 158 243 14 177 255 30 204 204 54 212 66 78 166 11 77 82 9 68 22 48 85 14 74 134 56 109 194 110 148 240 103 197 242 55 236 203 63 210 151 141 148 113 210 133 102 229 172 104 202 185 98 153 148 71 123 106 20 125 72 0 114 49 57 94 31 187 116 16 202 174 3 103 236 12 56 253 36 103 184 65 139 65 85 136 10 78 130 5 66 140 43 80 173 78 131 218 113 196 240 145 243 224 188 244 181 227 202 127 211 148 79 157 112 45 130 93 24 145 90 14 160 94 13 173 14 36 183 10 51 185 13 92 128 53 153 43 111 210 38 114 235 103 82 227 130 109 195 92 199 159 55 251 140 57 237 133 102 206 108 170 174 68 201 128 73 187 79 104 168 40 97 164 28 44 171 62 16 174 124 24 162 198 32 132 215 33 148 164 40 187 35 62 159 0 102 69 16 150 10 52 165 20 61 164 79 111 184 143 189 221 143 236 205 93 232 138 104 184 73 189 128 49 249 98 77 245 108 130 200 143 146 142 162 108 125 130 43 142 56 16 121 19 64 66 31 180 62 26 210 113 0 143 186 20 97 223 77 118 160 98 156 36 79 185 0 74 181 37 103 156 75 130 166 77 151 207 117 189 207 190 223 163 234 211 141 230 155 144 186 96 128 137 61 85 128 49 44 154 50 24 176 56 19 54 11 90 78 41 124 113 93 185 101 136 241 50 140 255 63 129 226 141 127 166 196 156 125 197 204 112 166 218 104 133 182 83 124 132 39 143 85 4 143 52 58 129 32 164 144 17 173 185 6 80 236 21 30 244 55 60 182 96 86 67 113 90 43 92 91 111 39 106 126 45 163 72 91 231 56 117 217 106 108 138 177 135 109 222 191 139 210 201 127 147 159 62 136 114 5 184 89 0 206 61 66 174 48 175 120 119 224 79 217 187 119 200 111 195 76 64 180 15 74 74 51 141 14 61 168 30 29 144 89 54 142 139 125 181 112 150 220 36 121 234 51 117 201 143 150 144 179 156 123 141 129 142 142 129 125 194 144 84 208 134 105 172 93 178 119 50 220 88 22 197 125 17 128 189 24 69 230 31 38 2 69 118 30 113 155 78 180 213 95 203 244 78 158 225 106 133 160 183 149 93 243 162 70 253 157 89 216 152 102 154 140 86 107 108 41 93 63 9 74 31 72 59 18 193 97 15 212 169 13 126 238 16 81 244 25 121 166 37 158 36 57 164 1 74 149 74 94 131 112 120 164 91 148 218 111 154 188 182 140 86 243 139 50 255 148 92 225 122 101 160 70 56 132 60 11 143 92 9 131 72 72 86 23 184 38 86 249 22 221 236 99 227 183 206 101 135 201 36 121 84 82 140 1 106 143 0 92 129 49 122 153 109 182 206 115 190 233 83 143 212 116 126 157 211 145 96 234 129 81 177 81 107 144 76 96 158 113 60 165 109 90 147 54 180 96 12 247 46 2 249 89 11 185 179 20 117 236 27 77 60 175 92 74 199 112 101 227 139 115 208 138 116 141 110 149 117 65 204 137 40 234 118 60 218 66 112 165 78 152 97 145 156 51 174 121 39 154 84 28 93 101 19 38 165 51 20 184 108 39 155 149 53 148 146 51 186 111 56 220 64 84 221 60 130 177 104 182 108 125 184 83 121 147 102 149 125 84 204 127 34 231 113 46 207 77 115 167 35 154 120 5 139 95 55 103 87 152 82 63 143 93 26 46 148 0 45 198 9 150 222 75 180 222 150 114 204 150 76 196 82 106 194 32 142 162 41 168 113 97 208 119 167 233 163 187 200 169 158 121 119 155 79 65 189 81 33 187 57 66 146 29 134 107 63 150 96 163 113 136 164 103 186 61 140 144 0 178 36 5 192 30 31 174 115 39 146 174 44 128 167 239 44 154 239 46 137 225 47 128 173 36 141 106 22 167 94 13 188 121 30 187 84 82 157 7 154 104 39 206 48 171 220 20 247 195 20 247 155 21 168 130 13 85 135 25 55 145 54 89 156 63 117 177 59 116 202 83 116 230 133 130 233 156 175 178 151 223 84 138 194 17 138 106 5 157 77 6 178 112 20 164 104 81 115 41 166 86 0 220 81 0 223 71 80 195 45 209 164 17 212 132 2 98 123 5 42 139 35 91 173 67 134 206 89 139 221 89 127 231 85 122 224 89 154 165 118 213 84 175 248 70 234 230 110 246 171 106 206 99 44 155 53 2 119 38 2 97 7 73 86 1 170 58 66 215 33 212 188 106 223 141 224 97 114 203 15 108 57 36 122 4 70 142 68 78 151 118 83 155 243 186 9 202 189 14 136 184 25 101 136 44 131 72 61 146 77 64 131 133 70 133 116 114 144 44 184 132 69 219 95 189 205 69 246 171 61 234 145 50 177 143 33 138 158 33 129 155 55 155 132 69 183 130 86 188 137 139 163 167 209 122 190 215 125 165 163 154 101 123 125 50 121 53 42 130 63 48 125 140 71 86 155 128 33 92 198 43 44 229 102 41 215 106 103 198 43 199 195 0 213 186 6 148 165 35 105 144 75 126 129 88 166 137 80 195 158 79 179 171 107 134 158 143 140 102 180 192 37 214 198 44 228 144 108 216 114 122 178 121 70 120 113 31 62 89 31 38 33 84 44 12 161 23 55 215 0 181 221 67 209 190 199 127 151 206 69 133 93 84 142 49 112 145 102 125 134 142 133 127 255 67 11 202 91 31 101 126 74 54 108 139 104 52 183 109 72 174 66 160 134 89 186 138 172 141 186 218 136 188 199 184 137 165 183 87 139 147 73 105 137 130 78 171 206 72 195 194 94 201 95 136 220 43 182 233 37 230 181 73 253 79 118 214 28 144 134 35 146 89 28 153 90 19 172 96 83 169 78 190 152 32 232 162 0 192 193 51 143 182 164 122 141 177 123 135 82 149 173 19 166 225 42 173 239 86 186 194 123 209 101 129 237 51 112 248 60 109 207 67 136 132 50 172 109 19 198 135 4 199 104 53 169 33 145 133 58 190 100 167 166 65 217 128 32 198 107 22 117 105 28 56 126 14 44 163 0 103 199 40 146 219 131 147 220 163 135 220 134 133 215 140 141 168 186 157 112 213 166 81 242 10 77 173 41 102 64 93 152 31 102 219 110 68 253 118 93 229 54 174 166 80 211 131 193 184 137 255 154 135 240 146 109 211 130 62 199 113 26 173 129 92 143 168 203 121 187 192 120 177 59 157 195 0 207 218 1 236 171 37 217 67 77 153 6 130 79 7 185 62 17 233 95 44 255 110 115 240 84 207 193 34 252 159 9 239 152 76 210 136 194 194 114 213 168 113 131 143 137 80 144 201 108 168 248 154 199 205 181 218 78 174 232 6 142 224 19 127 170 35 141 94 27 157 78 16 159 112 26 159 78 79 146 4 164 102 40 222 44 178 232 14 254 212 19 251 187 27 179 153 28 105 134 28 66 134 39 79 155 59 130 197 94 180 235 133 188 244 175 154 213 206 136 156 224 149 113 229 159 91 190 67 191 129 81 204 42 108 223 48 122 232 160 118 214 184 135 182 113 164 143 109 167 91 189 133 51 216 95 84 180 83 154 173 119 138 210 171 45 228 178 44 206 131 129 158 101 132 110 88 43 99 115 22 119 146 62 130 141 90 107 102 95 58 89 134 18 109 202 56 121 245 146 128 241 181 148 207 149 178 163 92 192 114 69 192 86 101 199 116 167 220 172 191 230 171 164 204 114 154 176 117 181 139 168 218 122 151 236 121 62 205 127 26 145 122 67 113 81 111 121 33 124 108 67 116 78 155 103 116 157 108 186 86 144 162 71 186 49 139 219 0 189 235 19 207 235 40 178 220 35 134 198 57 120 155 102 139 113 118 186 126 113 224 174 141 208 170 201 143 110 212 101 104 183 103 148 160 111 179 139 170 255 100 158 255 52 139 237 91 136 185 205 149 120 237 149 98 173 124 109 134 98 69 151 76 1 142 46 62 110 33 205 123 114 218 178 227 88 226 216 18 227 85 57 170 12 80 83 10 68 29 45 94 24 75 147 29 114 156 23 156 130 4 201 140 0 235 184 75 231 194 199 195 155 247 162 117 218 146 95 168 125 56 148 111 27 136 134 100 138 188 223 151 248 225 171 255 107 197 207 51 219 97 94 239 26 104 239 21 71 190 34 84 115 39 149 87 20 207 106 4 230 73 76 217 9 203 183 75 246 148 222 196 134 230 132 140 91 111 164 9 118 197 43 147 222 68 155 242 62 143 234 95 163 164 154 206 68 167 233 54 138 222 111 145 185 99 189 133 19 179 83 59 129 55 178 97 46 252 128 234 200 115 203 191 103 147 164 139 109 100 202 110 40 212 91 51 162 56 113 105 57 110 79 101 51 91 88 87 126 33 195 141 78 203 128 191 102 148 182 36 183 55 56 150 0 101 60 19 138 10 52 164 24 67 173 48 118 157 58 198 131 54 255 125 60 255 129 118 234 109 213 169 63 251 143 37 237 156 37 215 131 20 214 72 2 193 65 67 160 109 177 144 180 191 151 221 110 177 181 72 197 59 114 196 0 130 165 19 114 112 55 136 58 67 193 73 55 228 129 46 226 98 95 218 4 190 213 40 243 201 188 238 180 224 194 151 125 154 121 61 143 123 84 160 145 108 161 172 112 143 170 138 161 105 169 203 18 160 194 30 118 140 123 106 129 126 135 161 44 149 124 60 140 43 159 128 0 223 144 240 69 151 205 65 162 135 56 167 67 26 154 43 8 132 10 54 105 0 144 54 55 183 13 181 160 79 187 139 200 76 143 202 35 128 85 95 102 43 107 90 106 58 108 116 54 163 59 93 218 50 114 205 106 110 138 154 144 101 171 209 109 166 237 108 158 213 73 161 148 35 184 86 5 193 113 0 195 192 12 210 182 17 232 75 17 236 17 36 212 28 80 164 76 110 113 138 126 112 124 156 139 43 196 126 27 196 66 98 165 21 157 160 6 169 184 75 154 176 176 133 143 164 124 146 57 143 190 33 171 238 116 203 247 167 223 195 149 225 99 127 224 46 126 216 51 140 179 65 157 130 58 161 123 22 140 146 0 107 106 60 75 32 187 56 73 217 67 208 152 128 197 102 192 69 117 219 0 131 128 199 0 142 177 6 156 129 28 138 76 37 90 49 43 72 12 92 89 11 169 67 83 214 29 230 206 98 242 163 225 114 122 211 29 112 65 51 136 1 84 145 65 100 137 107 122 178 99 147 235 127 147 199 194 135 87 237 143 52 238 166 105 221 158 126 198 116 78 162 69 33 131 54 13 120 117 9 131 205 18 157 187 34 181 68 51 209 0 51 217 0 55 162 42 91 73 113 160 57 115 218 104 63 241 105 77 220 45 170 173 7 232 143 7 233 139 84 217 126 189 202 108 201 170 114 123 139 148 75 132 208 102 153 245 153 190 198 188 218 81 183 227 14 143 206 22 131 150 37 147 88 31 131 81 17 84 113 23 65 77 98 83 4 213 66 52 255 32 207 228 94 218 158 200 104 118 254 21 97 69 132 37 73 134 66 77 135 115 58 143 145 37 147 135 69 120 144 139 99 172 156 125 176 129 212 150 131 211 137 167 123 153 147 71 186 80 95 209 52 139 178 90 166 113 137 163 118 172 138 176 212 118 149 244 114 48 239 105 39 205 85 127 173 56 182 149 31 156 116 52 116 78 115 86 65 170 60 74 176 49 82 118 57 83 39 83 117 14 109 160 46 135 128 108 168 48 174 202 45 166 217 120 112 206 157 114 172 127 188 129 90 224 92 69 205 78 96 199 112 158 216 164 181 221 165 163 201 118 153 164 118 170 119 160 209 104 142 238 112 67 209 120 44 129 110 87 84 72 123 81 35 125 58 62 109 30 133 100 63 138 119 155 83 171 147 80 202 52 149 218 52 165 199 142 127 177 197 98 162 27 95 133 20 115 168 16 155 221 8 212 242 23 242 202 98 228 162 200 184 140 244 150 109 228 150 82 162 145 113 104 132 184 91 133 246 124 150 253 139 184 184 131 208 83 163 173 61 224 95 115 254 67 108 233 90 43 177 83 59 115 36 159 86 0 229 88 0 228 79 74 202 47 192 172 31 216 132 32 135 103 21 53 90 7 39 103 44 72 158 112 130 221 109 199 242 55 238 209 67 212 161 157 148 124 221 127 101 221 162 90 187 163 60 149 128 33 126 131 104 135 178 223 150 235 224 169 254 107 202 201 47 235 92 85 255 20 97 242 11 73 189 23 100 114 31 172 61 21 225 40 11 231 8 66 209 1 164 181 73 213 147 214 191 131 222 138 135 99 104 161 45 114 199 95 149 225 135 175 236 50 144 206 52 159 223 59 189 239 56 230 223 62 246 166 124 230 104 217 184 72 255 127 62 240 84 98 172 108 140 114 172 166 121 183 196 181 134 211 185 137 165 133 185 88 142 156 76 206 60 129 220 40 130 159 100 81 84 122 99 33 80 182 44 41 228 98 35 214 107 98 203 52 199 208 8 203 195 5 117 165 5 68 128 13 105 108 67 160 149 143 202 215 146 231 220 96 228 151 102 188 76 180 132 42 232 107 63 230 114 114 212 99 96 198 64 30 185 67 70 174 113 196 160 184 214 144 226 115 172 177 68 219 59 111 217 0 127 162 14 108 130 47 130 129 62 192 119 53 234 92 43 239 37 80 232 11 159 225 61 221 202 187 235 165 215 195 137 133 141 118 92 140 148 126 183 194 153 214 223 124 232 229 143 227 216 170 211 181 164 190 124 127 166 71 137 146 17 188 124 5 198 81 52 168 38 174 151 101 197 168 219 114 201 206 78 221 73 123 179 38 143 99 119 127 90 124 147 143 44 194 142 45 190 69 135 142 7 192 139 0 180 182 54 146 181 163 121 140 177 125 141 86 158 187 11 151 220 12 117 209 43 132 158 90 196 96 160 237 95 216 229 140 205 193 136 148 149 71 133 112 6 175 87 0 185 76 68 157 67 180 158 51 174 196 30 64 238 30 34 242 54 117 194 106 161 102 151 142 88 119 154 143 41 200 129 29 195 52 100 146 65 159 128 165 167 157 219 143 169 204 114 161 125 106 175 55 133 213 44 171 250 109 203 237 152 221 187 148 224 94 167 215 73 202 207 116 223 201 154 198 240 197 218 218 176 238 172 132 218 110 77 158 68 45 127 73 10 133 102 10 115 79 72 79 24 212 103 82 213 175 211 85 226 197 26 217 48 93 150 0 160 68 84 186 56 108 212 103 55 230 108 71 209 54 165 163 10 232 139 10 238 143 77 218 133 194 194 111 214 170 116 132 154 147 65 134 188 75 120 203 121 162 153 178 227 68 230 243 48 247 190 94 217 115 104 161 59 60 133 52 13 141 80 10 119 86 89 75 50 206 83 19 207 142 11 100 219 17 47 247 35 98 188 80 151 58 128 179 27 112 215 100 64 245 95 78 219 16 164 156 40 229 129 170 239 147 253 217 132 255 186 89 189 156 92 105 145 146 63 143 222 87 152 249 132 195 199 173 247 67 219 238 28 247 191 87 255 157 134 238 139 124 234 116 125 216 74 126 176 23 130 129 2 129 96 60 110 76 150 97 49 138 112 22 42 179 50 43 168 114 143 95 164 162 83 165 84 155 116 59 224 56 108 247 57 120 238 115 92 204 152 109 161 139 176 126 104 214 94 78 210 75 94 205 107 164 215 161 189 224 165 165 210 119 152 172 114 175 114 149 208 124 118 231 177 39 231 174 37 202 99 115 163 31 162 122 0 144 98 42 105 87 134 84 62 150 109 30 74 166 29 9 166 62 5 113 125 29 109 169 70 166 131 131 208 32 181 212 30 169 206 125 120 198 147 108 165 84 158 127 71 205 143 133 226 187 187 233 160 216 227 69 188 217 27 136 204 55 121 163 119 153 114 175 189 125 147 208 179 40 219 182 42 222 137 139 221 106 195 237 32 78 203 21 105 142 7 154 98 0 211 98 0 231 90 90 215 59 209 178 26 202 142 6 86 134 17 34 122 51 90 116 90 142 151 111 152 212 94 152 255 68 149 252 89 136 204 156 124 137 212 134 96 226 158 91 198 153 61 155 130 23 126 138 89 135 183 212 152 240 218 169 255 105 206 206 58 243 98 107 253 65 98 225 107 37 172 104 59 115 35 163 89 0 232 86 0 229 78 67 197 49 192 167 25 211 138 13 115 124 4 36 111 7 38 109 45 79 160 97 135 232 92 203 253 49 246 210 74 225 150 164 163 108 208 124 79 181 137 75 134 155 140 107 167 217 114 189 193 153 211 75 163 234 0 145 234 1 166 171 37 220 77 105 240 57 102 214 107 45 175 121 89 153 83 200 142 58 253 205 23 127 153 30 153 73 49 198 56 61 249 122 66 255 147 132 226 111 221 162 55 219 133 21 137 138 19 93 135 48 125 118 83 180 132 104 215 167 103 208 185 98 163 167 132 129 114 195 121 58 225 120 65 212 110 117 193 81 95 191 53 20 186 65 59 172 119 188 156 193 207 142 226 106 174 181 72 223 70 133 221 36 136 156 91 82 82 111 101 33 70 194 50 45 241 106 40 218 115 95 202 58 196 205 12 212 184 4 141 144 4 94 109 14 116 101 58 158 146 118 197 206 134 236 215 118 255 154 121 219 83 160 154 38 201 134 15 219 162 22 193 160 98 144 119 184 132 108 174 160 133 74 166 165 16 145 170 30 159 112 65 200 27 126 209 36 130 171 122 97 116 133 125 81 67 195 65 21 225 157 102 227 104 125 230 34 162 228 60 182 225 184 167 207 233 166 151 196 184 81 117 183 102 65 167 178 63 175 182 104 206 98 134 241 49 133 248 57 128 210 61 134 141 44 168 102 15 208 98 3 186 93 75 125 62 184 118 27 174 178 4 63 239 15 34 242 56 117 189 114 155 96 148 126 85 116 143 141 44 205 143 45 205 67 127 148 4 181 142 0 172 190 54 146 190 167 121 140 188 125 138 103 163 186 27 168 218 17 142 202 39 152 148 80 201 89 145 232 83 204 227 119 221 215 127 198 202 81 155 153 33 128 98 5 143 131 0 190 211 0 217 200 46 207 91 114 207 26 118 210 39 71 182 60 66 129 60 120 110 26 172 123 0 207 127 65 202 102 197 171 60 194 147 26 87 145 10 11 144 122 187 239 85 201 226 43 221 193 86 228 151 205 206 118 249 164 79 219 124 54 157 117 108 124 146 192 136 184 183 175 209 77 178 227 8 140 225 13 126 174 21 145 100 16 165 79 11 162 107 24 128 112 107 89 74 214 94 33 212 145 11 117 218 17 62 247 45 96 189 80 140 64 102 164 30 81 200 93 52 229 110 78 217 53 161 173 12 217 145 13 221 143 85 202 131 197 184 107 217 166 108 138 154 135 73 135 182 78 122 211 111 161 163 155 223 69 210 243 38 246 203 81 244 152 104 206 120 77 159 75 41 128 45 16 123 110 8 142 211 17 174 201 47 196 77 82 209 3 89 199 21 86 151 39 118 87 33 187 69 20 232 92 28 235 99 112 223 67 229 207 30 216 170 10 114 136 3 42 119 120 218 142 104 206 130 86 181 106 111 164 69 170 158 48 182 125 62 160 75 114 146 62 159 166 87 165 201 108 124 221 115 65 183 135 42 109 165 66 89 130 93 127 62 104 127 72 104 82 154 104 85 181 136 138 144 186 153 88 187 123 59 147 136 64 127 176 93 147 150 116 179 63 118 200 50 100 203 118 88 184 154 112 160 129 166 140 99 189 107 87 178 77 116 174 100 176 193 153 183 202 148 139 186 90 122 142 95 153 97 157 183 116 138 192 176 50 205 193 43 210 143 124 182 85 168 136 53 151 134 28 98 167 26 52 162 84 45 114 159 76 91 153 103 105 75 108 122 48 105 119 94 110 83 122 138 40 113 184 66 100 199 137 105 181 149 140 174 86 187 188 35 175 192 30 124 184 34 90 176 131 221 38 131 184 34 130 118 29 129 80 10 127 92 7 106 85 69 90 47 185 120 28 209 186 33 134 241 26 65 243 18 61 173 46 91 75 110 137 53 97 183 104 37 206 88 73 203 14 198 180 62 244 155 201 211 146 222 145 146 111 109 158 59 114 183 102 147 207 110 166 225 74 152 223 91 133 186 162 122 128 211 136 97 213 160 97 187 149 67 156 115 25 138 124 90 143 178 211 138 223 198 127 223 61 155 160 19 208 78 100 230 65 108 204 118 39 176 136 64 158 89 181 110 38 241 59 13 228 106 9 155 206 26 88 208 66 80 100 106 130 20 104 161 16 81 148 31 106 133 41 176 130 25 216 140 7 206 156 77 186 144 194 172 116 211 154 116 119 134 153 53 125 204 58 126 236 70 129 242 139 222 0 149 170 0 158 80 0 141 29 0 101 51 0 61 60 74 48 31 226 104 8 238 197 0 115 255 0 30 254 0 58 167 0 120 56 75 180 31 75 237 89 23 255 63 75 255 0 223 227 49 255 167 238 252 121 255 181 121 103 141 165 12 146 216 58 181 242 87 197 252 82 173 236 116 153 175 187 144 96 245 150 59 255 157 70 241 125 42 199 78 0 152 93 85 124 167 245 110 233 226 119 243 43 175 171 0 240 66 66 255 33 90 209 80 33 158 99 77 127 56 215 66 11 255 12 0 255 88 0 190 229 27 111 236 55 103 90 74 163 0 76 196 0 87 173 0 142 150 0 227 141 0 255 140 0 255 138 83 237 111 228 213 78 248 162 81 140 103 131 66 96 210 77 127 255 95 153 255
|
| 34 |
+
resample.2.ch=3
|
| 35 |
+
resample.2.w=37
|
| 36 |
+
resample.2.h=23
|
| 37 |
+
resample.2.box=0 0 37 23
|
| 38 |
+
resample.2.out=37 9
|
| 39 |
+
resample.2.filter=LANCZOS
|
| 40 |
+
resample.2.src=0 59 118 37 96 155 74 133 192 111 170 229 148 207 10 185 244 47 222 25 84 3 62 121 40 99 158 77 136 195 114 173 232 151 210 13 188 247 50 225 28 87 6 65 124 43 102 161 80 139 198 117 176 235 154 213 16 191 250 53 228 31 90 9 68 127 46 105 164 83 142 201 120 179 238 157 216 19 194 253 56 231 34 93 12 71 130 49 108 167 86 145 204 123 182 241 160 219 22 197 0 59 234 37 96 15 74 133 52 111 170 101 160 219 139 196 1 173 232 39 215 12 73 253 48 107 27 92 145 69 120 191 111 164 217 133 192 11 187 228 33 221 24 71 247 60 121 45 80 155 75 140 177 101 168 239 159 196 9 165 224 59 203 4 65 237 40 103 55 76 137 93 144 171 123 188 241 133 216 31 175 228 57 197 0 75 27 36 97 61 120 135 87 156 217 109 176 251 139 204 17 165 232 47 255 4 73 37 96 91 11 68 129 109 168 231 87 140 201 189 240 43 202 5 64 237 40 103 16 75 142 63 114 169 86 145 220 137 180 243 164 239 18 195 6 77 226 61 120 5 64 159 40 99 166 119 138 193 158 217 228 177 252 59 204 23 90 235 46 117 58 117 176 29 88 151 64 187 254 175 226 217 134 193 12 249 36 99 212 31 66 51 118 189 114 77 136 85 144 239 184 243 54 135 218 17 238 41 116 193 12 75 28 103 170 123 190 133 42 229 160 205 136 71 240 171 110 159 82 9 182 113 60 47 106 165 87 140 201 127 178 233 151 208 29 207 242 53 231 44 81 31 90 145 39 120 189 79 138 213 103 172 233 191 194 9 231 32 29 207 2 69 55 108 161 31 90 129 119 184 253 79 138 197 151 236 41 255 50 9 215 16 93 47 114 181 7 76 145 31 218 145 199 248 189 239 170 85 135 76 9 191 98 41 71 0 221 111 34 229 55 204 129 223 154 161 247 184 125 175 106 37 151 76 9 127 50 233 87 16 221 15 242 181 148 207 10 189 240 43 214 17 92 15 50 117 56 115 142 89 156 215 106 181 240 139 206 17 156 215 18 197 56 115 46 105 84 7 74 141 96 187 246 65 132 223 162 237 40 131 198 9 164 95 26 77 0 59 102 33 236 31 194 133 40 227 190 201 140 71 154 165 96 187 126 1 108 39 226 85 8 195 62 249 164 23 218 157 208 171 102 177 116 79 146 93 24 115 54 249 180 239 42 221 16 75 246 49 124 47 82 149 88 147 174 249 52 111 27 92 145 73 116 179 103 172 209 153 220 23 171 244 49 201 12 83 223 20 81 9 116 191 107 172 145 89 148 211 167 252 49 137 204 23 155 84 17 185 124 51 111 20 209 25 212 239 59 252 177 201 148 83 231 172 113 185 124 55 107 84 17 73 12 243 63 244 177 9 212 159 235 172 113 57 116 179 103 156 209 137 204 247 187 244 49 217 28 83 239 52 113 57 116 175 27 92 145 73 180 243 167 236 209 153 220 23 94 153 212 133 184 255 164 239 18 223 26 81 234 53 112 9 76 147 24 83 150 75 182 253 182 241 204 157 208 23 236 55 122 183 114 41 82 29 216 113 52 251 48 203 142 211 158 165 206 137 68 181 104 47 148 95 2 111 42 225 58 5 192 25 220 163 8 67 134 59 102 173 70 129 220 109 160 231 188 199 10 231 34 25 194 13 72 33 100 171 0 91 158 99 174 245 62 249 180 229 152 95 132 79 114 191 122 49 74 21 208 195 254 57 239 36 89 3 70 141 39 120 189 75 142 209 95 148 209 139 246 61 247 48 13 211 30 89 47 116 185 115 54 237 23 216 157 43 238 161 255 132 65 171 102 93 135 64 13 99 62 249 79 4 217 35 230 173 7 56 125 43 110 145 95 148 209 107 182 253 183 240 45 147 222 25 207 52 121 51 118 77 23 88 157 11 206 129 63 228 161 203 134 93 231 160 109 131 126 57 111 36 25 67 6 205 39 248 189 11 206 145 40 99 158 69 128 203 98 189 248 143 202 21 156 215 18 201 52 127 54 113 76 19 94 153 16 203 134 61 248 163 202 133 64 231 162 109 132 127 58 97 44 23 94 25 212 43 246 177 248 51 110 21 80 155 82 109 168 127 186 197 172 231 34 153 196 15 230 33 124 195 14 73 96 27 214 13 200 243 58 245 176 215 146 93 212 143 74 177 124 39 142 73 4 91 38 225 200 3 62 229 32 107 2 93 152 47 106 181 124 183 242 141 200 3 187 228 33 197 0 95 231 44 105 5 120 179 107 172 145 93 144 215 175 244 57 253 184 99 139 68 1 165 96 47 71 60 249 37 232 211 27 220 145 237 176 119 63 116 169 77 136 195 155 164 225 133 192 63 231 44 105 197 24 83 43 108 177 125 48 247 47 212 153 221 152 163 203 132 65 165 96 47 135 92 25 101 40 243 187 252 49 237 16 87 31 84 105 13 72 131 123 164 225 69 128 223 167 236 41 133 184 115 242 45 104 29 88 135 40 99 166 127 130 201 174 233 212 153 212 19 236 55 122 179 118 45 74 5 192 101 32 239 0 251 190 231 170 145 214 145 92 81 140 203 116 191 226 139 206 5 226 221 24 205 8 119 56 115 182 15 82 153 30 217 132 41 228 163 252 135 74 163 102 93 154 85 16 117 48 255 176 235 46 215 26 65 230 33 108 1 124 187 100 175 146 91 158 213 210 141 72 253 184 103 136 67 6 95 98 41 78 9 244 87 146 205 119 188 249 183 202 1 231 32 29 199 10 77 39 124 177 119 50 233 23 216 157 39 226 173 199 188 121 167 106 81 151 80 29 151 202 13 183 252 33 199 2 73 39 24 93 23 82 173 119 188 249 55 234 161 199 128 93 231 170 109 135 124 49 119 50 9 87 24 221 199 2 77 231 60 121 7 74 145 23 80 157 87 170 237 183 252 193 231 162 105 135 88 29 151 82 13 119 60 249 55 10 193 231 32 93 7 74 141 188 247 50 237 16 91 30 89 100 15 66 133 96 187 246 73 140 215 210 157 88 235 174 97 132 127 58 101 40 19 86 17 220 215 10 77 232 35 126 1 68 143 106 85 144 83 150 233 204 135 66 253 160 107 142 73 20 159 82 21 80 43 230 57 252 199 226 45 104 27 94 145 20 79 138 117 184 227 70 129 204 231 154 93 152 83 110 177 116 63 122 37 224 67 6 217 220 23 82 13 48 123 62 121 132 111 162 229 64 155 214 33 92 151 75 140 177 113 188 251 183 236 33 129 196 31 155 84 17 177 116 59 127 4 193 33 236 215 27 220 145 17 76 139 55 124 161 65 132 207 171 148 209 145 212 43 143 68 1 161 124 55 75 12 209 81 28 219 23 236 161 1 68 127 59 116 177 81 148 219 95 132 193 161 236 55 251 188 113 177 76 11 87 28 33 65 4 207 43 244 177 241 52 107 15 68 129 97 92 151 75 140 241 177 252 59 247 172 97 129 68 31 134 193 252 165 232 47 204 23 90 223 26 65 34 109 168 121 60 243 48 203 142 235 166 157 222 153 84 173 112 55 116 191 226 135 194 9 234 213 16 209 20 107 72 3 198 99 62 245 54 241 172 21 216 159 220 167 106 79 138 177 114 189 248 169 236 35 128 219 30 155 86 13 174 105 36 125 0 199 36 239 210 23 210 153 26 69 128 33 100 187 120 179 246 179 142 197 230 161 92 133 72 15 172 119 58 127 58 225 66 13 200 235 38 97 31 68 137 43 110 181 119 184 253 67 134 201 239 148 81 147 94 101 135 64 13 107 54 241 191 244 41 203 14 69 39 24 93 19 86 169 15 196 129 35 254 181 247 176 109 203 134 65 159 100 41 139 206 245 183 248 61 227 38 105 207 20 81 83 30 197 103 32 237 11 246 177 255 180 137 43 110 165 71 152 221 83 150 201 175 228 33 131 190 117 183 112 13 107 38 225 95 4 201 43 238 181 247 56 125 3 70 137 80 139 198 101 160 251 186 245 48 143 202 5 164 95 26 89 20 47 78 9 196 35 254 185 248 51 110 13 72 131 98 93 152 87 146 237 204 135 66 225 188 119 182 113 44 139 70 1 160 219 22 213 16 43 202 5 64 63 122 181 116 47 234 9 196 159 30 217 148 211 174 105 72 131 190 125 184 243 178 237 40 135 194 29 156 87 18 81 108 39 70 1 252 59 246 177 240 43 102 5 64 155 26 85 144 111 170 229 196 255 186 181 240 43 203 4 65 221 24 87 23 108 169 13 192 251 59 252 177 245 168 111 207 132 89 85 144 219 155 164 225 141 200 55 247 60 121 189 96 43 75 12 209 85 24 223 31 228 169 21 80 107 11 68 129 125 184 247 55 236 169 205 128 91 219 156 81 149 104 47 143 196 249 181 240 59 251 36 97 205 8 87 87 28 217 29 32 235 11 204 177 245 184 127 63 100 169 117 176 235 75 132 193 221 152 87 151 172 105 141 64 59 26 85 144 45 104 167 64 187 254 191 242 201 230 161 108 137 84 19 148 95 2 99 38 237 210 237 40 197 0 127 56 115 182 119 42 225 14 201 148 17 220 155 220 167 106 75 142 181 74 133 192 189 248 55 240 171 110 143 66 25 150 81 28 89 36 227 196 15 50 243 54 125 34 125 184 117 48 239 8 195 134 231 218 145 222 153 100 65 140 203 108 183 250 187 254 37 250 181 112 141 72 7 96 91 30 95 18 233 198 1 76 127 186 245 183 204 9 239 34 25 215 16 93 95 2 197 103 44 241 47 234 161 7 56 125 63 122 133 103 172 233 79 146 217 199 128 93 223 146 85 151 108 33 143 202 241 183 248 61 255 58 117 183 108 41 79 2 217 87 16 221 31 226 165 7 76 113 15 74 129 103 184 253 63 250 165 199 140 73 175 146 89 103 160 221 159 210 21 151 204 1 239 42 113 183 120 61 127 58 245 55 12 201 239 34 89 23 80 157 31 66 133 228 31 90 29 80 107 6 65 140 111 178 245 40 227 190 249 188 119 186 133 64 107 174 209 172 231 34 133 216 19 158 89 4 167 106 45 80 43 230 193 4 63 242 61 120 35 102 169 116 47 234 13 192 155 22 209 156 223 162 101 88 147 174 73 140 199 170 245 48 251 190 97 188 119 50 117 72 3 174 233 20 215 26 93 192 27 86 81 20 207 98 45 232 51 246 185 4 63 122 61 112 139 102 161 236 79 146 213 200 131 94 73 132 191 123 188 241 185 228 35 135 204 17 137 76 7 107 84 17 89 28 227 223 20 81 217 4 79 43 108 177 105 36 227 7 252 177 249 188 135 59 116 177 105 172 227 79 148 209 201 132 95 219 156 81 153 100 35 135 204 241 137 204 7 235 52 113 185 124 35 127 52 241 57 4 207 235 44 81 41 100 163 7 92 145 25 220 135 27 212 145 233 172 99 79 116 177 73 132 255 187 252 49 249 164 99 135 76 17 137 76 7 174 233 36 197 24 95 212 31 66 95 26 209 26 37 224 9 204 179 8 67 134 43 118 189 102 161 252 61 240 183 252 135 74 183 114 73 98 173 232 145 212 27 128 219 30 147 94 5 158 89 20 85 40 239 196 15 50 207 10 65 42 117 176 121 60 227 56 243 182 251 198 141 54 113 140 109 160 231 76 151 218 199 130 89 210 157 88 161 100 43 144 171 238 131 206 53 142 73 4 165 120 63 116 63 226 191 250 49 250 5 64
|
| 41 |
+
resample.2.expected=82 83 154 129 116 87 94 168 123 149 98 144 182 130 103 114 157 134 139 119 105 87 86 159 127 122 96 96 160 127 148 96 145 192 125 99 127 161 133 153 131 116 85 86 170 140 128 97 107 167 143 135 89 144 169 139 102 137 163 128 147 111 104 106 106 176 119 138 84 115 174 148 162 84 137 84 127 98 148 191 76 152 117 123 102 95 178 112 122 88 103 177 141 181 123 140 81 167 82 125 64 84 182 121 156 80 95 129 135 166 95 145 106 89 142 154 116 135 97 158 88 129 101 130 177 114 158 130 153 99 138 151 131 118 101 125 151 126 121 111 163 99 141 77 135 83 105 154 121 156 80 152 148 135 135 83 152 143 144 93 139 135 94 136 73 171 71 145 156 132 136 76 176 135 139 87 93 117 137 116 147 187 128 145 105 147 117 72 131 130 173 105 65 143 164 185 100 179 150 144 81 163 107 104 182 118 157 127 172 88 161 79 67 183 103 180 109 75 153 101 188 124 160 163 133 129 125 172 102 151 106 117 99 144 159 129 96 83 143 142 107 108 113 158 100 127 122 162 152 120 178 105 155 79 178 122 186 69 85 144 140 163 67 126 121 154 93 147 103 128 134 126 156 101 109 129 129 135 88 106 106 160 92 88 142 85 165 112 62 84 197 109 169 117 58 167 169 163 132 173 88 165 105 126 105 130 130 148 128 101 176 122 126 78 96 176 112 185 98 173 132 139 77 88 98 82 177 124 188 157 79 140 144 126 149 86 131 144 119 107 93 166 164 123 102 95 169 139 146 126 132 98 143 128 126 143 81 149 131 127 89 135 130 154 131 99 142 142 177 91 189 134 104 138 98 155 110 175 82 173 113 166 103 153 101 74 186 79 170 141 118 92 157 138 144 79 89 164 128 179 85 178 149 129 93 171 109 128 120 122 165 117 178 84 156 91 70 142 97 140 124 182 92 173 91 66 95 107 161 126 70 83 196 105 125 52 76 160 88 167 102 96 155 194 153 146 115 128 118 163 108 134 84 147 74 102 156 134 159 79 162 126 189 78 162 134 177 112 77 149 148 135 92 121 80 123 145 187 77 98 184 139 89 90 176 134 164 100 124 76 109 102 129 141 94 188 125 181 81 138 121 137 100 89 120 130 144 68 185 142 184 70 72 77 85 175 147 61 77 178 102 176 99 121 111 147 71 91 148 82 179 127 188 153 178 109 148 76 77 137 168 84 60 119 115 193 93 76 118 170 108 100 63 81 171 121 141 172 124 146 116 115 109 161 163 130 87 78 147 131 151 79 143 120 152 98 178 94 140 127 162 146 111 173 61 99 78 174 91 115 74 139 132 127 142 98 139 90 128 127 146 86 141 147 167 98 97 123 147 150 106 150 95 186 127 86 95 193 121 161 112 108 141 142 102 87 128 108 170 88 187 135 163 79 150 144 76 142 115 62 70 158 94 170 128 159 107 177 112 86 144 163 83 131 77 65 140 127 191 100 189 110 179 68 72 138 143 143 129 109 92 161 162 138 108 112 168 133 100 133 144 117 124 93 141 81 136 122 159 115 175 86 173 126 127 138 140 120 109 122 86 167 139 131 76 111 147 153 111 120 141 131 146 98 108 166 72 134 76 66 187 132 165 93 182 135 187 73 142 141 128 111 91 122 116 175 127 171 86 188 148 190 115 191 126 142 106 103 152 149 100 95 137 76 168 95 132 138 189 116 180 118 189 105 167 71 67 149 90 138 119 159 120 169 131 121 135 113 129 161 111 127 87 134 93 129 163 139 159 123 129 167 138 110 134 143 166 83 88 79 177 136 189 80 144 114 162 81 134 149 177 92 135 132 135 124 105 98 108 189 130 180 86 189 146 136 77 174 130 173 75 117 123 176 121 92 137 100 160 104 70 90 164 131 88 64 180 147 185 101 180 132 166 74 103 133 157 78 164 127 172 166 130 86 81 149 142 178 100 177 135 183 102 135 120 188 84 75 127 177 92 89 62 80 188 134 61 91 153 141 120 127 113 152 154 114 61 120 126 135 69 90 129 112 145 107 75 72 163 124 93 146 161 108 129 65 192 139 172 80 121 106 175 100 163 144 195 132 119 86 145 167 136 101 119 120 180 93 100 125 120 153 126 88 72 184 122 154 83 167 117 148 77 170 126 202 96 195 141 174 87 73 146 163 99 104 91 155 145 120 88 113 131 156 98 95 125 112 171 130 175 91 176 136 71 83 139 144 167 84 169 126 185 137 155 74 183 70 53
|
| 42 |
+
resample.3.ch=3
|
| 43 |
+
resample.3.w=37
|
| 44 |
+
resample.3.h=23
|
| 45 |
+
resample.3.box=5 0 20 23
|
| 46 |
+
resample.3.out=7 23
|
| 47 |
+
resample.3.filter=BILINEAR
|
| 48 |
+
resample.3.src=0 59 118 37 96 155 74 133 192 111 170 229 148 207 10 185 244 47 222 25 84 3 62 121 40 99 158 77 136 195 114 173 232 151 210 13 188 247 50 225 28 87 6 65 124 43 102 161 80 139 198 117 176 235 154 213 16 191 250 53 228 31 90 9 68 127 46 105 164 83 142 201 120 179 238 157 216 19 194 253 56 231 34 93 12 71 130 49 108 167 86 145 204 123 182 241 160 219 22 197 0 59 234 37 96 15 74 133 52 111 170 101 160 219 139 196 1 173 232 39 215 12 73 253 48 107 27 92 145 69 120 191 111 164 217 133 192 11 187 228 33 221 24 71 247 60 121 45 80 155 75 140 177 101 168 239 159 196 9 165 224 59 203 4 65 237 40 103 55 76 137 93 144 171 123 188 241 133 216 31 175 228 57 197 0 75 27 36 97 61 120 135 87 156 217 109 176 251 139 204 17 165 232 47 255 4 73 37 96 91 11 68 129 109 168 231 87 140 201 189 240 43 202 5 64 237 40 103 16 75 142 63 114 169 86 145 220 137 180 243 164 239 18 195 6 77 226 61 120 5 64 159 40 99 166 119 138 193 158 217 228 177 252 59 204 23 90 235 46 117 58 117 176 29 88 151 64 187 254 175 226 217 134 193 12 249 36 99 212 31 66 51 118 189 114 77 136 85 144 239 184 243 54 135 218 17 238 41 116 193 12 75 28 103 170 123 190 133 42 229 160 205 136 71 240 171 110 159 82 9 182 113 60 47 106 165 87 140 201 127 178 233 151 208 29 207 242 53 231 44 81 31 90 145 39 120 189 79 138 213 103 172 233 191 194 9 231 32 29 207 2 69 55 108 161 31 90 129 119 184 253 79 138 197 151 236 41 255 50 9 215 16 93 47 114 181 7 76 145 31 218 145 199 248 189 239 170 85 135 76 9 191 98 41 71 0 221 111 34 229 55 204 129 223 154 161 247 184 125 175 106 37 151 76 9 127 50 233 87 16 221 15 242 181 148 207 10 189 240 43 214 17 92 15 50 117 56 115 142 89 156 215 106 181 240 139 206 17 156 215 18 197 56 115 46 105 84 7 74 141 96 187 246 65 132 223 162 237 40 131 198 9 164 95 26 77 0 59 102 33 236 31 194 133 40 227 190 201 140 71 154 165 96 187 126 1 108 39 226 85 8 195 62 249 164 23 218 157 208 171 102 177 116 79 146 93 24 115 54 249 180 239 42 221 16 75 246 49 124 47 82 149 88 147 174 249 52 111 27 92 145 73 116 179 103 172 209 153 220 23 171 244 49 201 12 83 223 20 81 9 116 191 107 172 145 89 148 211 167 252 49 137 204 23 155 84 17 185 124 51 111 20 209 25 212 239 59 252 177 201 148 83 231 172 113 185 124 55 107 84 17 73 12 243 63 244 177 9 212 159 235 172 113 57 116 179 103 156 209 137 204 247 187 244 49 217 28 83 239 52 113 57 116 175 27 92 145 73 180 243 167 236 209 153 220 23 94 153 212 133 184 255 164 239 18 223 26 81 234 53 112 9 76 147 24 83 150 75 182 253 182 241 204 157 208 23 236 55 122 183 114 41 82 29 216 113 52 251 48 203 142 211 158 165 206 137 68 181 104 47 148 95 2 111 42 225 58 5 192 25 220 163 8 67 134 59 102 173 70 129 220 109 160 231 188 199 10 231 34 25 194 13 72 33 100 171 0 91 158 99 174 245 62 249 180 229 152 95 132 79 114 191 122 49 74 21 208 195 254 57 239 36 89 3 70 141 39 120 189 75 142 209 95 148 209 139 246 61 247 48 13 211 30 89 47 116 185 115 54 237 23 216 157 43 238 161 255 132 65 171 102 93 135 64 13 99 62 249 79 4 217 35 230 173 7 56 125 43 110 145 95 148 209 107 182 253 183 240 45 147 222 25 207 52 121 51 118 77 23 88 157 11 206 129 63 228 161 203 134 93 231 160 109 131 126 57 111 36 25 67 6 205 39 248 189 11 206 145 40 99 158 69 128 203 98 189 248 143 202 21 156 215 18 201 52 127 54 113 76 19 94 153 16 203 134 61 248 163 202 133 64 231 162 109 132 127 58 97 44 23 94 25 212 43 246 177 248 51 110 21 80 155 82 109 168 127 186 197 172 231 34 153 196 15 230 33 124 195 14 73 96 27 214 13 200 243 58 245 176 215 146 93 212 143 74 177 124 39 142 73 4 91 38 225 200 3 62 229 32 107 2 93 152 47 106 181 124 183 242 141 200 3 187 228 33 197 0 95 231 44 105 5 120 179 107 172 145 93 144 215 175 244 57 253 184 99 139 68 1 165 96 47 71 60 249 37 232 211 27 220 145 237 176 119 63 116 169 77 136 195 155 164 225 133 192 63 231 44 105 197 24 83 43 108 177 125 48 247 47 212 153 221 152 163 203 132 65 165 96 47 135 92 25 101 40 243 187 252 49 237 16 87 31 84 105 13 72 131 123 164 225 69 128 223 167 236 41 133 184 115 242 45 104 29 88 135 40 99 166 127 130 201 174 233 212 153 212 19 236 55 122 179 118 45 74 5 192 101 32 239 0 251 190 231 170 145 214 145 92 81 140 203 116 191 226 139 206 5 226 221 24 205 8 119 56 115 182 15 82 153 30 217 132 41 228 163 252 135 74 163 102 93 154 85 16 117 48 255 176 235 46 215 26 65 230 33 108 1 124 187 100 175 146 91 158 213 210 141 72 253 184 103 136 67 6 95 98 41 78 9 244 87 146 205 119 188 249 183 202 1 231 32 29 199 10 77 39 124 177 119 50 233 23 216 157 39 226 173 199 188 121 167 106 81 151 80 29 151 202 13 183 252 33 199 2 73 39 24 93 23 82 173 119 188 249 55 234 161 199 128 93 231 170 109 135 124 49 119 50 9 87 24 221 199 2 77 231 60 121 7 74 145 23 80 157 87 170 237 183 252 193 231 162 105 135 88 29 151 82 13 119 60 249 55 10 193 231 32 93 7 74 141 188 247 50 237 16 91 30 89 100 15 66 133 96 187 246 73 140 215 210 157 88 235 174 97 132 127 58 101 40 19 86 17 220 215 10 77 232 35 126 1 68 143 106 85 144 83 150 233 204 135 66 253 160 107 142 73 20 159 82 21 80 43 230 57 252 199 226 45 104 27 94 145 20 79 138 117 184 227 70 129 204 231 154 93 152 83 110 177 116 63 122 37 224 67 6 217 220 23 82 13 48 123 62 121 132 111 162 229 64 155 214 33 92 151 75 140 177 113 188 251 183 236 33 129 196 31 155 84 17 177 116 59 127 4 193 33 236 215 27 220 145 17 76 139 55 124 161 65 132 207 171 148 209 145 212 43 143 68 1 161 124 55 75 12 209 81 28 219 23 236 161 1 68 127 59 116 177 81 148 219 95 132 193 161 236 55 251 188 113 177 76 11 87 28 33 65 4 207 43 244 177 241 52 107 15 68 129 97 92 151 75 140 241 177 252 59 247 172 97 129 68 31 134 193 252 165 232 47 204 23 90 223 26 65 34 109 168 121 60 243 48 203 142 235 166 157 222 153 84 173 112 55 116 191 226 135 194 9 234 213 16 209 20 107 72 3 198 99 62 245 54 241 172 21 216 159 220 167 106 79 138 177 114 189 248 169 236 35 128 219 30 155 86 13 174 105 36 125 0 199 36 239 210 23 210 153 26 69 128 33 100 187 120 179 246 179 142 197 230 161 92 133 72 15 172 119 58 127 58 225 66 13 200 235 38 97 31 68 137 43 110 181 119 184 253 67 134 201 239 148 81 147 94 101 135 64 13 107 54 241 191 244 41 203 14 69 39 24 93 19 86 169 15 196 129 35 254 181 247 176 109 203 134 65 159 100 41 139 206 245 183 248 61 227 38 105 207 20 81 83 30 197 103 32 237 11 246 177 255 180 137 43 110 165 71 152 221 83 150 201 175 228 33 131 190 117 183 112 13 107 38 225 95 4 201 43 238 181 247 56 125 3 70 137 80 139 198 101 160 251 186 245 48 143 202 5 164 95 26 89 20 47 78 9 196 35 254 185 248 51 110 13 72 131 98 93 152 87 146 237 204 135 66 225 188 119 182 113 44 139 70 1 160 219 22 213 16 43 202 5 64 63 122 181 116 47 234 9 196 159 30 217 148 211 174 105 72 131 190 125 184 243 178 237 40 135 194 29 156 87 18 81 108 39 70 1 252 59 246 177 240 43 102 5 64 155 26 85 144 111 170 229 196 255 186 181 240 43 203 4 65 221 24 87 23 108 169 13 192 251 59 252 177 245 168 111 207 132 89 85 144 219 155 164 225 141 200 55 247 60 121 189 96 43 75 12 209 85 24 223 31 228 169 21 80 107 11 68 129 125 184 247 55 236 169 205 128 91 219 156 81 149 104 47 143 196 249 181 240 59 251 36 97 205 8 87 87 28 217 29 32 235 11 204 177 245 184 127 63 100 169 117 176 235 75 132 193 221 152 87 151 172 105 141 64 59 26 85 144 45 104 167 64 187 254 191 242 201 230 161 108 137 84 19 148 95 2 99 38 237 210 237 40 197 0 127 56 115 182 119 42 225 14 201 148 17 220 155 220 167 106 75 142 181 74 133 192 189 248 55 240 171 110 143 66 25 150 81 28 89 36 227 196 15 50 243 54 125 34 125 184 117 48 239 8 195 134 231 218 145 222 153 100 65 140 203 108 183 250 187 254 37 250 181 112 141 72 7 96 91 30 95 18 233 198 1 76 127 186 245 183 204 9 239 34 25 215 16 93 95 2 197 103 44 241 47 234 161 7 56 125 63 122 133 103 172 233 79 146 217 199 128 93 223 146 85 151 108 33 143 202 241 183 248 61 255 58 117 183 108 41 79 2 217 87 16 221 31 226 165 7 76 113 15 74 129 103 184 253 63 250 165 199 140 73 175 146 89 103 160 221 159 210 21 151 204 1 239 42 113 183 120 61 127 58 245 55 12 201 239 34 89 23 80 157 31 66 133 228 31 90 29 80 107 6 65 140 111 178 245 40 227 190 249 188 119 186 133 64 107 174 209 172 231 34 133 216 19 158 89 4 167 106 45 80 43 230 193 4 63 242 61 120 35 102 169 116 47 234 13 192 155 22 209 156 223 162 101 88 147 174 73 140 199 170 245 48 251 190 97 188 119 50 117 72 3 174 233 20 215 26 93 192 27 86 81 20 207 98 45 232 51 246 185 4 63 122 61 112 139 102 161 236 79 146 213 200 131 94 73 132 191 123 188 241 185 228 35 135 204 17 137 76 7 107 84 17 89 28 227 223 20 81 217 4 79 43 108 177 105 36 227 7 252 177 249 188 135 59 116 177 105 172 227 79 148 209 201 132 95 219 156 81 153 100 35 135 204 241 137 204 7 235 52 113 185 124 35 127 52 241 57 4 207 235 44 81 41 100 163 7 92 145 25 220 135 27 212 145 233 172 99 79 116 177 73 132 255 187 252 49 249 164 99 135 76 17 137 76 7 174 233 36 197 24 95 212 31 66 95 26 209 26 37 224 9 204 179 8 67 134 43 118 189 102 161 252 61 240 183 252 135 74 183 114 73 98 173 232 145 212 27 128 219 30 147 94 5 158 89 20 85 40 239 196 15 50 207 10 65 42 117 176 121 60 227 56 243 182 251 198 141 54 113 140 109 160 231 76 151 218 199 130 89 210 157 88 161 100 43 144 171 238 131 206 53 142 73 4 165 120 63 116 63 226 191 250 49 250 5 64
|
| 49 |
+
resample.3.expected=139 100 91 72 129 168 160 171 82 78 95 143 130 182 133 134 110 97 76 135 194 75 131 166 174 155 68 139 87 139 126 170 121 178 81 84 110 143 157 165 160 75 171 147 103 104 70 141 132 168 168 174 95 116 81 141 188 176 137 103 128 77 135 91 90 146 116 149 160 170 68 74 81 131 175 171 128 85 86 94 136 148 201 146 115 184 155 133 129 74 65 134 173 131 172 69 95 74 112 100 174 138 160 114 91 178 87 85 99 138 153 143 181 62 124 111 127 124 187 152 160 112 85 56 161 171 50 122 180 169 174 122 138 76 151 140 142 154 165 99 76 61 83 168 46 113 175 162 148 91 128 82 145 95 173 150 153 91 100 62 90 183 58 129 173 145 211 108 80 102 115 86 185 129 159 119 79 104 103 144 101 113 155 159 175 85 173 42 117 134 180 145 172 126 64 78 155 174 113 161 159 141 142 150 149 67 132 112 145 181 183 107 83 95 92 183 153 167 154 143 182 114 131 105 117 62 173 140 169 119 76 66 129 193 124 175 125 163 151 38 115 80 94 94 161 175 170 138 82 130 35 112 171 153 121 133 74 85 152 35 128 110 110 154 185 116 74 114 107 146 77 95 139 143 93 98 43 158 168 79 132 173 145 133 75 87 75 161 38 122 167 105 162 161 132 151 166 178 153 107 173 153 74 105 81 182 97 185 157 131 192 138 155 140 26 164 97 89 152 107 98 57 93 123 126 188 125 172 160 110 186 88 114 85 87 194 89 76 145 99 100 146 154 138 135 175 127 41 171 70 68 74 120 178 108 176 144 169 179 135 149 154 162 169 84 112 58 95 174 57 138 164 164 159 112 163 181 131 140 93 68 152 93 137 67 141 172 109 168 150 167 168 98 147 65 88 160 61 133 54 128 170 82 136 178 174 138 111 180 169 118 150 68 134 39 107 163 60 163 184 182 168 111 148 179 47 147 65 103 136 66 150 69 159 163 118 166 147 195 179 84 140 38 120 121 66 150 112 166 175 117 149 181 174 144 101 169 145 89 134 57 168 27 126 172 117 171 170 154 163 114 138 150 39 149 43 105 111 90 157 137 172 160
|
| 50 |
+
resample.4.ch=1
|
| 51 |
+
resample.4.w=41
|
| 52 |
+
resample.4.h=29
|
| 53 |
+
resample.4.box=0 0 41 29
|
| 54 |
+
resample.4.out=13 9
|
| 55 |
+
resample.4.filter=BOX
|
| 56 |
+
resample.4.src=0 37 74 111 148 185 222 3 40 77 114 151 188 225 6 43 80 117 154 191 228 9 46 83 120 157 194 231 12 49 86 123 160 197 234 15 52 89 126 163 200 101 139 173 215 253 27 69 111 133 187 221 247 45 75 101 159 165 203 237 55 93 123 133 175 197 27 61 87 109 139 165 255 37 11 109 87 189 155 197 47 5 202 237 16 63 86 137 164 195 226 5 40 119 158 177 204 235 58 29 64 175 134 249 212 51 114 85 184 135 238 193 28 123 42 205 240 159 182 105 4 35 194 47 87 127 151 207 231 31 39 79 103 191 231 207 55 31 119 79 151 255 215 47 7 31 199 239 135 191 71 111 55 223 247 175 151 127 87 15 231 223 167 143 148 189 214 15 56 89 106 139 156 197 46 7 96 65 162 131 164 77 102 31 40 201 154 187 108 85 62 23 208 177 146 115 180 221 246 47 88 121 138 171 252 249 27 73 103 153 171 201 223 9 107 89 167 137 155 185 111 25 59 201 231 185 107 73 63 9 235 57 103 137 187 217 239 57 27 73 167 153 235 201 95 9 94 133 164 223 234 9 24 75 182 157 236 183 82 113 48 211 206 181 148 111 58 25 8 59 70 109 188 231 194 33 0 99 62 229 132 191 74 105 56 235 214 195 239 3 39 75 95 139 247 211 47 115 23 43 255 171 135 99 79 35 7 43 95 107 183 147 207 51 23 11 63 203 231 131 111 67 39 11 31 75 119 147 40 69 98 143 156 201 54 19 16 61 202 231 132 97 94 43 248 21 82 127 172 153 230 195 96 13 58 215 212 177 142 91 200 229 2 47 124 169 150 243 176 141 187 197 231 5 107 93 175 253 139 165 71 37 27 237 63 77 155 133 231 197 43 125 47 221 203 165 135 101 187 237 31 13 123 69 167 133 171 93 111 61 242 29 40 127 174 153 236 179 74 101 0 231 214 81 116 139 226 205 56 15 30 41 252 163 154 117 176 215 230 1 100 91 210 253 136 95 78 57 12 19 42 87 119 183 231 199 39 119 23 39 199 167 151 151 183 199 39 23 119 55 199 231 135 119 87 199 231 7 23 87 183 231 135 151 119 55 231 7 39 119 87 167 188 237 30 15 96 73 210 235 132 101 86 215 232 1 106 83 204 253 142 159 80 57 226 27 20 117 70 231 152 177 122 67 220 13 62 111 64 169 242 139 100 33 75 113 183 129 155 177 127 33 27 17 55 65 171 145 143 161 75 81 23 1 59 81 95 161 251 177 87 65 43 241 15 97 75 177 247 129 155 113 63 225 134 165 204 223 34 121 48 235 222 173 116 135 234 209 72 99 54 21 220 79 114 169 128 155 174 125 36 23 26 33 120 179 230 133 172 127 66 217 16 11 126 235 31 43 119 67 239 147 135 107 191 203 39 19 15 35 247 203 159 139 183 227 207 83 103 11 255 43 71 83 175 131 183 107 95 43 247 3 111 83 199 235 80 101 186 143 164 89 78 35 248 13 98 87 204 225 182 139 160 213 202 63 116 9 30 211 72 125 178 135 156 81 70 59 240 5 26 111 196 249 174 131 152 181 203 221 23 13 59 245 207 85 155 141 247 189 75 85 31 21 11 125 55 205 219 149 143 181 251 205 87 29 11 245 63 117 75 221 151 141 123 181 207 213 26 45 64 191 230 137 148 99 210 197 56 119 14 17 220 75 74 189 240 143 150 89 196 243 34 117 8 231 222 65 108 187 250 141 96 95 198 233 52 3 50 127 183 239 215 95 103 47 7 63 103 79 199 223 151 143 183 255 183 79 87 31 7 15 103 63 199 175 103 159 151 239 183 127 55 239 23 31 103 47 199 191 228 29 6 111 40 249 186 107 172 133 158 167 80 193 242 35 116 13 22 223 88 73 170 251 188 117 174 215 192 81 98 51 4 61 102 79 200 217 154 139 140 73 123 185 135 137 107 89 223 217 43 105 7 249 59 105 79 201 219 153 135 137 235 185 127 57 235 41 7 25 27 233 79 73 187 249 135 137 171 217 223 89 174 197 212 95 26 9 8 43 102 61 252 183 98 145 128 147 158 85 196 207 42 121 56 251 54 109 76 199 210 161 144 131 142 165 116 191 250 41 104 11 6 19 47 115 199 251 191 123 183 131 175 99 87 219 223 91 103 19 15 51 103 59 255 187 119 163 143 131 151 155 223 219 71 83 47 243 7 123 63 251 55 67 120 133 242 175 108 73 198 211 96 29 26 7 20 97 46 203 72 117 162 255 188 121 182 227 176 77 74 215 36 17 30 27 24 37 82 207 204 169 102 179 128 221 27 21 7 21 235 45 79 205 203 181 103 181 251 189 127 189 251 181 71 53 235 45 15 13 11 21 103 85 219 221 95 93 155 149 135 149 171 237 79 77 66 125 184 255 190 121 188 243 186 101 176 199 198 113 36 235 50 13 8 239 46 105 76 195 202 85 160 151 150 129 148 155 162 93 88 223 222 89 28 19 26 167 215 199 71 87 231 39 23 23 7 23 23 39 215 215 71 71 215 167 103 151 135 135 151 151 231 215 87 71 215 215 103 39 23 7 7 23 231 39 87 87 12 45 110 47 208 73 66 203 212 165 102 151 152 129 154 147 156 93 94 223 192 89 82 219 36 21 22 7 8 17 42 227 44 77 78 207 176 105 162 235 180
|
| 57 |
+
resample.4.expected=109 136 129 130 136 123 140 126 129 127 137 115 108 129 130 109 132 112 124 118 118 107 172 140 127 156 130 127 124 121 125 122 105 124 133 126 114 105 140 129 123 138 154 105 143 105 139 132 132 135 95 103 115 141 137 106 126 124 118 110 119 108 125 145 119 123 117 150 127 117 137 117 140 149 108 130 166 129 148 110 105 134 134 140 116 127 138 136 127 132 127 105 140 141 111 148 117 149 125 103 117 101 136 130 125 143 131 112 146 96 143 139 109 124 68 143 96
|
| 58 |
+
resample.5.ch=1
|
| 59 |
+
resample.5.w=41
|
| 60 |
+
resample.5.h=29
|
| 61 |
+
resample.5.box=4 3 33 25
|
| 62 |
+
resample.5.out=8 8
|
| 63 |
+
resample.5.filter=BOX
|
| 64 |
+
resample.5.src=0 37 74 111 148 185 222 3 40 77 114 151 188 225 6 43 80 117 154 191 228 9 46 83 120 157 194 231 12 49 86 123 160 197 234 15 52 89 126 163 200 101 139 173 215 253 27 69 111 133 187 221 247 45 75 101 159 165 203 237 55 93 123 133 175 197 27 61 87 109 139 165 255 37 11 109 87 189 155 197 47 5 202 237 16 63 86 137 164 195 226 5 40 119 158 177 204 235 58 29 64 175 134 249 212 51 114 85 184 135 238 193 28 123 42 205 240 159 182 105 4 35 194 47 87 127 151 207 231 31 39 79 103 191 231 207 55 31 119 79 151 255 215 47 7 31 199 239 135 191 71 111 55 223 247 175 151 127 87 15 231 223 167 143 148 189 214 15 56 89 106 139 156 197 46 7 96 65 162 131 164 77 102 31 40 201 154 187 108 85 62 23 208 177 146 115 180 221 246 47 88 121 138 171 252 249 27 73 103 153 171 201 223 9 107 89 167 137 155 185 111 25 59 201 231 185 107 73 63 9 235 57 103 137 187 217 239 57 27 73 167 153 235 201 95 9 94 133 164 223 234 9 24 75 182 157 236 183 82 113 48 211 206 181 148 111 58 25 8 59 70 109 188 231 194 33 0 99 62 229 132 191 74 105 56 235 214 195 239 3 39 75 95 139 247 211 47 115 23 43 255 171 135 99 79 35 7 43 95 107 183 147 207 51 23 11 63 203 231 131 111 67 39 11 31 75 119 147 40 69 98 143 156 201 54 19 16 61 202 231 132 97 94 43 248 21 82 127 172 153 230 195 96 13 58 215 212 177 142 91 200 229 2 47 124 169 150 243 176 141 187 197 231 5 107 93 175 253 139 165 71 37 27 237 63 77 155 133 231 197 43 125 47 221 203 165 135 101 187 237 31 13 123 69 167 133 171 93 111 61 242 29 40 127 174 153 236 179 74 101 0 231 214 81 116 139 226 205 56 15 30 41 252 163 154 117 176 215 230 1 100 91 210 253 136 95 78 57 12 19 42 87 119 183 231 199 39 119 23 39 199 167 151 151 183 199 39 23 119 55 199 231 135 119 87 199 231 7 23 87 183 231 135 151 119 55 231 7 39 119 87 167 188 237 30 15 96 73 210 235 132 101 86 215 232 1 106 83 204 253 142 159 80 57 226 27 20 117 70 231 152 177 122 67 220 13 62 111 64 169 242 139 100 33 75 113 183 129 155 177 127 33 27 17 55 65 171 145 143 161 75 81 23 1 59 81 95 161 251 177 87 65 43 241 15 97 75 177 247 129 155 113 63 225 134 165 204 223 34 121 48 235 222 173 116 135 234 209 72 99 54 21 220 79 114 169 128 155 174 125 36 23 26 33 120 179 230 133 172 127 66 217 16 11 126 235 31 43 119 67 239 147 135 107 191 203 39 19 15 35 247 203 159 139 183 227 207 83 103 11 255 43 71 83 175 131 183 107 95 43 247 3 111 83 199 235 80 101 186 143 164 89 78 35 248 13 98 87 204 225 182 139 160 213 202 63 116 9 30 211 72 125 178 135 156 81 70 59 240 5 26 111 196 249 174 131 152 181 203 221 23 13 59 245 207 85 155 141 247 189 75 85 31 21 11 125 55 205 219 149 143 181 251 205 87 29 11 245 63 117 75 221 151 141 123 181 207 213 26 45 64 191 230 137 148 99 210 197 56 119 14 17 220 75 74 189 240 143 150 89 196 243 34 117 8 231 222 65 108 187 250 141 96 95 198 233 52 3 50 127 183 239 215 95 103 47 7 63 103 79 199 223 151 143 183 255 183 79 87 31 7 15 103 63 199 175 103 159 151 239 183 127 55 239 23 31 103 47 199 191 228 29 6 111 40 249 186 107 172 133 158 167 80 193 242 35 116 13 22 223 88 73 170 251 188 117 174 215 192 81 98 51 4 61 102 79 200 217 154 139 140 73 123 185 135 137 107 89 223 217 43 105 7 249 59 105 79 201 219 153 135 137 235 185 127 57 235 41 7 25 27 233 79 73 187 249 135 137 171 217 223 89 174 197 212 95 26 9 8 43 102 61 252 183 98 145 128 147 158 85 196 207 42 121 56 251 54 109 76 199 210 161 144 131 142 165 116 191 250 41 104 11 6 19 47 115 199 251 191 123 183 131 175 99 87 219 223 91 103 19 15 51 103 59 255 187 119 163 143 131 151 155 223 219 71 83 47 243 7 123 63 251 55 67 120 133 242 175 108 73 198 211 96 29 26 7 20 97 46 203 72 117 162 255 188 121 182 227 176 77 74 215 36 17 30 27 24 37 82 207 204 169 102 179 128 221 27 21 7 21 235 45 79 205 203 181 103 181 251 189 127 189 251 181 71 53 235 45 15 13 11 21 103 85 219 221 95 93 155 149 135 149 171 237 79 77 66 125 184 255 190 121 188 243 186 101 176 199 198 113 36 235 50 13 8 239 46 105 76 195 202 85 160 151 150 129 148 155 162 93 88 223 222 89 28 19 26 167 215 199 71 87 231 39 23 23 7 23 23 39 215 215 71 71 215 167 103 151 135 135 151 151 231 215 87 71 215 215 103 39 23 7 7 23 231 39 87 87 12 45 110 47 208 73 66 203 212 165 102 151 152 129 154 147 156 93 94 223 192 89 82 219 36 21 22 7 8 17 42 227 44 77 78 207 176 105 162 235 180
|
| 65 |
+
resample.5.expected=137 115 121 124 115 118 168 117 111 139 119 108 114 126 120 105 125 133 124 130 132 141 131 122 140 116 118 129 110 114 134 111 126 138 122 131 132 146 125 122 116 121 145 129 123 137 112 125 119 104 127 115 160 132 106 140 125 119 156 120 122 114 150 106
|
| 66 |
+
window.0.mode=crop
|
| 67 |
+
window.0.w=3024
|
| 68 |
+
window.0.h=4032
|
| 69 |
+
window.0.rect=0 2100 300 3000
|
| 70 |
+
window.0.result=0 1425 2250 2250 0 0 512 512
|
| 71 |
+
window.1.mode=crop
|
| 72 |
+
window.1.w=512
|
| 73 |
+
window.1.h=512
|
| 74 |
+
window.1.rect=300 170 470 408
|
| 75 |
+
window.1.result=0 0 512 512 0 0 512 512
|
| 76 |
+
window.2.mode=crop
|
| 77 |
+
window.2.w=800
|
| 78 |
+
window.2.h=600
|
| 79 |
+
window.2.rect=700 500 800 600
|
| 80 |
+
window.2.result=288 88 512 512 0 0 512 512
|
| 81 |
+
window.3.mode=crop
|
| 82 |
+
window.3.w=400
|
| 83 |
+
window.3.h=300
|
| 84 |
+
window.3.rect=10 10 50 50
|
| 85 |
+
window.3.result=0 0 300 300 0 0 512 512
|
| 86 |
+
window.4.mode=letterbox
|
| 87 |
+
window.4.w=3024
|
| 88 |
+
window.4.h=4032
|
| 89 |
+
window.4.rect=0 2100 300 3000
|
| 90 |
+
window.4.result=0 0 3024 4032 64 0 384 512
|
| 91 |
+
window.5.mode=letterbox
|
| 92 |
+
window.5.w=512
|
| 93 |
+
window.5.h=512
|
| 94 |
+
window.5.rect=300 170 470 408
|
| 95 |
+
window.5.result=0 0 512 512 0 0 512 512
|
| 96 |
+
window.6.mode=letterbox
|
| 97 |
+
window.6.w=800
|
| 98 |
+
window.6.h=600
|
| 99 |
+
window.6.rect=700 500 800 600
|
| 100 |
+
window.6.result=0 0 800 600 0 64 512 384
|
| 101 |
+
window.7.mode=letterbox
|
| 102 |
+
window.7.w=400
|
| 103 |
+
window.7.h=300
|
| 104 |
+
window.7.rect=10 10 50 50
|
| 105 |
+
window.7.result=0 0 400 300 0 64 512 384
|
| 106 |
+
resample_count=6
|
| 107 |
+
window_count=8
|
app/versions.env
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 固定版本,env.sh / env.ps1、tools/ 和 Gradle 工程共用。格式:KEY=VALUE,不加引号,# 开头为注释。
|
| 2 |
+
# 升级时同时更新校验值,并在 docs/toolchain.md 中记录。
|
| 3 |
+
# JDK、Android SDK、NDK、build-tools 的版本由 gitea 的 contrib/android/versions.env 决定(只读复用那套工具链)。
|
| 4 |
+
|
| 5 |
+
# ONNX Runtime(Maven,com.microsoft.onnxruntime)。
|
| 6 |
+
# onnxruntime-android-qnn 是含 QNN EP 的完整 ORT 构建,与 onnxruntime-android 二选一,不能同时依赖。
|
| 7 |
+
ORT_ANDROID_VERSION=1.31.0
|
| 8 |
+
ORT_ANDROID_QNN_VERSION=1.29.0
|
| 9 |
+
# 主机 Python(conda 环境 onnx-android)中的 onnxruntime,用于参考管线和模型准备
|
| 10 |
+
ORT_PYTHON_VERSION=1.31.0
|
| 11 |
+
|
| 12 |
+
# Android 应用(app/、probe/)。Gradle 由 wrapper 下载到 GRADLE_USER_HOME(本仓库 .toolchain/gradle)
|
| 13 |
+
GRADLE_VERSION=9.7.1
|
| 14 |
+
GRADLE_SHA256=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
|
| 15 |
+
AGP_VERSION=9.4.1
|
| 16 |
+
|
| 17 |
+
# 目标 API 级别
|
| 18 |
+
ANDROID_MIN_SDK=29
|
| 19 |
+
ANDROID_TARGET_SDK=36
|
| 20 |
+
|
| 21 |
+
# 模型(Hugging Face simonw/Moebius-ONNX,main 分支,fp32,opset 18)。
|
| 22 |
+
# 大小来自 HF API(2026-10-16);sha256 由 tools/fetch-models.py 首次下载后写入(E4)
|
| 23 |
+
MODEL_REPO=simonw/Moebius-ONNX
|
| 24 |
+
MODEL_REVISION=main
|
| 25 |
+
MODEL_FILES=unet.onnx vae_encoder.onnx vae_decoder.onnx
|
| 26 |
+
MODEL_unet_SIZE=906698976
|
| 27 |
+
MODEL_unet_SHA256=e3f90f52f72378339b990459fadb29a68d3c7b5c6851545ba42774f489160b08
|
| 28 |
+
MODEL_vae_encoder_SIZE=136757093
|
| 29 |
+
MODEL_vae_encoder_SHA256=b8b81d41e757222a0707665ba9d826703987855e5bed056036b90b988968042f
|
| 30 |
+
MODEL_vae_decoder_SIZE=198078671
|
| 31 |
+
MODEL_vae_decoder_SHA256=d90ef0b7f6c8c8b7234459c8b449d70be0033bf1576c842e8b9991baf3934280
|
| 32 |
+
|
| 33 |
+
# QNN 运行库(Maven com.qualcomm.qti:qnn-runtime)。onnxruntime-android-qnn 的 POM 依赖这个版本,
|
| 34 |
+
# AAR 本身不含 libQnn*.so。Maven 上最新为 2.51.0,换版本前要确认与 ORT 1.29 的 QNN EP 兼容(E6,见 docs/engine.md)
|
| 35 |
+
QNN_RUNTIME_VERSION=2.42.0
|
| 36 |
+
# 主机离线编 HTP 上下文用的 onnxruntime-qnn 2.7.0(pip,插件 EP)带的 QNN 版本;探针 qnn251 flavor 在设备上用同一版本的运行库(R6)
|
| 37 |
+
QNN_RUNTIME_HOST_VERSION=2.51.0
|
| 38 |
+
ORT_QNN_PLUGIN_VERSION=2.7.0
|
| 39 |
+
|
| 40 |
+
# AAR 的 sha256(2026-10-16 从 repo1.maven.org 下载,sha1 与 Maven 发布的一致;E6)
|
| 41 |
+
ORT_ANDROID_AAR_SHA256=72683787a80d0608a3121b967d8e1239aef48286826661c953211626690540a7
|
| 42 |
+
ORT_ANDROID_QNN_AAR_SHA256=cc4cfd72a47703aee65d8e6ce7448fa7488e3fff6556f9d6d354ec1687347cfd
|
| 43 |
+
QNN_RUNTIME_AAR_SHA256=ecb69bd6a7b1be4717ef10805199fb49ec6b60ca3f481d3068ed9605fba91d5b
|