Files
imajviewer/docs/image-canvas-interactions.md
Alhan 35d3bc3749
Some checks failed
Build Windows / build-windows (push) Has been cancelled
feat: right-click contrast, Ctrl+saturation, Shift+rotate filters
- Add ColorFilter.matrix (row-major) for saturation and contrast
- Saturation: 0=grayscale, 1=normal, 2=oversaturated (luminance-weighted)
- Contrast: standard contrast matrix (pivot at 0.5)
- Keyboard modifier tracking via HardwareKeyboard.addHandler
- Right-click + vertical drag: contrast (0-2)
- Ctrl + right-click + vertical drag: saturation (0-2)
- Shift + right-click + vertical drag: rotate
- Right double-click: reset all values
- Update install.sh to check debug/ bundle before release/
- Update .deb package with new release build
2026-07-27 19:39:54 +03:00

6.6 KiB
Raw Permalink Blame History

Image Canvas Interactions — Analiz ve İmplementasyon Planı

Tarih: 2026-07-27
Kapsam: lib/widgets/image_canvas.dart


1. Mevcut Durum

ImageCanvas widget'ı şu anda:

  • Sol tık + sürükle: Pan (imleç hareketiyle resmi kaydırma) — GestureDetector.onPanUpdate
  • Tekerlek: Zoom (imleç pozisyonu merkezli) — Listener.onPointerSignal → PointerScrollEvent
  • Çift tık: Fill/Fit mod geçişi — GestureDetector.onDoubleTap
  • Transform: Matrix4 ile sadece translate(_tx, _ty) + scale(_sc), rotate yok
  • Renk/efekt filtresi: Yok

Sorun: GestureDetector.onPanUpdate hangi mouse tuşuna basıldığını ayırt etmez. Tüm tuşlarla yapılan sürüklemeyi onPanUpdate olarak yakalar. Sağ tık için ayrı bir işlem yapılamaz.


2. Gereksinimler

# Etkileşim Davranış
R1 Sağ tık + Dikey sürükle Rotate. Yukarı sürükle → saat yönü, aşağı sürükle → saat tersi. Viewport merkezinden dönmeli. Hassasiyet: ~100px = 90°
R2 Ctrl + Sağ tık + Dikey sürükle Contrast ayarı. Yukarı sürükle → artır, aşağı sürükle → azalt. Aralık: 0.0 2.0 (default: 1.0)
R3 Shift + Sağ tık + Dikey sürükle Brightness ayarı. Yukarı sürükle → artır, aşağı sürükle → azalt. Aralık: -1.0 1.0 (default: 0.0)
R4 Sağ çift tık Reset: rotate, contrast, brightness sıfırlanır (0°, 1.0, 0.0)
R5 Sol tık + sürükle Mevcut pan davranışı korunacak

3. Teknik Tasarım

3.1 Gesture Handling: GestureDetectorListener

Mevcut GestureDetector tuş ayrımı yapamadığı için, Listener widget'ı ile pointer event'leri manuel yakalayacağız:

Listener(
  onPointerDown: _onPointerDown,   // hangi tuş + modifier → mod belirle
  onPointerMove: _onPointerMove,   // moda göre pan / contrast / brightness
  onPointerUp:   _onPointerUp,     // tıklama ise rotate uygula
  onPointerSignal: _handleScroll,  // zoom (mevcut)
)

Mod State Machine:

PointerDown:
  if (sol tuş)              → mod = pan
  if (sağ tuş, no modifier) → mod = rotate, başlangıç açısını kaydet
  if (sağ tuş + Ctrl)       → mod = contrast, başlangıç değerini kaydet
  if (sağ tuş + Shift)      → mod = brightness, başlangıç değerini kaydet

PointerMove:
  pan:        _tx += dx, _ty += dy
  rotate:     _angle += dy * sensitivity  (clamp yok, serbest dönüş)
  contrast:   _contrast += dy * sensitivity, clamp(0.0, 2.0)
  brightness: _brightness += dy * sensitivity, clamp(-1.0, 1.0)

PointerUp:
  modu temizle

Çift tık (sağ tuş):
  _angle = 0; _contrast = 1.0; _brightness = 0.0

Tuş tespiti: PointerEvent.buttons bitmask'i:

  • kPrimaryMouseButton (1) = sol
  • kSecondaryMouseButton (2) = sağ
  • kMiddleMouseButton (4) = orta

Modifier tespiti: HardwareKeyboard.instance.isControlPressed / isShiftPressed

