Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
39b9e50
fix(presets): support multi-preset toggle and add active border indic…
fabian-co Sep 26, 2026
77917d8
feat: implement usePresets hook for managing user presets and folders
fabian-co Sep 26, 2026
9415492
style: format presets panel and clean up assignment
fabian-co Sep 26, 2026
31b362f
feat: implement effects adjustment panel and Tauri backend processing…
fabian-co Oct 5, 2026
76ead1f
feat: add lens blur and relight image processing features with locali…
fabian-co Oct 5, 2026
5bd9916
feat: add localization files and relight effect components
fabian-co Oct 5, 2026
25641b2
feat: implement AI features including depth mapping, image segmentati…
fabian-co Oct 5, 2026
bf1c4b8
feat: add caching utilities, relight module, and localization support…
fabian-co Oct 5, 2026
266b194
feat: add RelightPanel component and Tauri backend commands for AI re…
fabian-co Oct 5, 2026
682f94d
feat: implement AI image processing backend module for segmentation, …
fabian-co Oct 6, 2026
2db8dc1
ci: skip Android build when signing secrets are missing
fabian-co Oct 6, 2026
9bc5fdf
Merge upstream/main into repomain
fabian-co Oct 7, 2026
9a1216f
Merge upstream/main into repomain
fabian-co Oct 7, 2026
3138fa7
Merge remote-tracking branch 'upstream/main'
fabian-co Oct 9, 2026
bc6f203
add gpu acceleration and linear dng output to ai denoise
fabian-co Oct 9, 2026
efa4612
feat: add internationalization locales, denoising backend, and UI pro…
fabian-co Oct 9, 2026
ccbefb4
add amount, color and detail controls to the ai denoise layer
fabian-co Oct 9, 2026
53cffa2
feat: add AI-powered facial refinement, skin retouching, and inpainti…
fabian-co Oct 10, 2026
9e82af9
feat(library): add picked and rejected views with counts to the sourc…
fabian-co Oct 31, 2026
0c60650
Merge remote-tracking branch 'upstream/main' into merge/upstream-2026…
fabian-co Oct 31, 2026
6663948
Merge pull request #1 from fabian-co/merge/upstream-2026-10-31
fabian-co Oct 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ on:
required: false
type: string
default: ''
android-signing:
required: false
type: string
default: ''

jobs:
build:
if: ${{ inputs.mobile != 'android' || inputs.android-signing != 'false' }}
runs-on: ${{ inputs.platform }}
steps:
- name: Checkout repository
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,24 @@ on:
- main

