fix: aspect ratio distortion on image switch (v1.3.1)
Some checks failed
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled

Downsample used ResizeImagePolicy.exact which forced a 2048×2048
square and stretched non-square images. Switch to ResizeImagePolicy.fit
to preserve aspect ratio while still capping both dimensions.
This commit is contained in:
Alhan
2026-08-14 22:17:48 +03:00
parent 72c35a8a26
commit 1002156cd4
5 changed files with 147 additions and 2 deletions

View File

@@ -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<Object> 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)