3.2 Rotate — Matrix4 Güncellemesi

Rotate, viewport'un tam merkezi etrafında dönmeli. Matrix4 işlem sırası (son yazılan ilk uygulanır):

Matrix4.identity()
  ..translate(vpCenterX, vpCenterY)    // 4. pivot noktasına geri taşı
  ..rotateZ(_angleRad)                 // 3. döndür
  ..translate(-vpCenterX, -vpCenterY)  // 2. pivot'u orijine taşı
  ..translate(_tx, _ty)                // 1. pan offset'i
  ..scale(_sc);                        // 0. zoom

_angleRad = _angle * pi / 180

3.3 Contrast ve Brightness — ColorFilter

Image widget'ını ColorFiltered ile saracağız. Hem contrast hem brightness tek bir matrix'te birleştirilebilir:

ColorFiltered(
  colorFilter: ColorFilter.matrix(_buildColorMatrix()),
  child: imageWidget,
)

Birleşik matrix (contrast × brightness):

Önce brightness, sonra contrast uygulanır:

b = brightness   (-1.0 .. 1.0)
c = contrast     (0.0 .. 2.0)
t = (1.0 - c) / 2.0

Matrix:
  c, 0, 0, 0, t + b,
  0, c, 0, 0, t + b,
  0, 0, c, 0, t + b,
  0, 0, 0, 1, 0,

Default değerler: c = 1.0, b = 0.0 → birim matrix (görüntü değişmez).

3.4 Sensitivity (Hassasiyet)

  • Rotate: _angle += dy * 0.9 (100px sürükleme ≈ 90°)
  • Contrast: _contrast += dy * 0.005 (200px sürükleme = 1.0 birim)
  • Brightness: _brightness += dy * 0.005 (200px sürükleme = 1.0 birim)

Değerler setState içinde clamp'lenir (rotate hariç, serbest dönüş).

3.5 State Değişkenleri (Eklenecekler)

double _angle = 0;         // derece cinsinden, serbest
double _contrast = 1.0;    // 0.0  2.0
double _brightness = 0.0;  // -1.0  1.0

// Gesture tracking
String _interactionMode = 'none'; // 'pan' | 'rotate' | 'contrast' | 'brightness'
Offset _pointerDownPos = Offset.zero;
double _startAngle = 0;
double _startContrast = 1.0;
double _startBrightness = 0.0;

4. Riskler ve Dikkat Edilecek Noktalar

Risk Etki Çözüm
Sağ tık + sürükleme, pan ile çakışabilir Yanlış moda girme rotate_pending modunda eşik kontrolü: 5px'den az hareket → rotate, fazla → pan
Rotate sonrası pan yönü bozulabilir Kullanıcı deneyimi Rotate, pan offset'lerinden (_tx, _ty) önce değil sonra uygulandığı için etkilenmez. Ama fit mode'da letterbox offset'i (m, n) rotate'den etkilenir — test edilecek
ColorFilter performansı Büyük resimlerde takılma Flutter'da ColorFiltered GPU'da çalışır, performans sorunu beklenmiyor
Ctrl/Shift + scroll zoom ile çakışma Zoom yerine contrast değişebilir Scroll event'leri onPointerSignal ile ayrı yakalanıyor, karışmaz
Fill/Fit mod geçişinde rotate sıfırlanmalı mı? UX kararı _toggleFillFit() içinde _angle = 0 yapalım — fill ↔ fit geçişinde rotate sıfırlansın

5. İmplementasyon Planı

Adım 1: Listener tabanlı gesture sistemine geçiş

  • GestureDetector yerine Listener kullan
  • onPointerDown, onPointerMove, onPointerUp handler'larını yaz
  • Sol tık pan'i koru, sağ tık modlarını ekle

Adım 2: Rotate

  • _angle state değişkeni ekle
  • Matrix4'e rotate transform'u ekle
  • _resetView() ve _toggleFillFit() içinde _angle = 0

Adım 3: Contrast ve Brightness

  • _contrast, _brightness state değişkenleri ekle
  • ColorFiltered widget'ı ile image'ı sar
  • _buildColorMatrix() metodunu yaz

Adım 4: Test ve clamp

  • Tüm modlarda clamp/değer aralığı kontrolü
  • Rotate + zoom + pan birlikte çalışıyor mu test et

6. Etkilenen Dosyalar

Dosya Değişiklik
lib/widgets/image_canvas.dart Ana değişikliklerin tümü burada
(başka dosya yok) Diğer widget'lar etkilenmez