jobs:
# Forks do not have the Android signing secrets, so the Android build is skipped there.
android-signing:
runs-on: ubuntu-latest
outputs:
available: ${{ steps.check.outputs.available }}
steps:
- id: check
env:
ANDROID_KEY_BASE64: ${{ secrets.ANDROID_KEY_BASE64 }}
run: |
if [ -n "$ANDROID_KEY_BASE64" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi

build:
needs: android-signing
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -65,6 +82,7 @@ jobs:
build-args: ${{ matrix.args }}
asset-prefix: ${{ matrix.asset-prefix }}
mobile: ${{ matrix.mobile }}
android-signing: ${{ needs.android-signing.outputs.available }}
secrets: inherit
permissions:
contents: write
18 changes: 18 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ on:
types: [created]

jobs:
# Forks do not have the Android signing secrets, so the Android build is skipped there.
android-signing:
runs-on: ubuntu-latest
outputs:
available: ${{ steps.check.outputs.available }}
steps:
- id: check
env:
ANDROID_KEY_BASE64: ${{ secrets.ANDROID_KEY_BASE64 }}
run: |
if [ -n "$ANDROID_KEY_BASE64" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi

release:
needs: android-signing
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -66,6 +83,7 @@ jobs:
asset-name-pattern: '[name]_v[version]_[platform]_[arch][ext]'
asset-prefix: ${{ matrix.asset-prefix }}
mobile: ${{ matrix.mobile }}
android-signing: ${{ needs.android-signing.outputs.available }}
secrets: inherit
permissions:
contents: write
4 changes: 3 additions & 1 deletion src-tauri/src/ai_denoise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ pub fn apply_ai_denoise<'a>(
(adjustments["aiDenoiseAmount"].as_f64().unwrap_or(100.0) as f32 / 100.0).clamp(0.0, 1.0);
let detail =
(adjustments["aiDenoiseDetail"].as_f64().unwrap_or(0.0) as f32 / 100.0).clamp(0.0, 1.0);
let color =
(adjustments["aiDenoiseColor"].as_f64().unwrap_or(100.0) as f32 / 100.0).clamp(0.0, 1.0);
if amount <= 0.0 {
return Cow::Borrowed(base);
}
Expand Down Expand Up @@ -336,7 +338,7 @@ pub fn apply_ai_denoise<'a>(
let denoised: &[f16] = resized.as_deref().unwrap_or(&entry.data);

let luma_keep = 1.0 - amount * (1.0 - detail);
let chroma_keep = 1.0 - amount;
let chroma_keep = 1.0 - amount * color;

let mut out = base.to_rgb32f();
out.par_chunks_mut(BLEND_CHUNK)
Expand Down
74 changes: 74 additions & 0 deletions src-tauri/src/ai_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ use tauri::Manager;
use tokenizers::Tokenizer;
use tokio::sync::Mutex as TokioMutex;

use crate::face_parsing::{FaceModels, load_face_parser_session};
use crate::face_refine::FaceCropCache;

use crate::face_masking::FaceAnalysis;

const ENCODER_URL: &str = "https://huggingface.co/CyberTimon/RapidRAW-Models/resolve/main/sam_vit_b_01ec64_encoder.onnx?download=true";
Expand Down Expand Up @@ -124,6 +127,16 @@ const NORMAL_MAX_SIZE: u32 = 768;
const NORMAL_NUM_TOKENS: i64 = 2500;
const NORMAL_SHA256: &str = "24eacb5dc7a2c54c7bc98f7de085ffbed79ad006ea5b664c2c2cdc02ff3a52f0";

const FACE_DETECTOR_URL: &str = "https://huggingface.co/opencv/face_detection_yunet/resolve/main/face_detection_yunet_2023mar.onnx?download=true";
const FACE_DETECTOR_FILENAME: &str = "face_detection_yunet_2023mar.onnx";
const FACE_DETECTOR_SHA256: &str =
"8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4";

const FACE_PARSER_URL: &str =
"https://github.com/yakhyo/face-parsing/releases/download/v0.0.1/resnet18.onnx";
const FACE_PARSER_FILENAME: &str = "face_parsing_resnet18.onnx";
const FACE_PARSER_SHA256: &str = "0d9bd318e46987c3bdbfacae9e2c0f461cae1c6ac6ea6d43bbe541a91727e33f";

type SharedSession = Arc<Mutex<Session>>;

const GPU_IDLE: u8 = 0;
Expand All @@ -140,6 +153,20 @@ struct ModelAsset {
name: &'static str,
}

const FACE_DETECTOR_ASSET: ModelAsset = ModelAsset {
filename: FACE_DETECTOR_FILENAME,
url: FACE_DETECTOR_URL,
sha256: Some(FACE_DETECTOR_SHA256),
name: "Face Detection Model",
};

const FACE_PARSER_ASSET: ModelAsset = ModelAsset {
filename: FACE_PARSER_FILENAME,
url: FACE_PARSER_URL,
sha256: Some(FACE_PARSER_SHA256),
name: "Face Regions Model",
};

const SAM_ENCODER_ASSET: ModelAsset = ModelAsset {
filename: ENCODER_FILENAME,
url: ENCODER_URL,
Expand Down Expand Up @@ -691,6 +718,8 @@ pub struct AiState {
pub esam3_features: Option<Arc<Esam3Features>>,
pub face_analysis: Option<Arc<FaceAnalysis>>,
pub depth_map: Option<CachedDepthMap>,
pub face_models: Option<Arc<FaceModels>>,
pub face_crops: Option<Arc<FaceCropCache>>,
}

impl AiState {
Expand Down Expand Up @@ -1356,6 +1385,51 @@ pub async fn get_or_init_lama_model(
Ok(lama_model)
}

pub async fn get_or_init_face_models(
app_handle: &tauri::AppHandle,
ai_state_mutex: &Mutex<Option<AiState>>,
ai_init_lock: &TokioMutex<()>,
) -> Result<Arc<FaceModels>> {
let loaded = || {
ai_state_mutex
.lock()
.unwrap()
.as_ref()
.and_then(|state| state.face_models.clone())
};
if let Some(face_models) = loaded() {
return Ok(face_models);
}

let _guard = ai_init_lock.lock().await;

if let Some(face_models) = loaded() {
return Ok(face_models);
}

let models_dir = get_models_dir(app_handle)?;
download_and_verify_model(app_handle, &models_dir, &FACE_DETECTOR_ASSET).await?;
download_and_verify_model(app_handle, &models_dir, &FACE_PARSER_ASSET).await?;

let _ = ort::init().with_name("AI-Face").commit();
let detector = Session::builder()?.commit_from_file(models_dir.join(FACE_DETECTOR_FILENAME))?;
let parser = load_face_parser_session(&models_dir.join(FACE_PARSER_FILENAME))?;
let face_models = Arc::new(FaceModels {
detector: Mutex::new(detector),
parser: Mutex::new(parser),
});

crate::register_exit_handler();

ai_state_mutex
.lock()
.unwrap()
.get_or_insert_with(AiState::default)
.face_models = Some(face_models.clone());

Ok(face_models)
}

pub async fn get_or_init_normal_model(
app_handle: &tauri::AppHandle,
ai_state_mutex: &Mutex<Option<AiState>>,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub fn all_available_adjustments() -> HashSet<String> {
"aiDenoiseEnabled",
"aiDenoiseMethod",
"aiDenoiseAmount",
"aiDenoiseColor",
"aiDenoiseDetail",
"chromaticAberrationRedCyan",
"chromaticAberrationBlueYellow",
Expand Down
7 changes: 6 additions & 1 deletion src-tauri/src/cache_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ fn hash_ai_denoise(adjustments: &serde_json::Value, hasher: &mut DefaultHasher)
if enabled {
hash_effect_keys(
adjustments,
&["aiDenoiseMethod", "aiDenoiseAmount", "aiDenoiseDetail"],
&[
"aiDenoiseMethod",
"aiDenoiseAmount",
"aiDenoiseColor",
"aiDenoiseDetail",
],
hasher,
);
crate::ai_denoise::cache_epoch().hash(hasher);
Expand Down
Loading
Loading