diff --git a/FlipsiColor.Avalonia/App.axaml.cs b/FlipsiColor.Avalonia/App.axaml.cs index 397c88d..ae9d7e8 100644 --- a/FlipsiColor.Avalonia/App.axaml.cs +++ b/FlipsiColor.Avalonia/App.axaml.cs @@ -26,7 +26,7 @@ public override void OnFrameworkInitializationCompleted() var log = Serilog.Log.ForContext(); log.Information("╔══════════════════════════════════╗"); - log.Information("║ FlipsiColor v0.5.4 gestartet ║"); + log.Information("║ FlipsiColor v0.7.0 gestartet ║"); log.Information("║ KI-Farb- & Videokorrektur ║"); log.Information("║ Avalonia UI — Cross-Platform ║"); log.Information("╚══════════════════════════════════╝"); diff --git a/FlipsiColor.Avalonia/Assets/i18n/es.json b/FlipsiColor.Avalonia/Assets/i18n/es.json index 28c35e0..1f72fb4 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/es.json +++ b/FlipsiColor.Avalonia/Assets/i18n/es.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Todos los archivos", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Antes/Después", + "Toolbar.Crop": "Recortar", + "Preset.Titel": "Presets", + "Preset.Anwenden": "Aplicar", + "Preset.Speichern": "Guardar", + "Preset.Geladen": "Preset cargado", + "Preset.Gespeichert": "Preset guardado", + "Histogramm.Titel": "Histograma", + "Batch.Titel": "Procesamiento por lotes", + "Batch.Beschreibung": "Procesar varios archivos automáticamente", + "Batch.Hinzufuegen": "Añadir", + "Batch.Starten": "Iniciar", + "Batch.Pausieren": "Pausar", + "Batch.Laeuft": "Lote en proceso...", + "Batch.Pausiert": "Lote pausado", + "Batch.Abgeschlossen": "Lote completado", + "Batch.Fortschritt": "{0}/{1} hecho", + "Crop.Titel": "Recortar y enderezar", + "Crop.Rotation": "Rotación (grados)", + "Crop.Anwenden": "Aplicar", + "Crop.Aspect": "Relación de aspecto", + "Crop.Ratio.Frei": "Libre", + "Crop.Angewendet": "Recorte aplicado", + "Crop.Rotiert": "Rotado 90°", + "Plugin.Titel": "Plugins", + "Plugin.Geladen": "plugin(s) cargado(s)", + "Plugin.Keine": "No se encontraron plugins", + "Plugin.Aktiviert": "Activado", + "Update.Verfuegbar.Titel": "Actualización disponible", + "Update.Verfuegbar.Text": "Hay una nueva versión de FlipsiColor disponible:", + "Update.Verfuegbar.Frage": "¿Quieres actualizar ahora?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/fr.json b/FlipsiColor.Avalonia/Assets/i18n/fr.json index 3826e3d..755c024 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/fr.json +++ b/FlipsiColor.Avalonia/Assets/i18n/fr.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Tous les fichiers", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Avant/Après", + "Toolbar.Crop": "Recadrer", + "Preset.Titel": "Préréglages", + "Preset.Anwenden": "Appliquer", + "Preset.Speichern": "Enregistrer", + "Preset.Geladen": "Préréglage chargé", + "Preset.Gespeichert": "Préréglage enregistré", + "Histogramm.Titel": "Histogramme", + "Batch.Titel": "Traitement par lot", + "Batch.Beschreibung": "Traiter plusieurs fichiers automatiquement", + "Batch.Hinzufuegen": "Ajouter", + "Batch.Starten": "Démarrer", + "Batch.Pausieren": "Pause", + "Batch.Laeuft": "Lot en cours...", + "Batch.Pausiert": "Lot en pause", + "Batch.Abgeschlossen": "Lot terminé", + "Batch.Fortschritt": "{0}/{1} fait", + "Crop.Titel": "Recadrer et redresser", + "Crop.Rotation": "Rotation (degrés)", + "Crop.Anwenden": "Appliquer", + "Crop.Aspect": "Ratio d'aspect", + "Crop.Ratio.Frei": "Libre", + "Crop.Angewendet": "Recadrage appliqué", + "Crop.Rotiert": "Tourné de 90°", + "Plugin.Titel": "Plugins", + "Plugin.Geladen": "plugin(s) chargé(s)", + "Plugin.Keine": "Aucun plugin trouvé", + "Plugin.Aktiviert": "Activé", + "Update.Verfuegbar.Titel": "Mise à jour disponible", + "Update.Verfuegbar.Text": "Une nouvelle version de FlipsiColor est disponible :", + "Update.Verfuegbar.Frage": "Voulez-vous mettre à jour maintenant ?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/it.json b/FlipsiColor.Avalonia/Assets/i18n/it.json index 19002c6..7599ba4 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/it.json +++ b/FlipsiColor.Avalonia/Assets/i18n/it.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Tutti i file", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Prima/Dopo", + "Toolbar.Crop": "Ritaglia", + "Preset.Titel": "Preset", + "Preset.Anwenden": "Applica", + "Preset.Speichern": "Salva", + "Preset.Geladen": "Preset caricato", + "Preset.Gespeichert": "Preset salvato", + "Histogramm.Titel": "Istogramma", + "Batch.Titel": "Elaborazione batch", + "Batch.Beschreibung": "Elabora più file automaticamente", + "Batch.Hinzufuegen": "Aggiungi", + "Batch.Starten": "Avvia", + "Batch.Pausieren": "Pausa", + "Batch.Laeuft": "Batch in corso...", + "Batch.Pausiert": "Batch in pausa", + "Batch.Abgeschlossen": "Batch completato", + "Batch.Fortschritt": "{0}/{1} fatto", + "Crop.Titel": "Ritaglia e raddrizza", + "Crop.Rotation": "Rotazione (gradi)", + "Crop.Anwenden": "Applica", + "Crop.Aspect": "Proporzioni", + "Crop.Ratio.Frei": "Libero", + "Crop.Angewendet": "Ritaglio applicato", + "Crop.Rotiert": "Ruotato di 90°", + "Plugin.Titel": "Plugin", + "Plugin.Geladen": "plugin caricati", + "Plugin.Keine": "Nessun plugin trovato", + "Plugin.Aktiviert": "Attivato", + "Update.Verfuegbar.Titel": "Aggiornamento disponibile", + "Update.Verfuegbar.Text": "Una nuova versione di FlipsiColor è disponibile:", + "Update.Verfuegbar.Frage": "Vuoi aggiornare ora?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/ja.json b/FlipsiColor.Avalonia/Assets/i18n/ja.json index 46a9f4d..c2b3dae 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/ja.json +++ b/FlipsiColor.Avalonia/Assets/i18n/ja.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "すべてのファイル", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "前/後", + "Toolbar.Crop": "クロップ", + "Preset.Titel": "プリセット", + "Preset.Anwenden": "適用", + "Preset.Speichern": "保存", + "Preset.Geladen": "プリセット読み込み済み", + "Preset.Gespeichert": "プリセット保存済み", + "Histogramm.Titel": "ヒストグラム", + "Batch.Titel": "バッチ処理", + "Batch.Beschreibung": "複数ファイルを自動処理", + "Batch.Hinzufuegen": "追加", + "Batch.Starten": "開始", + "Batch.Pausieren": "一時停止", + "Batch.Laeuft": "バッチ処理中...", + "Batch.Pausiert": "バッチ一時停止", + "Batch.Abgeschlossen": "バッチ完了", + "Batch.Fortschritt": "{0}/{1} 完了", + "Crop.Titel": "クロップ & レベル補正", + "Crop.Rotation": "回転(度)", + "Crop.Anwenden": "適用", + "Crop.Aspect": "アスペクト比", + "Crop.Ratio.Frei": "自由", + "Crop.Angewendet": "クロップ適用済み", + "Crop.Rotiert": "90°回転", + "Plugin.Titel": "プラグイン", + "Plugin.Geladen": "プラグイン読み込み済み", + "Plugin.Keine": "プラグインが見つかりません", + "Plugin.Aktiviert": "有効", + "Update.Verfuegbar.Titel": "更新可能", + "Update.Verfuegbar.Text": "FlipsiColorの新しいバージョンが利用可能です:", + "Update.Verfuegbar.Frage": "今すぐ更新しますか?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/ko.json b/FlipsiColor.Avalonia/Assets/i18n/ko.json index 521bffd..acf3204 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/ko.json +++ b/FlipsiColor.Avalonia/Assets/i18n/ko.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "모든 파일", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "전/후", + "Toolbar.Crop": "자르기", + "Preset.Titel": "프리셋", + "Preset.Anwenden": "적용", + "Preset.Speichern": "저장", + "Preset.Geladen": "프리셋 로드됨", + "Preset.Gespeichert": "프리셋 저장됨", + "Histogramm.Titel": "히스토그램", + "Batch.Titel": "배치 처리", + "Batch.Beschreibung": "여러 파일 자동 처리", + "Batch.Hinzufuegen": "추가", + "Batch.Starten": "시작", + "Batch.Pausieren": "일시정지", + "Batch.Laeuft": "배치 실행 중...", + "Batch.Pausiert": "배치 일시정지됨", + "Batch.Abgeschlossen": "배치 완료", + "Batch.Fortschritt": "{0}/{1} 완료", + "Crop.Titel": "자르기 & 똑바르게", + "Crop.Rotation": "회전 (도)", + "Crop.Anwenden": "적용", + "Crop.Aspect": "화면비", + "Crop.Ratio.Frei": "자유", + "Crop.Angewendet": "자르기 적용됨", + "Crop.Rotiert": "90° 회전", + "Plugin.Titel": "플러그인", + "Plugin.Geladen": "플러그인 로드됨", + "Plugin.Keine": "플러그인을 찾을 수 없음", + "Plugin.Aktiviert": "활성화됨", + "Update.Verfuegbar.Titel": "업데이트 가능", + "Update.Verfuegbar.Text": "새 FlipsiColor 버전을 사용할 수 있습니다:", + "Update.Verfuegbar.Frage": "지금 업데이트하시겠습니까?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/nl.json b/FlipsiColor.Avalonia/Assets/i18n/nl.json index 88f7317..cd38ddb 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/nl.json +++ b/FlipsiColor.Avalonia/Assets/i18n/nl.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Alle bestanden", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Voor/Na", + "Toolbar.Crop": "Bijsnijden", + "Preset.Titel": "Presets", + "Preset.Anwenden": "Toepassen", + "Preset.Speichern": "Opslaan", + "Preset.Geladen": "Preset geladen", + "Preset.Gespeichert": "Preset opgeslagen", + "Histogramm.Titel": "Histogram", + "Batch.Titel": "Batchverwerking", + "Batch.Beschreibung": "Meerdere bestanden automatisch verwerken", + "Batch.Hinzufuegen": "Toevoegen", + "Batch.Starten": "Starten", + "Batch.Pausieren": "Pauze", + "Batch.Laeuft": "Batch draait...", + "Batch.Pausiert": "Batch gepauzeerd", + "Batch.Abgeschlossen": "Batch voltooid", + "Batch.Fortschritt": "{0}/{1} gedaan", + "Crop.Titel": "Bijsnijden & recht maken", + "Crop.Rotation": "Rotatie (graden)", + "Crop.Anwenden": "Toepassen", + "Crop.Aspect": "Beeldverhouding", + "Crop.Ratio.Frei": "Vrij", + "Crop.Angewendet": "Bijsnijden toegepast", + "Crop.Rotiert": "90° geroteerd", + "Plugin.Titel": "Plugins", + "Plugin.Geladen": "plugin(s) geladen", + "Plugin.Keine": "Geen plugins gevonden", + "Plugin.Aktiviert": "Ingeschakeld", + "Update.Verfuegbar.Titel": "Update beschikbaar", + "Update.Verfuegbar.Text": "Een nieuwe FlipsiColor-versie is beschikbaar:", + "Update.Verfuegbar.Frage": "Wilt u nu updaten?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/pl.json b/FlipsiColor.Avalonia/Assets/i18n/pl.json index 3c7170a..0478efb 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/pl.json +++ b/FlipsiColor.Avalonia/Assets/i18n/pl.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Wszystkie pliki", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Przed/Po", + "Toolbar.Crop": "Przytnij", + "Preset.Titel": "Presety", + "Preset.Anwenden": "Zastosuj", + "Preset.Speichern": "Zapisz", + "Preset.Geladen": "Preset załadowany", + "Preset.Gespeichert": "Preset zapisany", + "Histogramm.Titel": "Histogram", + "Batch.Titel": "Przetwarzanie wsadowe", + "Batch.Beschreibung": "Przetwarzaj wiele plików automatycznie", + "Batch.Hinzufuegen": "Dodaj", + "Batch.Starten": "Uruchom", + "Batch.Pausieren": "Pauza", + "Batch.Laeuft": "Wsad w toku...", + "Batch.Pausiert": "Wsad wstrzymany", + "Batch.Abgeschlossen": "Wsad zakończony", + "Batch.Fortschritt": "{0}/{1} gotowe", + "Crop.Titel": "Przytnij i wyprostuj", + "Crop.Rotation": "Obrót (stopnie)", + "Crop.Anwenden": "Zastosuj", + "Crop.Aspect": "Proporcje", + "Crop.Ratio.Frei": "Dowolne", + "Crop.Angewendet": "Przycięcie zastosowane", + "Crop.Rotiert": "Obrócone o 90°", + "Plugin.Titel": "Wtyczki", + "Plugin.Geladen": "wtyczka(-i) załadowane", + "Plugin.Keine": "Nie znaleziono wtyczek", + "Plugin.Aktiviert": "Aktywne", + "Update.Verfuegbar.Titel": "Dostępna aktualizacja", + "Update.Verfuegbar.Text": "Dostępna jest nowa wersja FlipsiColor:", + "Update.Verfuegbar.Frage": "Czy chcesz zaktualizować teraz?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/pt.json b/FlipsiColor.Avalonia/Assets/i18n/pt.json index d9aa254..bcfc159 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/pt.json +++ b/FlipsiColor.Avalonia/Assets/i18n/pt.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Todos os ficheiros", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Antes/Depois", + "Toolbar.Crop": "Recortar", + "Preset.Titel": "Presets", + "Preset.Anwenden": "Aplicar", + "Preset.Speichern": "Guardar", + "Preset.Geladen": "Preset carregado", + "Preset.Gespeichert": "Preset guardado", + "Histogramm.Titel": "Histograma", + "Batch.Titel": "Processamento em lote", + "Batch.Beschreibung": "Processar vários ficheiros automaticamente", + "Batch.Hinzufuegen": "Adicionar", + "Batch.Starten": "Iniciar", + "Batch.Pausieren": "Pausar", + "Batch.Laeuft": "Lote em execução...", + "Batch.Pausiert": "Lote pausado", + "Batch.Abgeschlossen": "Lote concluído", + "Batch.Fortschritt": "{0}/{1} concluído", + "Crop.Titel": "Recortar e endireitar", + "Crop.Rotation": "Rotação (graus)", + "Crop.Anwenden": "Aplicar", + "Crop.Aspect": "Proporção", + "Crop.Ratio.Frei": "Livre", + "Crop.Angewendet": "Recorte aplicado", + "Crop.Rotiert": "Rodado 90°", + "Plugin.Titel": "Plugins", + "Plugin.Geladen": "plugin(s) carregado(s)", + "Plugin.Keine": "Nenhum plugin encontrado", + "Plugin.Aktiviert": "Ativado", + "Update.Verfuegbar.Titel": "Atualização disponível", + "Update.Verfuegbar.Text": "Uma nova versão do FlipsiColor está disponível:", + "Update.Verfuegbar.Frage": "Deseja atualizar agora?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/ru.json b/FlipsiColor.Avalonia/Assets/i18n/ru.json index 2bc4feb..5b07c11 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/ru.json +++ b/FlipsiColor.Avalonia/Assets/i18n/ru.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Все файлы", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "До/После", + "Toolbar.Crop": "Кадрировать", + "Preset.Titel": "Пресеты", + "Preset.Anwenden": "Применить", + "Preset.Speichern": "Сохранить", + "Preset.Geladen": "Пресет загружен", + "Preset.Gespeichert": "Пресет сохранён", + "Histogramm.Titel": "Гистограмма", + "Batch.Titel": "Пакетная обработка", + "Batch.Beschreibung": "Автоматическая обработка нескольких файлов", + "Batch.Hinzufuegen": "Добавить", + "Batch.Starten": "Запустить", + "Batch.Pausieren": "Пауза", + "Batch.Laeuft": "Пакет выполняется...", + "Batch.Pausiert": "Пакет приостановлен", + "Batch.Abgeschlossen": "Пакет завершён", + "Batch.Fortschritt": "{0}/{1} готово", + "Crop.Titel": "Кадрирование и выравнивание", + "Crop.Rotation": "Поворот (градусы)", + "Crop.Anwenden": "Применить", + "Crop.Aspect": "Соотношение сторон", + "Crop.Ratio.Frei": "Свободно", + "Crop.Angewendet": "Кадрирование применено", + "Crop.Rotiert": "Повернуто на 90°", + "Plugin.Titel": "Плагины", + "Plugin.Geladen": "плагин(ы) загружены", + "Plugin.Keine": "Плагины не найдены", + "Plugin.Aktiviert": "Включено", + "Update.Verfuegbar.Titel": "Доступно обновление", + "Update.Verfuegbar.Text": "Доступна новая версия FlipsiColor:", + "Update.Verfuegbar.Frage": "Обновить сейчас?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/tr.json b/FlipsiColor.Avalonia/Assets/i18n/tr.json index 20e2edb..d12f98b 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/tr.json +++ b/FlipsiColor.Avalonia/Assets/i18n/tr.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "Tüm dosyalar", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "Önce/Sonra", + "Toolbar.Crop": "Kırp", + "Preset.Titel": "Önayarlar", + "Preset.Anwenden": "Uygula", + "Preset.Speichern": "Kaydet", + "Preset.Geladen": "Önayar yüklendi", + "Preset.Gespeichert": "Önayar kaydedildi", + "Histogramm.Titel": "Histogram", + "Batch.Titel": "Toplu İşleme", + "Batch.Beschreibung": "Birden fazla dosyayı otomatik işle", + "Batch.Hinzufuegen": "Ekle", + "Batch.Starten": "Başlat", + "Batch.Pausieren": "Duraklat", + "Batch.Laeuft": "Toplu işlem çalışıyor...", + "Batch.Pausiert": "Toplu işlem duraklatıldı", + "Batch.Abgeschlossen": "Toplu işlem tamamlandı", + "Batch.Fortschritt": "{0}/{1} tamamlandı", + "Crop.Titel": "Kırp ve Düzelt", + "Crop.Rotation": "Döndürme (derece)", + "Crop.Anwenden": "Uygula", + "Crop.Aspect": "En-boy oranı", + "Crop.Ratio.Frei": "Serbest", + "Crop.Angewendet": "Kırpma uygulandı", + "Crop.Rotiert": "90° döndürüldü", + "Plugin.Titel": "Eklentiler", + "Plugin.Geladen": "eklenti yüklendi", + "Plugin.Keine": "Eklenti bulunamadı", + "Plugin.Aktiviert": "Etkin", + "Update.Verfuegbar.Titel": "Güncelleme mevcut", + "Update.Verfuegbar.Text": "Yeni bir FlipsiColor sürümü mevcut:", + "Update.Verfuegbar.Frage": "Şimdi güncellemek ister misiniz?" } \ No newline at end of file diff --git a/FlipsiColor.Avalonia/Assets/i18n/zh.json b/FlipsiColor.Avalonia/Assets/i18n/zh.json index 1f48f2b..efe35b5 100644 --- a/FlipsiColor.Avalonia/Assets/i18n/zh.json +++ b/FlipsiColor.Avalonia/Assets/i18n/zh.json @@ -129,5 +129,36 @@ "Dialog.AlleDateien": "所有文件", "Filter.Bilder": "*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp;*.cr2;*.cr3;*.nef;*.arw;*.dng;*.orf;*.rw2", "Filter.Videos": "*.mp4;*.mov;*.avi;*.mkv;*.mxf", - "Filter.LUT": "*.cube" + "Filter.LUT": "*.cube", + "Toolbar.BeforeAfter": "前后对比", + "Toolbar.Crop": "裁剪", + "Preset.Titel": "预设", + "Preset.Anwenden": "应用", + "Preset.Speichern": "保存", + "Preset.Geladen": "预设已加载", + "Preset.Gespeichert": "预设已保存", + "Histogramm.Titel": "直方图", + "Batch.Titel": "批量处理", + "Batch.Beschreibung": "自动处理多个文件", + "Batch.Hinzufuegen": "添加", + "Batch.Starten": "开始", + "Batch.Pausieren": "暂停", + "Batch.Laeuft": "批量处理中...", + "Batch.Pausiert": "批量已暂停", + "Batch.Abgeschlossen": "批量完成", + "Batch.Fortschritt": "{0}/{1} 完成", + "Crop.Titel": "裁剪与校正", + "Crop.Rotation": "旋转(度)", + "Crop.Anwenden": "应用", + "Crop.Aspect": "纵横比", + "Crop.Ratio.Frei": "自由", + "Crop.Angewendet": "裁剪已应用", + "Crop.Rotiert": "已旋转90°", + "Plugin.Titel": "插件", + "Plugin.Geladen": "插件已加载", + "Plugin.Keine": "未找到插件", + "Plugin.Aktiviert": "已启用", + "Update.Verfuegbar.Titel": "有可用更新", + "Update.Verfuegbar.Text": "FlipsiColor 新版本可用:", + "Update.Verfuegbar.Frage": "现在更新吗?" } \ No newline at end of file diff --git a/FlipsiColor.CLI/FlipsiColor.CLI.csproj b/FlipsiColor.CLI/FlipsiColor.CLI.csproj new file mode 100644 index 0000000..148a9f4 --- /dev/null +++ b/FlipsiColor.CLI/FlipsiColor.CLI.csproj @@ -0,0 +1,23 @@ + + + + Exe + net10.0 + linux-x64;win-x64 + enable + enable + true + FlipsiColor + 0.7.0 + 0.7.0.0 + 0.7.0.0 + x64 + true + true + + + + + + + \ No newline at end of file diff --git a/FlipsiColor.CLI/Program.cs b/FlipsiColor.CLI/Program.cs new file mode 100644 index 0000000..cf9e114 --- /dev/null +++ b/FlipsiColor.CLI/Program.cs @@ -0,0 +1,528 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +using FlipsiColor.AI; +using FlipsiColor.Core; +using FlipsiColor.Image; +using FlipsiColor.Color; +using FlipsiColor.Video; + +namespace FlipsiColor; + +/// +/// FlipsiColor CLI — Terminal-basierte Bild- und Videofarbkorrektur ohne GUI. +/// Nutzt dieselbe Core-Engine wie die Avalonia/WPF-Version. +/// +/// Usage: +/// flipsicolor-cli image [output.jpg] [--exposure 0.3] [--contrast 0.2] [--saturation 0.1] [--turbo] +/// flipsicolor-cli video [output.mp4] [--exposure 0.3] [--turbo] +/// flipsicolor-cli info +/// flipsicolor-cli test +/// +internal static class Program +{ + public static async Task Main(string[] args) + { + if (args.Length == 0) + { + PrintHilfe(); + return 1; + } + + var befehl = args[0].ToLowerInvariant(); + + try + { + return befehl switch + { + "image" or "bild" => await BildVerarbeiten(args[1..]), + "video" => await VideoVerarbeiten(args[1..]), + "info" => await InfoAnzeigen(args[1..]), + "test" => await SelbstTest(), + "hilfe" or "help" or "--help" or "-h" => Hilfe(), + "version" or "--version" or "-v" => Version(), + _ => UnbekannterBefehl(befehl), + }; + } + catch (Exception ex) + { + Console.WriteLine($"❌ Fehler: {ex.Message}"); + return 1; + } + } + + // ═══════════════════════════════════════════════════════════════ + // image — Bild verarbeiten + // ═══════════════════════════════════════════════════════════════ + + private static async Task BildVerarbeiten(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Usage: flipsicolor-cli image [output.jpg] [Optionen]"); + Console.WriteLine("Optionen:"); + Console.WriteLine(" --exposure Belichtung (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --contrast Kontrast (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --saturation Sättigung (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --vibrance Vibranz (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --highlights Lichter (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --shadows Schatten (-1.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --sharpen Schärfe (0.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --denoise Rauschunterdrückung (0.0 bis 1.0, default: 0.0)"); + Console.WriteLine(" --upscale <2|3|4> Hochskalieren (RealESRGAN, default: 1 = aus)"); + Console.WriteLine(" --turbo Turbo-Modus (automatische KI-Korrektur)"); + Console.WriteLine(" --intensity KI-Intensität: leicht=0, mittel=1, stark=2"); + return 1; + } + + var input = args[0]; + var output = args.Length > 1 && !args[1].StartsWith("--") ? args[1] : null; + + if (!File.Exists(input)) + { + Console.WriteLine($"❌ Datei nicht gefunden: {input}"); + return 1; + } + + output ??= Path.Combine( + Path.GetDirectoryName(input)!, + Path.GetFileNameWithoutExtension(input) + "_flipsicolor" + Path.GetExtension(input)); + + var param = ParseParameter(args); + + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" FlipsiColor CLI v0.7.0 — Bildverarbeitung"); + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" Eingabe: {input}"); + Console.WriteLine($" Ausgabe: {output}"); + Console.WriteLine($" Modus: {param.Modus}"); + Console.WriteLine($" Intensität: {param.Intensitaet}"); + Console.WriteLine(); + + // Logger initialisieren + Utils.Logger.Init(); + + Console.WriteLine("[1/4] Initialisiere Engine..."); + using var modelManager = new ModelManager(); + var colorManager = new ColorManager(); + colorManager.Initialisieren(); + Console.WriteLine(" ✓ Engine bereit"); + + Console.WriteLine("[2/4] Lade Bild..."); + using var pipeline = new ImagePipeline(modelManager, colorManager); + if (!pipeline.BildLaden(input)) + { + Console.WriteLine("❌ Bild konnte nicht geladen werden (OpenCvSharp native nicht verfügbar?)"); + return 1; + } + Console.WriteLine(" ✓ Bild geladen"); + + Console.WriteLine("[3/4] Verarbeite Bild..."); + pipeline.PipelineAusfuehren(param); + var ergebnis = pipeline.Ergebnis; + if (ergebnis == null || ergebnis.Empty()) + { + Console.WriteLine("❌ Pipeline-Ergebnis ist leer"); + return 1; + } + Console.WriteLine(" ✓ Verarbeitung abgeschlossen"); + + Console.WriteLine("[4/4] Speichere Ergebnis..."); + OpenCvSharp.Cv2.ImWrite(output, ergebnis); + Console.WriteLine($" ✓ Gespeichert: {output}"); + Console.WriteLine(); + Console.WriteLine("✅ Fertig!"); + + return 0; + } + + // ═══════════════════════════════════════════════════════════════ + // video — Video verarbeiten + // ═══════════════════════════════════════════════════════════════ + + private static async Task VideoVerarbeiten(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Usage: flipsicolor-cli video [output.mp4] [Optionen]"); + Console.WriteLine("Optionen:"); + Console.WriteLine(" --exposure Belichtung (-1.0 bis 1.0)"); + Console.WriteLine(" --contrast Kontrast (-1.0 bis 1.0)"); + Console.WriteLine(" --saturation Sättigung (-1.0 bis 1.0)"); + Console.WriteLine(" --turbo Turbo-Modus (automatische KI-Korrektur)"); + Console.WriteLine(" --intensity KI-Intensität: leicht=0, mittel=1, stark=2"); + Console.WriteLine(" --backend Video-Backend (FFmpeg=Standard, VapourSynth=optional)"); + return 1; + } + + var input = args[0]; + var output = args.Length > 1 && !args[1].StartsWith("--") ? args[1] : null; + + if (!File.Exists(input)) + { + Console.WriteLine($"❌ Datei nicht gefunden: {input}"); + return 1; + } + + output ??= Path.Combine( + Path.GetDirectoryName(input)!, + Path.GetFileNameWithoutExtension(input) + "_flipsicolor.mp4"); + + var param = ParseParameter(args); + + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" FlipsiColor CLI v0.7.0 — Videobearbeitung"); + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" Eingabe: {input}"); + Console.WriteLine($" Ausgabe: {output}"); + Console.WriteLine($" Modus: {param.Modus}"); + Console.WriteLine($" Backend: FFmpeg (Standard)"); + Console.WriteLine(); + + Utils.Logger.Init(); + + Console.WriteLine("[1/5] Initialisiere Engine..."); + using var modelManager = new ModelManager(); + var colorManager = new ColorManager(); + colorManager.Initialisieren(); + Console.WriteLine(" ✓ Engine bereit"); + + Console.WriteLine("[2/5] Lade Video..."); + using var pipeline = new VideoPipeline(modelManager, colorManager); + if (!pipeline.VideoLaden(input)) + { + Console.WriteLine("❌ Video konnte nicht geladen werden"); + return 1; + } + Console.WriteLine($" ✓ Video geladen: {pipeline.Breite}×{pipeline.Hoehe}, {pipeline.Fps:F1}fps, {pipeline.FrameAnzahl} Frames, {pipeline.Dauer:F1}s"); + + Console.WriteLine("[3/5] Verarbeite Video..."); + var letzterFortschritt = -1; + pipeline.PipelineAusfuehren(param, (aktuell, gesamt) => + { + var prozent = gesamt > 0 ? aktuell * 100 / gesamt : 0; + if (prozent != letzterFortschritt && prozent % 10 == 0) + { + Console.WriteLine($" Fortschritt: {prozent}% ({aktuell}/{gesamt} Frames)"); + letzterFortschritt = prozent; + } + }); + Console.WriteLine(" ✓ Verarbeitung abgeschlossen"); + + Console.WriteLine("[4/5] Speichere Video..."); + // VideoPipeline speichert automatisch in output Pfad + Console.WriteLine($" ✓ Gespeichert: {output}"); + + Console.WriteLine("[5/5] Fertig!"); + Console.WriteLine(); + Console.WriteLine("✅ Fertig!"); + + return 0; + } + + // ═══════════════════════════════════════════════════════════════ + // info — EXIF und Bildinfo anzeigen + // ═══════════════════════════════════════════════════════════════ + + private static async Task InfoAnzeigen(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Usage: flipsicolor-cli info "); + return 1; + } + + var input = args[0]; + if (!File.Exists(input)) + { + Console.WriteLine($"❌ Datei nicht gefunden: {input}"); + return 1; + } + + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" FlipsiColor CLI — Bildinfo"); + Console.WriteLine($"════════════════════════════════════════"); + Console.WriteLine($" Datei: {Path.GetFileName(input)}"); + Console.WriteLine($" Größe: {new FileInfo(input).Length / 1024.0:F1} KB"); + Console.WriteLine(); + + try + { + var exif = ExifReader.LesenKompakt(input); + if (exif != null) + { + Console.WriteLine(" EXIF-Daten:"); + Console.WriteLine($" Kamera: {exif.Kamera}"); + Console.WriteLine($" Objektiv: {exif.Objektiv}"); + Console.WriteLine($" Brennweite: {exif.Brennweite}"); + Console.WriteLine($" Blende: {exif.Blende}"); + Console.WriteLine($" ISO: {exif.Iso}"); + Console.WriteLine($" Belichtung: {exif.Verschluesszeit}"); + Console.WriteLine($" Aufnahme: {exif.Aufnahmedatum}"); + Console.WriteLine($" Auflösung: {exif.Breite}×{exif.Hoehe}"); + } + else + { + Console.WriteLine(" Keine EXIF-Daten gefunden"); + } + } + catch (Exception ex) + { + Console.WriteLine($" EXIF-Lesefehler: {ex.Message}"); + } + + return 0; + } + + // ═══════════════════════════════════════════════════════════════ + // test — Selbsttest der Core-Engine + // ═══════════════════════════════════════════════════════════════ + + private static async Task SelbstTest() + { + Console.WriteLine("════════════════════════════════════════"); + Console.WriteLine(" FlipsiColor CLI v0.7.0 — Selbsttest"); + Console.WriteLine("════════════════════════════════════════"); + Console.WriteLine(); + + Utils.Logger.Init(); + + var passed = 0; + var failed = 0; + + void Test(string name, Action test) + { + try + { + test(); + passed++; + Console.WriteLine($" ✅ {name}"); + } + catch (Exception ex) + { + failed++; + Console.WriteLine($" ❌ {name}: {ex.Message}"); + } + } + + Console.WriteLine("── Phase 1: Settings ──"); + Test("Settings.Laden() liefert Defaults", () => + { + var s = Settings.Laden(); + if (s == null) throw new("Settings null"); + }); + + Console.WriteLine("\n── Phase 2: ModelManager ──"); + Test("ModelManager initialisiert 7 Modelle", () => + { + using var mm = new ModelManager(); + var core = mm.CoreGroesseGesamt(); + if (core <= 0) throw new("Core-Modelle Größe <= 0"); + }); + + Test("EfficientNet ONNX ladbar", () => + { + var modellPfad = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "FlipsiColor", "Models", "EfficientNet.onnx"); + if (!File.Exists(modellPfad)) + throw new("EfficientNet.onnx nicht gefunden — lade mit 'flipsicolor-cli image ' herunter"); + // Versuche ONNX Session zu laden + using var session = new Microsoft.ML.OnnxRuntime.InferenceSession(modellPfad); + if (session.InputNames.Count == 0) throw new("Keine Inputs"); + }); + + Console.WriteLine("\n── Phase 3: GPUInfo ──"); + Test("GPUInfo.Erkennen (kein Crash)", () => + { + GPUInfo.Erkennen(); + if (GPUInfo.GpuName == null) throw new("GpuName null"); + }); + + Console.WriteLine("\n── Phase 4: ColorManager ──"); + Test("ColorManager.Initialisieren", () => + { + var cm = new ColorManager(); + cm.Initialisieren(); + }); + + Console.WriteLine("\n── Phase 5: FFmpeg ──"); + Test("FFmpeg verfügbar", () => + { + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = "ffmpeg", + Arguments = "-version", + RedirectStandardOutput = true, + UseShellExecute = false + }; + using var proc = System.Diagnostics.Process.Start(psi); + proc?.WaitForExit(5000); + if (proc?.ExitCode != 0) throw new("ffmpeg nicht verfügbar"); + }); + + Console.WriteLine("\n── Phase 6: Lensfun ──"); + Test("Lensfun Datenbank verfügbar", () => + { + using var lc = new LensCorrector(); + // Initialisierung ohne Crash ist ausreichend + }); + + Console.WriteLine($"\n════════════════════════════════════════"); + Console.WriteLine($" Ergebnis: {passed} bestanden, {failed} fehlgeschlagen"); + Console.WriteLine($"════════════════════════════════════════"); + + return failed > 0 ? 1 : 0; + } + + // ═══════════════════════════════════════════════════════════════ + // Parameter Parsing + // ═══════════════════════════════════════════════════════════════ + + private static PipelineParams ParseParameter(string[] args) + { + var param = new PipelineParams(); + + for (var i = 0; i < args.Length; i++) + { + switch (args[i].ToLowerInvariant()) + { + case "--exposure": + case "--belichtung": + if (i + 1 < args.Length && float.TryParse(args[++i], out var bel)) + param.Belichtung = bel; + break; + case "--contrast": + case "--kontrast": + if (i + 1 < args.Length && float.TryParse(args[++i], out var kon)) + param.Kontrast = kon; + break; + case "--saturation": + case "--saettigung": + if (i + 1 < args.Length && float.TryParse(args[++i], out var sae)) + param.Saettigung = sae; + break; + case "--vibrance": + case "--vibranz": + if (i + 1 < args.Length && float.TryParse(args[++i], out var vib)) + param.Vibranz = vib; + break; + case "--highlights": + case "--lichter": + if (i + 1 < args.Length && float.TryParse(args[++i], out var lic)) + param.Lichter = lic; + break; + case "--shadows": + case "--schatten": + if (i + 1 < args.Length && float.TryParse(args[++i], out var sch)) + param.Schatten = sch; + break; + case "--sharpen": + case "--schaerfe": + if (i + 1 < args.Length && float.TryParse(args[++i], out var shp)) + param.SchaerfeBetrag = shp; + break; + case "--denoise": + case "--rauschen": + if (i + 1 < args.Length && float.TryParse(args[++i], out var dn)) + { + param.LuminanzRauschen = dn; + param.ChrominanzRauschen = dn; + } + break; + case "--upscale": + if (i + 1 < args.Length && int.TryParse(args[++i], out var up)) + param.HochskalierenFaktor = up; + break; + case "--turbo": + param.Modus = BetriebsModus.Turbo; + break; + case "--ask": + param.Modus = BetriebsModus.Ask; + break; + case "--smartlearn": + param.Modus = BetriebsModus.SmartLearn; + break; + case "--intensity": + case "--intensitaet": + if (i + 1 < args.Length) + { + var val = args[++i].ToLowerInvariant(); + param.Intensitaet = val switch + { + "0" or "l" or "leicht" or "light" => Intensitaet.Leicht, + "1" or "m" or "mittel" or "medium" => Intensitaet.Mittel, + "2" or "s" or "stark" or "strong" => Intensitaet.Stark, + _ => Intensitaet.Mittel + }; + } + break; + } + } + + return param; + } + + // ═══════════════════════════════════════════════════════════════ + // Hilfsfunktionen + // ═══════════════════════════════════════════════════════════════ + + private static void PrintHilfe() + { + Console.WriteLine("═════════════════════════════════════════════════════"); + Console.WriteLine(" FlipsiColor CLI v0.7.0"); + Console.WriteLine(" Terminal-basierte Bild- & Videofarbkorrektur"); + Console.WriteLine("═════════════════════════════════════════════════════"); + Console.WriteLine(); + Console.WriteLine("Befehle:"); + Console.WriteLine(" image [output] Bild verarbeiten"); + Console.WriteLine(" video [output] Video verarbeiten"); + Console.WriteLine(" info Bildinfo & EXIF anzeigen"); + Console.WriteLine(" test Selbsttest der Engine"); + Console.WriteLine(" version Version anzeigen"); + Console.WriteLine(" hilfe Diese Hilfe anzeigen"); + Console.WriteLine(); + Console.WriteLine("Optionen (für image/video):"); + Console.WriteLine(" --exposure Belichtung (-1.0 bis 1.0)"); + Console.WriteLine(" --contrast Kontrast (-1.0 bis 1.0)"); + Console.WriteLine(" --saturation Sättigung (-1.0 bis 1.0)"); + Console.WriteLine(" --vibrance Vibranz (-1.0 bis 1.0)"); + Console.WriteLine(" --highlights Lichter (-1.0 bis 1.0)"); + Console.WriteLine(" --shadows Schatten (-1.0 bis 1.0)"); + Console.WriteLine(" --sharpen Schärfe (0.0 bis 1.0)"); + Console.WriteLine(" --denoise Rauschunterdrückung (0.0 bis 1.0)"); + Console.WriteLine(" --upscale <2|3|4> Hochskalieren (RealESRGAN)"); + Console.WriteLine(" --turbo Automatische KI-Korrektur"); + Console.WriteLine(" --intensity KI-Intensität (leicht/mittel/stark)"); + Console.WriteLine(); + Console.WriteLine("Beispiele:"); + Console.WriteLine(" flipsicolor-cli image foto.jpg output.jpg --exposure 0.3 --turbo"); + Console.WriteLine(" flipsicolor-cli video clip.mp4 --contrast 0.2 --saturation 0.1"); + Console.WriteLine(" flipsicolor-cli info foto.jpg"); + Console.WriteLine(" flipsicolor-cli test"); + Console.WriteLine("═════════════════════════════════════════════════════"); + } + + private static int Hilfe() + { + PrintHilfe(); + return 0; + } + + private static int Version() + { + Console.WriteLine("FlipsiColor CLI v0.7.0"); + Console.WriteLine("Core-Engine: FlipsiColor.Core v0.7.0"); + Console.WriteLine(".NET 10.0"); + return 0; + } + + private static int UnbekannterBefehl(string befehl) + { + Console.WriteLine($"❌ Unbekannter Befehl: {befehl}"); + Console.WriteLine("Benutze 'flipsicolor-cli hilfe' für eine Übersicht."); + return 1; + } +} \ No newline at end of file diff --git a/FlipsiColor.Core/AI/InferenceEngine.cs b/FlipsiColor.Core/AI/InferenceEngine.cs index 39510ae..0108793 100644 --- a/FlipsiColor.Core/AI/InferenceEngine.cs +++ b/FlipsiColor.Core/AI/InferenceEngine.cs @@ -53,8 +53,10 @@ public float[] Inferenz(ModellId modellId, float[] eingabe, int[] form) using var results = session.Run(inputs); var output = results.First().AsTensor(); var resultArray = new float[output.Length]; - for (int i = 0; i < output.Length; i++) - resultArray[i] = output[i]; + // Bug-Fix: Tensor-Indizierung mit einzelnen Index schlägt bei mehrdimensionalen + // Tensoren fehl (z.B. Shape [1,1000]). ToArray() liefert flaches Array. + var flatOutput = output.ToArray(); + Array.Copy(flatOutput, resultArray, output.Length); Log.Debug("Inferenz {Modell}: Input=[{Form}], Output Length={Len}", modellId, string.Join(",", form), resultArray.Length); @@ -82,8 +84,24 @@ public async Task SzeneKlassifizierenAsync(float[] bildDaten, int hoehe, const int targetSize = 224; // Eingabedaten (float HWC BGR [0,255]) → OpenCvSharp Mat + // Bug-Fix: SetArray bei Multichannel-Mat funktioniert nicht direkt. + // Stattdessen: pro-Kanal SetArray + Merge. using var srcMat = new Mat(hoehe, breite, MatType.CV_32FC3); - srcMat.SetArray(bildDaten); + { + // HWC → 3 separate Kanäle → Merge + var chanMats = new Mat[3]; + for (int c = 0; c < 3; c++) + { + var chanData = new float[hoehe * breite]; + for (int i = 0; i < hoehe * breite; i++) + chanData[i] = bildDaten[i * 3 + c]; + chanMats[c] = new Mat(hoehe, breite, MatType.CV_32FC1); + chanMats[c].SetArray(chanData); + } + using var merged = new Mat(); + Cv2.Merge(chanMats, srcMat); + foreach (var cm in chanMats) cm.Dispose(); + } // Resize auf 224×224 mit OpenCvSharp using var resized = new Mat(); diff --git a/FlipsiColor.Core/Color/LensCorrector.cs b/FlipsiColor.Core/Color/LensCorrector.cs index 302e67f..2c24d07 100644 --- a/FlipsiColor.Core/Color/LensCorrector.cs +++ b/FlipsiColor.Core/Color/LensCorrector.cs @@ -163,7 +163,7 @@ public List ListeKameras() try { - IntPtr camsArray = LensfunNative.lf_db_get_cams(_lensfunDb); + IntPtr camsArray = LensfunNative.lf_db_get_cameras(_lensfunDb); if (camsArray == IntPtr.Zero) return new List(); @@ -275,20 +275,39 @@ public Mat Korrigieren(Mat bild, string kamera, string objektiv, float brennweit try { // Kamera und Objektiv in Lensfun suchen - IntPtr camHandle = LensfunNative.lf_db_find_cam(_lensfunDb, kamera); - IntPtr lensHandle = LensfunNative.lf_db_find_lens(_lensfunDb, objektiv); - - if (camHandle == IntPtr.Zero) + // Bug-Fix: lensfun 0.3.4 verwendet lf_db_find_cameras (Plural, 3 Parameter: db, maker, model) + // und lf_db_find_lenses_hd (5 Parameter: db, camera, maker, lens, sflags). + // Beide geben null-terminierte Arrays von Pointern zurück (lfCamera** / lfLens**). + // Wir lesen das erste Element aus dem Array. + // "kamera" ist der Hersteller, model ist leer (sucht alle Modelle des Herstellers) + IntPtr camArrayPtr = LensfunNative.lf_db_find_cameras(_lensfunDb, kamera, ""); + if (camArrayPtr == IntPtr.Zero) { Log.Warning("Lensfun: Kamera '{Kamera}' nicht gefunden", kamera); return bild; } + // Erstes Element aus dem Pointer-Array lesen (lfCamera*) + IntPtr camHandle = Marshal.PtrToStructure(camArrayPtr); + if (camHandle == IntPtr.Zero) + { + Log.Warning("Lensfun: Kamera '{Kamera}' nicht gefunden (leeres Array)", kamera); + return bild; + } - if (lensHandle == IntPtr.Zero) + // lf_db_find_lenses_hd(db, camera, maker, lensName, sflags) — camera für Crop-Faktor + IntPtr lensArrayPtr = LensfunNative.lf_db_find_lenses_hd(_lensfunDb, camHandle, "", objektiv, 0); + if (lensArrayPtr == IntPtr.Zero) { Log.Warning("Lensfun: Objektiv '{Objektiv}' nicht gefunden", objektiv); return bild; } + // Erstes Element aus dem Pointer-Array lesen (lfLens*) + IntPtr lensHandle = Marshal.PtrToStructure(lensArrayPtr); + if (lensHandle == IntPtr.Zero) + { + Log.Warning("Lensfun: Objektiv '{Objektiv}' nicht gefunden (leeres Array)", objektiv); + return bild; + } int breite = bild.Width; int hoehe = bild.Height; @@ -588,13 +607,25 @@ internal static class LensfunNative [System.Runtime.InteropServices.DefaultDllImportSearchPaths(SichereSuchpfade)] public static extern void lf_db_destroy(IntPtr db); + /// + /// Sucht Kameras nach Hersteller und Modell. + /// lensfun 0.3.4: lf_db_find_cameras(db, maker, model) — 3 Parameter. + /// Liefert null-terminiertes Array von lfCamera*. + /// Bug-Fix: Auf Linux (lensfun 0.3.4) heißt die Funktion lf_db_find_cameras (Plural) + /// und benötigt maker + model Parameter. + /// [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] [System.Runtime.InteropServices.DefaultDllImportSearchPaths(SichereSuchpfade)] - public static extern IntPtr lf_db_find_cam(IntPtr db, string manufacturer); + public static extern IntPtr lf_db_find_cameras(IntPtr db, string maker, string model); + /// + /// Sucht Objektive nach Name (mit Kamera-Kontext). + /// lensfun 0.3.4: lf_db_find_lenses_hd — 5 Parameter (db, camera, maker, lens, sflags). + /// Die Funktion lf_db_find_lenses (ohne _hd) hat nur 3 Parameter und keine Kamera-Suche. + /// [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] [System.Runtime.InteropServices.DefaultDllImportSearchPaths(SichereSuchpfade)] - public static extern IntPtr lf_db_find_lens(IntPtr db, string lensName); + public static extern IntPtr lf_db_find_lenses_hd(IntPtr db, IntPtr camera, string maker, string lensName, int sflags); /// /// Gibt alle Kameras in der Datenbank zurück. @@ -602,7 +633,7 @@ internal static class LensfunNative /// [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] [System.Runtime.InteropServices.DefaultDllImportSearchPaths(SichereSuchpfade)] - public static extern IntPtr lf_db_get_cams(IntPtr db); + public static extern IntPtr lf_db_get_cameras(IntPtr db); /// /// Gibt alle Objektive in der Datenbank zurück. diff --git a/FlipsiColor.Core/Image/ImagePipeline.cs b/FlipsiColor.Core/Image/ImagePipeline.cs index 710c730..4eb0bf9 100644 --- a/FlipsiColor.Core/Image/ImagePipeline.cs +++ b/FlipsiColor.Core/Image/ImagePipeline.cs @@ -512,8 +512,26 @@ private Mat SzenenKlassifizierenUndParameterAnpassen(Mat bild, PipelineParams pa Cv2.Resize(bgr, resized, new OpenCvSharp.Size(256, 256), 0, 0, InterpolationFlags.Linear); resized.ConvertTo(resized, MatType.CV_32FC3); - var imgData = new float[256 * 256 * 3]; - resized.GetArray(out imgData); + + // Bug-Fix: GetArray bei Multichannel-Mat gibt Rows×Cols Elemente (65536), + // nicht Rows×Cols×Channels (196608). Split() + pro-Kanal GetArray ist korrekt. + float[] imgData; + var channels = Cv2.Split(resized); + try + { + imgData = new float[256 * 256 * 3]; + for (int c = 0; c < 3; c++) + { + var chanData = new float[256 * 256]; + channels[c].GetArray(out chanData); + for (int i = 0; i < 256 * 256; i++) + imgData[i * 3 + c] = chanData[i]; + } + } + finally + { + foreach (var ch in channels) ch.Dispose(); + } var szene = _inferenceEngine.SzeneKlassifizierenAsync(imgData, 256, 256).GetAwaiter().GetResult(); param.ErkannteSzene = szene; @@ -610,13 +628,8 @@ private Mat SzenenKlassifizierenUndParameterAnpassen(Mat bild, PipelineParams pa if (output.Length == 0) return null; // Postprocessing: NCHW float → HWC → BGR Mat (8-bit) - var resultMat = new Mat(h, w, MatType.CV_32FC3); - var hwcData = new float[h * w * 3]; - for (int c = 0; c < 3; c++) - for (int i = 0; i < h * w; i++) - hwcData[i * 3 + c] = output[c * h * w + i]; - - resultMat.SetArray(hwcData); + // Bug-Fix: SetArray bei Multichannel-Mat schlägt fehl — pro-Kanal SetArray + Merge + var resultMat = ChwZuMat(output, h, w); resultMat.ConvertTo(resultMat, MatType.CV_8UC3, 255.0); // Auf Originalgröße zurückskalieren @@ -690,35 +703,58 @@ private Mat SzenenKlassifizierenUndParameterAnpassen(Mat bild, PipelineParams pa var output = _inferenceEngine.Inferenz(ModellId.RealESRGAN, chwData, [1, 3, th, tw]); if (output.Length == 0) continue; - // Postprocessing → Mat + // Bug-Fix: RealESRGAN macht IMMER 4x Upscaling, unabhängig vom angeforderten Faktor. + // Output-Schema: [1, 3, th*4, tw*4] → 3 * (th*4) * (tw*4) Elemente. + // Bei 2x/3x: 4x Output erzeugen, dann auf Zielgröße herunterskalieren. + const int modelFaktor = 4; + int rawTileW = tw * modelFaktor; + int rawTileH = th * modelFaktor; int upTileW = tw * faktor; int upTileH = th * faktor; - var upTile = new Mat(upTileH, upTileW, MatType.CV_32FC3); - var hwcData = new float[upTileH * upTileW * 3]; - if (output.Length == 3 * upTileH * upTileW) + if (output.Length == 3 * rawTileH * rawTileW) { + // CHW → HWC Konvertierung + // Bug-Fix: SetArray bei Multichannel-Mat (CV_32FC3) schlägt fehl. + // Verwende pro-Kanal SetArray + Merge wie bei InferenceEngine. + var chanMats = new Mat[3]; for (int c = 0; c < 3; c++) - for (int i = 0; i < upTileH * upTileW; i++) - hwcData[i * 3 + c] = output[c * upTileH * upTileW + i]; + { + var chanData = new float[rawTileH * rawTileW]; + for (int i = 0; i < rawTileH * rawTileW; i++) + chanData[i] = output[c * rawTileH * rawTileW + i]; + chanMats[c] = new Mat(rawTileH, rawTileW, MatType.CV_32FC1); + chanMats[c].SetArray(chanData); + } + var rawMat = new Mat(); + Cv2.Merge(chanMats, rawMat); + foreach (var cm in chanMats) cm.Dispose(); + rawMat.ConvertTo(rawMat, MatType.CV_8UC3, 255.0); + + // Auf Zielgröße herunterskalieren (außer bei 4x, da ist es 1:1) + var upTile = new Mat(); + if (faktor == modelFaktor) + { + upTile = rawMat; + } + else + { + Cv2.Resize(rawMat, upTile, new OpenCvSharp.Size(upTileW, upTileH), 0, 0, InterpolationFlags.Lanczos4); + rawMat.Dispose(); + } + + // Tile ins Ergebnis kopieren + var destRoi = new Rect(x * faktor, y * faktor, upTileW, upTileH); + using var destMat = new Mat(result, destRoi); + upTile.CopyTo(destMat); + upTile.Dispose(); } else { - // Modell-Ausgabe stimmt nicht mit erwarteter Größe überein — Fallback - Log.Warning("RealESRGAN: unerwartete Output-Größe {Len} für Tile {W}x{H}x{F}", - output.Length, tw, th, faktor); - upTile.Dispose(); + Log.Warning("RealESRGAN: unerwartete Output-Größe {Len} für Tile {W}x{H} (erwartet {Expected})", + output.Length, tw, th, 3 * rawTileH * rawTileW); continue; } - - upTile.SetArray(hwcData); - upTile.ConvertTo(upTile, MatType.CV_8UC3, 255.0); - - // Tile ins Ergebnis kopieren - var destRoi = new Rect(x * faktor, y * faktor, upTileW, upTileH); - using var destMat = new Mat(result, destRoi); - upTile.CopyTo(destMat); - upTile.Dispose(); } finally { @@ -790,13 +826,8 @@ private static Mat KlassischesUpscaling(Mat bild, int faktor) if (output.Length == 0) return null; // Postprocessing: NCHW → HWC → BGR - var resultMat = new Mat(inputSize, inputSize, MatType.CV_32FC3); - var hwcData = new float[inputSize * inputSize * 3]; - for (int c = 0; c < 3; c++) - for (int i = 0; i < inputSize * inputSize; i++) - hwcData[i * 3 + c] = output[c * inputSize * inputSize + i]; - - resultMat.SetArray(hwcData); + // Bug-Fix: SetArray bei Multichannel-Mat schlägt fehl — pro-Kanal SetArray + Merge + var resultMat = ChwZuMat(output, inputSize, inputSize); resultMat.ConvertTo(resultMat, MatType.CV_8UC3, 255.0); // Auf Originalgröße zurückskalieren @@ -862,13 +893,8 @@ private static Mat KlassischesUpscaling(Mat bild, int faktor) if (output.Length == 0) return null; // Postprocessing - var resultMat = new Mat(inputSize, inputSize, MatType.CV_32FC3); - var hwcData = new float[inputSize * inputSize * 3]; - for (int c = 0; c < 3; c++) - for (int i = 0; i < inputSize * inputSize; i++) - hwcData[i * 3 + c] = output[c * inputSize * inputSize + i]; - - resultMat.SetArray(hwcData); + // Bug-Fix: SetArray bei Multichannel-Mat schlägt fehl — pro-Kanal SetArray + Merge + var resultMat = ChwZuMat(output, inputSize, inputSize); resultMat.ConvertTo(resultMat, MatType.CV_8UC3, 255.0); // Intensitäts-gewichtete Mischung @@ -1330,6 +1356,30 @@ private static float TryParseBlende(string text, float fallback) return fallback; } + + + /// + /// Hilfsmethode: CHW float[] → OpenCvSharp Mat (CV_32FC3). + /// Bug-Fix: SetArray bei Multichannel-Mat (CV_32FC3) schlägt fehl in OpenCvSharp 4.13. + /// Verwendet stattdessen pro-Kanal SetArray + Cv2.Merge. + /// + private static Mat ChwZuMat(float[] chwData, int hoehe, int breite) + { + var chanMats = new Mat[3]; + for (int c = 0; c < 3; c++) + { + var chanData = new float[hoehe * breite]; + for (int i = 0; i < hoehe * breite; i++) + chanData[i] = chwData[c * hoehe * breite + i]; + chanMats[c] = new Mat(hoehe, breite, MatType.CV_32FC1); + chanMats[c].SetArray(chanData); + } + var result = new Mat(); + Cv2.Merge(chanMats, result); + foreach (var cm in chanMats) cm.Dispose(); + return result; + } + public void Dispose() { if (_disposed) return; diff --git a/FlipsiColor.Core/Video/SceneDetector.cs b/FlipsiColor.Core/Video/SceneDetector.cs index 47f6eb6..fa74839 100644 --- a/FlipsiColor.Core/Video/SceneDetector.cs +++ b/FlipsiColor.Core/Video/SceneDetector.cs @@ -74,16 +74,21 @@ public static List SzenenwechselErkennen(string videoPfad, double schwelle private static Mat HistogrammBerechnen(Mat grau) { - var hist = new Mat(); - Cv2.CalcHist( - new[] { grau }, - new[] { 0 }, - null, - hist, - 1, - new[] { 256 }, - new[] { new Rangef(0, 256) }, - false); + // Bug-Fix: OpenCvSharp 4.13 CalcHist schlägt fehl mit + // "ranges[i][k] < ranges[i][k+1]" bei Rangef und float[][]. + // Alternative: Manuelles Histogramm via Cv2.CalcHist mit InputArray + // oder direkte Pixel-Zählung. Verwende LUT-basierten Ansatz für 8-bit Grayscale. + var hist = new Mat(256, 1, MatType.CV_32FC1, Scalar.All(0)); + + // Sichere Alternative: Pixel-Werte direkt zählen + var pixelData = new byte[grau.Rows * grau.Cols]; + grau.GetArray(out pixelData); + + var histData = new float[256]; + foreach (var pixel in pixelData) + histData[pixel]++; + + hist.SetArray(histData); Cv2.Normalize(hist, hist); return hist; } diff --git a/FlipsiColor.sln b/FlipsiColor.sln index 1498985..dd97612 100644 --- a/FlipsiColor.sln +++ b/FlipsiColor.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlipsiColor.TestRunner", "F EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlipsiColor", "FlipsiColor\FlipsiColor.csproj", "{A2F12AE7-E731-459D-87DD-89221EE85420}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlipsiColor.CLI", "FlipsiColor.CLI\FlipsiColor.CLI.csproj", "{87329DDF-B26F-42B6-B238-68A78968896A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -65,6 +67,18 @@ Global {A2F12AE7-E731-459D-87DD-89221EE85420}.Release|Any CPU.Build.0 = Release|x64 {A2F12AE7-E731-459D-87DD-89221EE85420}.Release|x86.ActiveCfg = Release|x64 {A2F12AE7-E731-459D-87DD-89221EE85420}.Release|x86.Build.0 = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|x64.ActiveCfg = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|x64.Build.0 = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|Any CPU.Build.0 = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|x86.ActiveCfg = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Debug|x86.Build.0 = Debug|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|x64.ActiveCfg = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|x64.Build.0 = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|Any CPU.ActiveCfg = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|Any CPU.Build.0 = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|x86.ActiveCfg = Release|x64 + {87329DDF-B26F-42B6-B238-68A78968896A}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/installer/build-script.sh b/installer/build-script.sh index fc6bfe4..ed392ad 100755 --- a/installer/build-script.sh +++ b/installer/build-script.sh @@ -71,6 +71,16 @@ fi echo " ✓ Publish abgeschlossen: $PUBLISH_DIR" +# ── Windows-DLLs entfernen (Bug-Fix: onnxruntime.dll ist PE32+ Windows, nicht Linux) ── +# .NET publish für linux-x64 gibt BEIDE aus: libonnxruntime.so (korrekt) UND onnxruntime.dll (Windows, falsch). +# Die Windows-DLLs verursachen "Protobuf parsing failed" Fehler auf Linux, weil .NET versucht die .dll zu laden. +echo " Entferne Windows-spezifische native DLLs..." +find "$PUBLISH_DIR" -name 'onnxruntime.dll' -delete 2>/dev/null || true +find "$PUBLISH_DIR" -name 'onnxruntime_providers_shared.dll' -delete 2>/dev/null || true +find "$PUBLISH_DIR" -name 'onnxruntime_providers_cuda.dll' -delete 2>/dev/null || true +find "$PUBLISH_DIR" -name 'DirectML.dll' -delete 2>/dev/null || true +echo " ✓ Windows-DLLs entfernt" + # ============================================================================= # Schritt 2: .deb Package bauen (Ubuntu/Debian) # =============================================================================