fix: aspect ratio distortion on image switch (v1.3.1)
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:
@@ -4,6 +4,11 @@ All notable changes to imajViewer are documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.3.0] - 2026-08-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
BIN
dist/imajviewer_1.3.1_amd64.deb
vendored
Normal file
BIN
dist/imajviewer_1.3.1_amd64.deb
vendored
Normal file
Binary file not shown.
135
docs/rapor-aspect-ratio-ram.md
Normal file
135
docs/rapor-aspect-ratio-ram.md
Normal 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)
|
||||||
@@ -426,7 +426,12 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
_cachedCacheWidth = effectiveWidth;
|
_cachedCacheWidth = effectiveWidth;
|
||||||
_cachedFileImage = FileImage(File(widget.filePath));
|
_cachedFileImage = FileImage(File(widget.filePath));
|
||||||
_cachedProvider = effectiveWidth != null
|
_cachedProvider = effectiveWidth != null
|
||||||
? ResizeImage.resizeIfNeeded(effectiveWidth, effectiveWidth, _cachedFileImage!)
|
? ResizeImage(
|
||||||
|
_cachedFileImage!,
|
||||||
|
width: effectiveWidth,
|
||||||
|
height: effectiveWidth,
|
||||||
|
policy: ResizeImagePolicy.fit,
|
||||||
|
)
|
||||||
: _cachedFileImage;
|
: _cachedFileImage;
|
||||||
_readImageSize(_cachedProvider!);
|
_readImageSize(_cachedProvider!);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: imajviewer
|
name: imajviewer
|
||||||
description: "Ultra-lightweight image viewer for Linux and Windows."
|
description: "Ultra-lightweight image viewer for Linux and Windows."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.3.0+5
|
version: 1.3.1+6
|
||||||
|
|
||||||
msix_config:
|
msix_config:
|
||||||
display_name: ImajViewer
|
display_name: ImajViewer
|
||||||
|
|||||||
Reference in New Issue
Block a user