136 lines
5.0 KiB
Dart
136 lines
5.0 KiB
Dart
// 보기 화면 그림 기억 모듈
|
|
//
|
|
// 약속
|
|
// - 입력: 그림 원본 ([ReaderSource]) · 장 번호 · 읽을 너비 (기기 픽셀)
|
|
// - 출력: 장마다 한 번만 읽은 그림 ([ReaderImage] 의 Future) - 통째 · 반쪽 (두 배 너비) · 확대해 다시 그린 것 · 작은 그림.
|
|
// [imageSize] 는 그림의 원래 크기 (모르면 null)
|
|
// - 오류: 읽기 실패는 Future 의 오류로 그대로 돌려준다 (기억에 남아 같은 실패를 다시 보인다 - 지우려면 [forget]).
|
|
// 버릴 때 dispose 실패는 삼킨다
|
|
// 이 모듈이 그림을 버리는 (dispose) 유일한 곳이다. 다른 보기 화면도 다시 쓸 수 있다.
|
|
|
|
import 'dart:async';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
import '../../core/reader_sources.dart';
|
|
|
|
class ReaderImageCache {
|
|
ReaderImageCache(this.source);
|
|
|
|
/// 읽을 원본 (폴더 그림 목록처럼 장이 바뀌어도 같은 객체)
|
|
final ReaderSource source;
|
|
|
|
final _full = <int, Future<ReaderImage>>{};
|
|
|
|
/// 반쪽으로 보는 장 (두 배 너비)
|
|
final _half = <int, Future<ReaderImage>>{};
|
|
|
|
/// 확대한 배율에 맞춰 더 크게 다시 그린 쪽 (쪽 번호 - 흐릿하지 않게)
|
|
final _sharp = <int, Future<ReaderImage>>{};
|
|
final _sharpWidth = <int, int>{};
|
|
|
|
/// 아래 갤러리 줄의 작은 그림 (장 번호)
|
|
final _thumbs = <int, Future<ReaderImage>>{};
|
|
|
|
static void _drop(Future<ReaderImage> f) => unawaited(f.then((x) => x.dispose(), onError: (_) {}));
|
|
|
|
/// [page] 장 통째 ([width] 너비). 처음 읽을 때만 [onLoaded] 를 부른다 (크기 알아 두기 등)
|
|
Future<ReaderImage> full(int page, int width, {void Function(ReaderImage r)? onLoaded}) => _full.putIfAbsent(page, () {
|
|
final f = source.load(page, maxWidth: width);
|
|
f.then((r) => onLoaded?.call(r), onError: (_) {});
|
|
return f;
|
|
});
|
|
|
|
/// [page] 장을 반쪽으로 볼 때 (화면 너비 [width] 의 두 배, 8192 까지)
|
|
Future<ReaderImage> half(int page, int width) =>
|
|
_half.putIfAbsent(page, () => source.load(page, maxWidth: math.min(width * 2, 8192)));
|
|
|
|
/// [slot] 쪽을 확대해 다시 그린 것 (없으면 null)
|
|
Future<ReaderImage>? sharp(int slot) => _sharp[slot];
|
|
|
|
/// 작은 그림 (160 너비)
|
|
Future<ReaderImage> thumb(int page) => _thumbs.putIfAbsent(page, () => source.load(page, maxWidth: 160));
|
|
|
|
/// 확대 배율 [scale] 에 맞는 너비. 더 크게 다시 그릴 만큼이 아니면 null ([base] 쪽 기본 너비)
|
|
int? sharpWidthFor(int slot, double scale, int base) {
|
|
if (scale < 1.3) return null;
|
|
final want = (base * scale).round().clamp(base, 8192);
|
|
if (want <= (_sharpWidth[slot] ?? base) * 1.2) return null;
|
|
return want;
|
|
}
|
|
|
|
/// [slot] 쪽 ([page] 장) 을 [width] 너비로 다시 그린다. 앞의 것은 새것을 건 뒤에 버린다
|
|
void sharpen(int slot, int page, int width) {
|
|
_sharpWidth[slot] = width;
|
|
final old = _sharp[slot];
|
|
_sharp[slot] = source.load(page, maxWidth: width);
|
|
if (old != null) _drop(old);
|
|
}
|
|
|
|
/// 확대해 다시 그린 것을 모두 버린다
|
|
void clearSharp() {
|
|
for (final f in _sharp.values) {
|
|
_drop(f);
|
|
}
|
|
_sharp.clear();
|
|
_sharpWidth.clear();
|
|
}
|
|
|
|
/// 통째 · 반쪽을 모두 버린다 (장이 바뀌었을 때)
|
|
void clearPages() {
|
|
for (final f in [..._full.values, ..._half.values]) {
|
|
_drop(f);
|
|
}
|
|
_full.clear();
|
|
_half.clear();
|
|
}
|
|
|
|
/// [다시 시도]: [page] 장과 [slot] 쪽의 기억을 잊는다 (버리지 않음 - 아직 화면에 있을 수 있다)
|
|
void forget(int page, int slot) {
|
|
_full.remove(page);
|
|
_half.remove(page);
|
|
_sharp.remove(slot);
|
|
_sharpWidth.remove(slot);
|
|
}
|
|
|
|
/// [page] 가까이 (앞뒤 3장) 만 남기고 버린다 (메모리)
|
|
void trim(int page) {
|
|
for (final m in [_full, _half]) {
|
|
for (final k in m.keys.toList()) {
|
|
if ((k - page).abs() > 3) _drop(m.remove(k)!);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 모두 버린다 (작은 그림 → 통째 · 반쪽 → 확대한 것 차례)
|
|
void dispose() {
|
|
for (final f in _thumbs.values) {
|
|
_drop(f);
|
|
}
|
|
clearPages();
|
|
clearSharp();
|
|
}
|
|
|
|
/// 그림의 원래 크기 (그려 둔 그림이면 바로, 아니면 한 번 풀어 보고). 못 읽으면 null
|
|
static Future<Size?> imageSize(ReaderImage r) {
|
|
final img = r.image;
|
|
if (img != null) return Future.value(Size(img.width.toDouble(), img.height.toDouble()));
|
|
final provider = r.provider;
|
|
if (provider == null) return Future.value(null);
|
|
final done = Completer<Size?>();
|
|
final stream = provider.resolve(ImageConfiguration.empty);
|
|
late final ImageStreamListener l;
|
|
l = ImageStreamListener((info, _) {
|
|
if (!done.isCompleted) done.complete(Size(info.image.width.toDouble(), info.image.height.toDouble()));
|
|
info.dispose();
|
|
stream.removeListener(l);
|
|
}, onError: (_, _) {
|
|
if (!done.isCompleted) done.complete(null);
|
|
stream.removeListener(l);
|
|
});
|
|
stream.addListener(l);
|
|
return done.future;
|
|
}
|
|
}
|