- Save visible area at original resolution as JPEG q90 (title bar button or Ctrl+S) - Output name: original name + _HHmmss suffix; no overwrite, warning shown - Active color effects baked in; rotation saves bounding box - New: crop_save_service, l10n strings TR/EN, docs/plan-crop-save.md
5.6 KiB
5.6 KiB
Plan — Crop Save (görünen alanı kaydet)
Tarih: 2026-10-11 · Versiyon: 1.4.0 · Durum: UYGULANDI (2026-10-11) — manual test + commit/push bekleniyor
Not — Uygulama Sırası
imagev3 kullanılamadı:msix 3.18.0image ^4.0.0gerektiriyor → v4 async API kullanıldı (decodeImageUint8List ile,copyCrop(x:,y:,width:,height:),JpegEncoder(quality:90),bakeOrientation, pixel kanallarır/g/b/a).- v4'te renk matrisi filtresi yok → matris
crop_save_service.dart:_applyMatrixile elle uygulandı (aynı 0-255 alanı). - Boyut okuma ayrı descriptor yerine tam decode (
load()) üzerinden yapılıyor.
Gereksinim + Kararlar
- Pencerenin görünen kısmı (image canvas viewport'u) → orijinal çözünürlükte crop
- JPEG quality 90
- Dosya adı: orijinal ismin sonuna saat only eki →
resim.jpg→resim_143025.jpg - Çakışma: dosya zaten varsa yazı YAPILMAZ, uyarı gösterilir (mevcut dosya korunur)
- Renk efektleri: kayda GÖMÜLÜR (contrast/saturation/brightness, ekrandaki gibi)
- Rotasyon aktifken: yapılsın — görünür bölgeyi saran dikdörtgen (bounding box) kaydedilir
- Title bar: Save butonu minimize'in SOLUNDA (sıralama: save · minimize · x)
- Klavye: Ctrl+S de tetikler
Mimari Analiz
1) Görünen alan → kaynak piksel eşlemesi
image_canvas.dart render zinciri: Transform(translate(_tx,_ty) scale(_sc)) içinde
opsiyonel Transform(rotate(_angle), vpCenter) → Image(fit: cover|contain).
Canvas lokal koordinatındaki bir nokta p için geri dönüş:
q = (p - (tx,ty)) / sc_angle != 0iser=q'yi vpCenter ekseninde-anglekadar döndür (yoksar = q)- cover (
_isFilled):k = max(vpW/iw, vpH/ih); contain:k = min(vpW/iw, vpH/ih); her ikisinde deox=(vpW-iw*k)/2,oy=(vpH-ih*k)/2→src = ((r.x-ox)/k, (r.y-oy)/k) - Viewport'un 4 köşesi →
srcuzayda → min/max bounding box →[0,iw]×[0,ih]'a clamp → int - Rotasyonlu durumda bounding box, ekranda görünür bölgeyi kapsayan dikdörtgendir (çıkış aspect/boyutu pencereden farklı olabilir — onaylı davranış)
iw,ih= gerçek dosya boyutu (descriptor ile)._imageSizedownsample'lı (2048) olabilir → eşlemede kullanılmaz.- Görünen alan = tamamı canvas viewport (title bar 24px bindirilmiş overlay, görsel arkasında görünüyor → kesime dahil).
2) Encode zinciri (yeni bağımlılık: image ^4.x, saf Dart)
File → img.decodeImage(bytes, keepOrientation: true)
→ img.cropRect(rect)
→ aktif renk matrisi var ise img.ColorMatrixFilter(mat4x5).applyTo(crop)
→ img.encodeJpeg(crop, quality: 90)
→ hedef yol var ise → uyarı (yazılmaz); yoksa File.writeAsBytes
- Renk matrisi:
image_canvas.dart'daki mevcut_buildCombinedFilter()4x5 matrisi (contrast×brightness×saturation) aynı zamandaColorFilter.matrixolarak kullanılıyor. State'eList<double>? activeColorMatrixgetter'ı eklenir (filtre identity ise null). Flutter veimagepaketi aynı 4x5 row-major / 0-255 alanı kullanır → matris aynen taşınır, yeniden türetilmez. - Tuzak — EXIF yönü: Flutter engine JPEG'lerde EXIF orientation uygular;
keepOrientation: trueverilmezse 90° döndürülmüş fotoğraflarda crop yanlış yerden kesilir. Test listesinde zorunlu. - WBMP:
imageWBMP çözer (uygulama destekliyor); decode hatası → "desteklenmiyor" mesajı, çökme yok.
3) Bileşenler
| Dosya | Değişiklik |
|---|---|
pubspec.yaml |
image: ^4.x; versiyon 1.4.0+8 |
lib/services/crop_save_service.dart |
YENİ — imageSize(path) (descriptor), saveCrop({srcPath, rect, matrix, outPath}), ad üretimi (_HHmmss), çakışma kontrolü (exists → uyarı), hata mesajları |
lib/widgets/image_canvas.dart |
State → ImageCanvasState (public); Rect? cropSourceRect(int w, int h); List<double>? activeColorMatrix getter |
lib/screens/viewer_screen.dart |
GlobalKey<ImageCanvasState>, _cropSave(), sonuç geri bildirimi (kısa overlay/Snackbar: yol veya hata) |
lib/widgets/custom_title_bar.dart |
onSaveCrop param; save ikonu minimize'in solunda; mevcut _BarButton stili (hover kırmızı) |
lib/shortcuts/app_shortcuts.dart |
Ctrl+S → crop save (callback param ile) |
lib/l10n/app_{en,tr}.arb |
saveCrop (tooltip), cropSaved, cropFailed, cropFileExists, cropUnsupported + shortcutsList güncelleme; flutter gen-l10n |
CHANGELOG.md |
[1.4.0] |
4) Etkileşim
- Buton yalnızca görsel yüklüyken aktif (yoksa null → pas geçilir).
- Başarıda: kaydedilen tam yol gösterilir; hata/çakışmada: hata metni.
- Tam decode kayıt anında, tek seferlik (downsample swap ile ilişkisi yok).
Sınır Durumları
- Görsel yok → buton pasif, Ctrl+S no-op
- Crop rect boş/degenerate → hata mesajı
- Yazılamayan klasör → hata mesajı, çökme yok
- Çakışma: dosya var → yazılmaz, uyarı (karar)
- WBMP/decode hatası →
cropUnsupported - Rotasyonlu bounding box (karar: kaydedilir)
- Büyük görsellerde tam decode RAM'i — tek seferlik, kabul edilebilir
Doğrulama
flutter analyze(otomatik izinli)flutter build linux --debug+./install.sh(otomatik izinli)- Kullanıcı manuel test: fit/cover, zoom, pan, rotasyon + kaydet, renk efektleri + kaydet, EXIF 90° görsel, WBMP, çakışan isim (uyarı + dosya korunur), salt okunur dizin
- Commit + push: yalnızca senin komutunla
Kapsam Dışı
- Kullanıcı alanı seçerek crop (her zaman "görünen bölge")
- PNG/özel format seçimi (sabit JPEG q90)
- Gitea CI release / tag (senin komutunla)