feat: downsampling + full-res swap + RAM leak fix
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

- Büyük resimler ilk yüklemede max 2048px'e downsampling (RAM ~1.6MB)
- Zoom 1.5x stretch'i geçince full-res'e swap (orijinal çözünürlük)
- Resim geçişinde eski provider evict + ImageCache.evict() (RAM leak)
- Tiled render kodu tamamen kaldırıldı
This commit is contained in:
Alhan
2026-08-14 21:22:26 +03:00
parent 64dc1ec53d
commit 45cf56a1e6
5 changed files with 219 additions and 9 deletions

View File

@@ -0,0 +1,58 @@
# Durum Raporu: Downsampling + Full-Res Swap + RAM Leak Fix
Tarih: 2026-08-14
Branch: `main`
## Yapılan Değişiklikler
Tek dosya: `lib/widgets/image_canvas.dart`
### 1. Downsampling (ilk yüklemede RAM tasarrufu)
- `_downsampleTarget = 2048` sabiti eklendi.
- `_buildImage()` artık ilk yüklemede `ResizeImage.resizeIfNeeded(2048, null, ...)`
kullanıyor. Büyük resimler (ör. 12000px) 2048px'e küçültülerek decode ediliyor.
- Önceki davranış: resim her zaman tam çözünürlükte decode ediliyordu
(`cacheWidth = longest * _sc * 2`), bu da dev resimlerde RAM'i dolduruyordu.
### 2. Full-Res Swap (zoom'da kalite)
- `_checkFullResSwap()`: downsampled resmin viewport'a oranı (`stretch`) 1.5x'i
geçince `_usingFullRes = true` yapılıyor ve doğrudan `FileImage` (tam çözünürlük)
kullanılıyor.
- `_handleScroll()` içinde `_sc` güncellendikten sonra çağrılıyor.
- Sonuç: küçük görünümde 2048px (RAM ~1.6MB), zoom ile yakınlaşınca orijinal.
### 3. RAM Leak Fix (resim geçişlerinde sızıntı)
- `didUpdateWidget()` içinde `old.filePath != widget.filePath` kontrolü:
- `_cachedFileImage?.evict()`, `_cachedProvider?.evict()`
- `PaintingBinding.instance.imageCache.evict(FileImage(File(old.filePath)))`
- Tüm cache alanları `null`'a çekiliyor.
- Önceki davranış: her yüklenen resim Flutter'ın global `ImageCache`'inde
birikiyor, RAM sürekli artıyordu.
### 4. Görüntü boyutu takibi
- `_imageSize`, `loadingBuilder` + `ImageStreamListener` ile decode edilen
gerçek piksel boyutundan dolduruluyor. `_checkFullResSwap()` bu değeri
stretch hesabında kullanıyor.
## Temizlik
- Tiled render kodunun tamamı kaldırıldı (Flutter codec API region decode
desteklemiyor).
- `dart:ui` ve `dart:typed_data` import'ları gereksiz kalınca silindi.
- Kullanılmayan `_uiImage`, `_fullResImage`, `_fullResLoading` alanları kaldırıldı.
## Doğrulama
- `flutter analyze --no-pub`: 0 error.
- `flutter build linux --debug`: başarılı.
- Kullanıcı testi: onaylandı ("herşey tamam gözüküyor").
## Açık Konular / Sonraki Adımlar
- `_downsampleTarget` ve swap threshold (1.5x) değerleri tune edilebilir.
- LOD (progressive loading) seviyeleri — şimdilik tek kademe (2048 → full) yeterli.
- Android/iOS hedeflerinde `ResizeImage` davranışı ayrıca test edilmeli.

View File

@@ -0,0 +1,101 @@
# Yönerge: Downsampling + Full-Res Swap + RAM Leak Fix
## Amaç
Yüksek çözünürlüklü görsellerde RAM tüketimini azaltmak ve resim geçişlerinde bellek sızıntısını gidermek.
## Dosya
`lib/widgets/image_canvas.dart`
## 1. State değişkenleri ekle
`_ImageCanvasState` sınıfına aşağıdaki değişkenleri ekle:
```dart
ui.Image? _uiImage;
ui.Image? _fullResImage;
bool _fullResLoading = false;
bool _usingFullRes = false;
static const int _downsampleTarget = 2048;
```
## 2. `_loadUiImage()` — Downsample decode
Mevcut `_loadUiImage()` metodunu aşağıdaki gibi değiştir:
- `ui.instantiateImageCodec()` çağrısına `targetWidth: _downsampleTarget, targetHeight: _downsampleTarget` parametrelerini ekle
- Decode edilen resmin boyutunu `_uiImage` ve `_tiledImageSize` olarak kaydet
- Eğer decode boyutu target'tan büyükse → `_loadFullResImage(bytes)` başlat (arka plan)
- Eğer decode boyutu target'tan küçükse → resim zaten küçük, `_usingFullRes = true` yap
## 3. `_loadFullResImage()` — Arka plan full-res yükleme
Yeni metot:
```dart
Future<void> _loadFullResImage(Uint8List bytes) async {
if (_fullResLoading) return;
_fullResLoading = true;
try {
final codec = await ui.instantiateImageCodec(bytes);
final frame = await codec.getNextFrame();
if (mounted && !_usingFullRes) {
setState(() => _fullResImage = frame.image);
} else {
frame.image.dispose();
}
} catch (e) { /* log */ }
finally { _fullResLoading = false; }
}
```
## 4. `_checkFullResSwap()` — Zoom threshold'unda swap
Yeni metot. `_handleScroll()` içinde `_sc` güncellendikten sonra çağrılır.
- Downsample resmin ne kadar stretch edildiğini hesapla (`stretchX`, `stretchY`)
- `maxStretch >= 1.5` ise:
- `_uiImage?.dispose()`
- `_uiImage = _fullResImage`
- `_fullResImage = null`
- `_tiledImageSize` güncelle
- `_usingFullRes = true`
## 5. `_handleScroll()` — Swap kontrolü ekle
`_sc = newSc;` satırından sonra `_checkFullResSwap()` çağrısı ekle.
## 6. RAM Leak Fix — `didUpdateWidget`
Resim değiştiğinde eski tüm referansları temizle:
```dart
_uiImage?.dispose();
_fullResImage?.dispose();
_cachedFileImage?.evict();
_cachedProvider?.evict();
_cachedFileImage = null;
_cachedProvider = null;
_cachedPath = null;
_cachedCacheWidth = null;
PaintingBinding.instance.imageCache.evict(FileImage(File(old.filePath)));
```
## 7. `_resetView()` — Tüm state sıfırla
`_fullResImage`, `_fullResLoading`, `_usingFullRes` sıfırlansın.
## 8. `dispose()` — Her iki resmi de dispose et
```dart
_uiImage?.dispose();
_fullResImage?.dispose();
```
## 9. Import
`dart:typed_data` import'u ekle (`Uint8List` için).
## Sonuç
- İlk yükleme: max 2048px downsample (RAM ~1.6MB)
- Zoom ile yakınlaşınca: full-res swap (~512MB+ büyük resimlerde)
- Resim geçişinde: eski tüm cache dispose + evict