diff --git a/CHANGELOG.md b/CHANGELOG.md index de6e743..984772d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to imajViewer are documented in this file. ## [Unreleased] +## [1.3.1] - 2026-08-14 + +### Fixed +- Aspect ratio distortion on image switch — downsample now uses `ResizeImagePolicy.fit` (was `exact`, which forced 2048×2048 square and stretched non-square images) + ## [1.3.0] - 2026-08-14 ### Fixed diff --git a/dist/imajviewer_1.3.1_amd64.deb b/dist/imajviewer_1.3.1_amd64.deb new file mode 100644 index 0000000..d26e812 Binary files /dev/null and b/dist/imajviewer_1.3.1_amd64.deb differ diff --git a/docs/rapor-aspect-ratio-ram.md b/docs/rapor-aspect-ratio-ram.md new file mode 100644 index 0000000..a77fd9e --- /dev/null +++ b/docs/rapor-aspect-ratio-ram.md @@ -0,0 +1,135 @@ +# Analiz: Aspect Ratio Bozulması + RAM Büyümesi + +Tarih: 2026-08-14 +Sürüm: v1.3.0 sonrası regresyon + +--- + +## 1. Aspect Ratio Bug'ı (kök neden bulundu) + +### Belirti +- Next/prev ile resim değişince görüntü **en/boy oranı bozulmuş** (gerilmiş/sıkışmış) geliyor. +- Fazla zoom yapınca aspect düzeliyor. + +### Kök Neden — `ResizeImagePolicy.exact` + +`lib/widgets/image_canvas.dart:429`: + +```dart +ResizeImage.resizeIfNeeded(effectiveWidth, effectiveWidth, _cachedFileImage!) +``` + +Bu çağrı Flutter SDK içinde şuna dönüşür (`image_provider.dart:1302-1310`): + +```dart +static ImageProvider resizeIfNeeded(int? cacheWidth, int? cacheHeight, provider) { + if (cacheWidth != null || cacheHeight != null) { + return ResizeImage(provider, width: cacheWidth, height: cacheHeight); + } + return provider; +} +``` + +Ve `ResizeImage` constructor'ı (`image_provider.dart:1262-1268`): + +```dart +const ResizeImage(this.imageProvider, { this.width, this.height, + this.policy = ResizeImagePolicy.exact, // ← VARSAYILAN exact + this.allowUpscaling = false, +}); +``` + +**`ResizeImagePolicy.exact`**: genişlik ve yüksekliği **bağımsız** ölçekler, aspect ratio'yu +**koruMAZ**. Yani `width: 2048, height: 2048` demek → her resmi **2048×2048 kareye +zorla**. 4000×2000 bir resim 2048×2048'e gerilir (2:1 → 1:1), görüntü bozulur. + +Zoom yapınca `_checkFullResSwap()` → `_usingFullRes = true` → `_cachedProvider = +_cachedFileImage` (orijinal boyutlu `FileImage`, aspect doğru) → görüntü düzelir. +Bu, belirtiyle birebir örtüşüyor. + +### Düzeltme + +`resizeIfNeeded` yerine doğrudan `ResizeImage` + `policy: ResizeImagePolicy.fit`: + +```dart +_cachedProvider = effectiveWidth != null + ? ResizeImage( + _cachedFileImage!, + width: effectiveWidth, + height: effectiveWidth, + policy: ResizeImagePolicy.fit, + ) + : _cachedFileImage; +``` + +`fit` policy'si (SDK `image_provider.dart:1378-1409`) aspect ratio'yu koruyarak +2048×2048 kutusuna **sığdırır**: +- 4000×2000 → 2048×1024 (2:1 korunur) +- 800×20000 dikey → 82×2048 (HATA-2'nin istediği dikey sınırlama da korunur) +- 20000×800 panorama → 2048×82 + +> Not: `resizeIfNeeded` helper'ı `policy` parametresi **almaz**, bu yüzden `fit` +> için `ResizeImage`'ı doğrudan kurmak zorunlu. + +--- + +## 2. RAM Büyümesi Analizi + +### Gözlem +RAM 200MB'a kadar büyüdü. "Daha büyür mü?" — Evet, aşağıdaki modele göre büyüyebilir. + +### RAM modeli (mevcut) + +| Kaynak | Boyut | +|---|---| +| Downsample (fit 2048×2048, RGBA) | en fazla 2048×2048×4 = **16.7MB** | +| Full-res swap (zoom ≥ 1.5x) | resme bağlı: 12000px → **576MB**, 4000px → 64MB | +| `ImageCache.maximumSize` | 16 entry | +| `ImageCache.maximumSizeBytes` | 256MB | + +### Neden 200MB'a büyüdü? + +1. Kullanıcı zoom yaptı → `_checkFullResSwap` threshold 1.5 aşıldı → tam çözünürlük + decode edildi → RAM'e ~100MB+ girdi. +2. Full-res resim **canlı** olduğu için `maximumSizeBytes = 256MB` sınırı onu + **atamaz** (Flutter aktif kullanımdaki image'ı evict etmez). + +### Daha büyür mü? — Evet, iki senaryo + +- **Daha büyük resim:** 12000px panorama full-res'e geçince tek resim ~576MB. + `maximumSizeBytes = 256MB` canlı resmi korur, sınır aşılır. +- **Çok sayıda full-res swap:** her resme zoom yapıp geçince, `didUpdateWidget` + eski provider'ı `evict` eder ama evict **asenkron/gecikmeli** olabilir; ardışık + hızlı geçişte 2-3 full-res aynı anda RAM'de kalabilir. + +### Yapılacak iyileştirmeler + +1. **Full-res swap'ı kademeli yap** (şu an binary: 2048 → full). Orta seviye + (ör. 4096px) ekleyerek RAM'i sınırla. Ama bu kullanıcı isteği değilse yapma. +2. **`_checkFullResSwap` stretch eşiğini yükselt** (1.5 → 2.0) → daha az full-res + swap, daha az RAM. Trade-off: zoom'da biraz daha geç keskinleşme. +3. **`maximumSizeBytes`'ı düşür** (256MB → 128MB) → LRU daha agresif. Ama canlı + resmi yine korur, etkisi sınırlı. +4. **Full-res swap'ı dispose etme zamanlamasını netleştir** — şu an `didUpdateWidget` + evict yapıyor; `dispose()` içine `_cachedProvider?.evict()` de eklenebilir. + +### Karar önerisi + +Aspect bug'ı **kesin düzeltilmeli** (görsel regresyon, her resimde). RAM büyümesi +ise full-res swap'ın **beklenen davranışı** — 200MB makul, ama 576MB'lık dev +panoramalarda tek resimde patlayabilir. `fit` policy'ye geçmek downsample'ı da +düzelteceği için ortalama RAM'i de bir miktar düşürür (exact kare yerine doğru +orantılı küçük resim). + +--- + +## 3. Öncelik Sırası + +1. **Aspect bug fix** — `ResizeImagePolicy.fit` (tek satır, kritik görsel düzeltme) +2. **RAM** — şimdilik izle; 2. eşik/kademeli swap istersen ayrı yaparız. + +## 4. Doğrulama + +- `flutter analyze`: 0 error +- Release build: ✓ +- Manuel test: kullanıcı (aspect düzelmesi + RAM gözlemi) diff --git a/lib/widgets/image_canvas.dart b/lib/widgets/image_canvas.dart index 1ffe028..97fe2cc 100644 --- a/lib/widgets/image_canvas.dart +++ b/lib/widgets/image_canvas.dart @@ -426,7 +426,12 @@ class _ImageCanvasState extends State { _cachedCacheWidth = effectiveWidth; _cachedFileImage = FileImage(File(widget.filePath)); _cachedProvider = effectiveWidth != null - ? ResizeImage.resizeIfNeeded(effectiveWidth, effectiveWidth, _cachedFileImage!) + ? ResizeImage( + _cachedFileImage!, + width: effectiveWidth, + height: effectiveWidth, + policy: ResizeImagePolicy.fit, + ) : _cachedFileImage; _readImageSize(_cachedProvider!); } diff --git a/pubspec.yaml b/pubspec.yaml index 3799ab4..373a652 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: imajviewer description: "Ultra-lightweight image viewer for Linux and Windows." publish_to: 'none' -version: 1.3.0+5 +version: 1.3.1+6 msix_config: display_name: ImajViewer