This commit is contained in:
2024-12-11 13:41:34 +08:00
parent 3cf9228e2a
commit 0c9f7126a2
6 changed files with 101 additions and 65 deletions

View File

@@ -1,5 +1,6 @@
import 'dart:async' show Future, StreamController, scheduleMicrotask;
import 'dart:convert';
import 'dart:math';
import 'dart:ui' as ui show Codec;
import 'dart:ui';
import 'package:flutter/foundation.dart';
@@ -10,6 +11,39 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
extends ImageProvider<T> {
const BaseImageProvider();
static double? _effectiveScreenWidth;
static const double _normalComicImageRatio = 0.72;
static const double _minComicImageWidth = 1920 * _normalComicImageRatio;
static TargetImageSize _getTargetSize(width, height) {
if (_effectiveScreenWidth == null) {
final screens = PlatformDispatcher.instance.displays;
for (var screen in screens) {
if (screen.size.width > screen.size.height) {
_effectiveScreenWidth = max(
_effectiveScreenWidth ?? 0,
screen.size.height * _normalComicImageRatio,
);
} else {
_effectiveScreenWidth = max(
_effectiveScreenWidth ?? 0,
screen.size.width
);
}
}
if (_effectiveScreenWidth! < _minComicImageWidth) {
_effectiveScreenWidth = _minComicImageWidth;
}
}
if (width > _effectiveScreenWidth!) {
height = (height * _effectiveScreenWidth! / width).round();
width = _effectiveScreenWidth!.round();
}
return TargetImageSize(width: width, height: height);
}
@override
ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
@@ -45,19 +79,12 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
while (data == null && !stop) {
try {
if(_cache.containsKey(key.key)){
data = _cache[key.key];
} else {
data = await load(chunkEvents);
_checkCacheSize();
_cache[key.key] = data;
_cacheSize += data.length;
}
data = await load(chunkEvents);
} catch (e) {
if(e.toString().contains("Invalid Status Code: 404")) {
if (e.toString().contains("Invalid Status Code: 404")) {
rethrow;
}
if(e.toString().contains("Invalid Status Code: 403")) {
if (e.toString().contains("Invalid Status Code: 403")) {
rethrow;
}
if (e.toString().contains("handshake")) {
@@ -73,23 +100,24 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
}
}
if(stop) {
if (stop) {
throw Exception("Image loading is stopped");
}
if(data!.isEmpty) {
if (data!.isEmpty) {
throw Exception("Empty image data");
}
try {
final buffer = await ImmutableBuffer.fromUint8List(data);
return await decode(buffer);
return await decode(buffer, getTargetSize: _getTargetSize);
} catch (e) {
await CacheManager().delete(this.key);
if (data.length < 2 * 1024) {
// data is too short, it's likely that the data is text, not image
try {
var text = const Utf8Codec(allowMalformed: false).decoder.convert(data);
var text =
const Utf8Codec(allowMalformed: false).decoder.convert(data);
throw Exception("Expected image data, but got text: $text");
} catch (e) {
// ignore
@@ -107,30 +135,6 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
}
}
static final _cache = <String, Uint8List>{};
static var _cacheSize = 0;
static var _cacheSizeLimit = 50 * 1024 * 1024;
static void _checkCacheSize(){
while (_cacheSize > _cacheSizeLimit){
var firstKey = _cache.keys.first;
_cacheSize -= _cache[firstKey]!.length;
_cache.remove(firstKey);
}
}
static void clearCache(){
_cache.clear();
_cacheSize = 0;
}
static void setCacheSizeLimit(int size){
_cacheSizeLimit = size;
_checkCacheSize();
}
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents);
String get key;

View File

@@ -2,6 +2,7 @@ import 'dart:async' show Future, StreamController;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:venera/network/images.dart';
import 'package:venera/utils/io.dart';
import 'base_image_provider.dart';
import 'reader_image.dart' as image_provider;
@@ -20,6 +21,14 @@ class ReaderImageProvider
@override
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
if (imageKey.startsWith('file://')) {
var file = File(imageKey);
if (await file.exists()) {
return file.readAsBytes();
}
throw "Error: File not found.";
}
await for (var event
in ImageDownloader.loadComicImage(imageKey, sourceKey, cid, eid)) {
chunkEvents.add(ImageChunkEvent(

View File

@@ -162,19 +162,22 @@ class _GalleryModeState extends State<_GalleryMode>
} else {
int pageIndex = index - 1;
int startIndex = pageIndex * reader.imagesPerPage;
int endIndex = math.min(startIndex + reader.imagesPerPage, reader.images!.length);
List<String> pageImages = reader.images!.sublist(startIndex, endIndex);
int endIndex = math.min(
startIndex + reader.imagesPerPage, reader.images!.length);
List<String> pageImages =
reader.images!.sublist(startIndex, endIndex);
cached[index] = true;
cache(index);
photoViewControllers[index] = PhotoViewController();
if(reader.imagesPerPage == 1) {
if (reader.imagesPerPage == 1) {
return PhotoViewGalleryPageOptions(
filterQuality: FilterQuality.medium,
controller: photoViewControllers[index],
imageProvider: _createImageProviderFromKey(pageImages[0], context),
imageProvider:
_createImageProviderFromKey(pageImages[0], context),
fit: BoxFit.contain,
errorBuilder: (_, error, s, retry) {
return NetworkError(message: error.toString(), retry: retry);
@@ -645,32 +648,19 @@ class _ContinuousModeState extends State<_ContinuousMode>
ImageProvider _createImageProviderFromKey(
String imageKey, BuildContext context) {
if (imageKey.startsWith('file://')) {
return FileImage(File(imageKey.replaceFirst("file://", '')));
} else {
var reader = context.reader;
return ReaderImageProvider(
imageKey,
reader.type.comicSource!.key,
reader.cid,
reader.eid,
);
}
var reader = context.reader;
return ReaderImageProvider(
imageKey,
reader.type.comicSource!.key,
reader.cid,
reader.eid,
);
}
ImageProvider _createImageProvider(int page, BuildContext context) {
var reader = context.reader;
var imageKey = reader.images![page - 1];
if (imageKey.startsWith('file://')) {
return FileImage(File(imageKey.replaceFirst("file://", '')));
} else {
return ReaderImageProvider(
imageKey,
reader.type.comicSource!.key,
reader.cid,
reader.eid,
);
}
return _createImageProviderFromKey(imageKey, context);
}
void _precacheImage(int page, BuildContext context) {

View File

@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_memory_info/flutter_memory_info.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@@ -21,6 +22,7 @@ import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/history.dart';
import 'package:venera/foundation/image_provider/reader_image.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/pages/settings/settings_page.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/file_type.dart';
@@ -142,9 +144,27 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
if(appdata.settings['enableTurnPageByVolumeKey']) {
handleVolumeEvent();
}
setImageCacheSize();
super.initState();
}
void setImageCacheSize() async {
var availableRAM = await MemoryInfo.getFreePhysicalMemorySize();
if (availableRAM == null) return;
int maxImageCacheSize;
if (availableRAM < 1 << 30) {
maxImageCacheSize = 100 << 20;
} else if (availableRAM < 2 << 30) {
maxImageCacheSize = 200 << 20;
} else if (availableRAM < 4 << 30) {
maxImageCacheSize = 300 << 20;
} else {
maxImageCacheSize = 500 << 20;
}
Log.info("Reader", "Detect available RAM: $availableRAM, set image cache size to $maxImageCacheSize");
PaintingBinding.instance.imageCache.maximumSizeBytes = maxImageCacheSize;
}
@override
void dispose() {
autoPageTurningTimer?.cancel();
@@ -154,6 +174,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
Future.microtask(() {
DataSync().onDataChanged();
});
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20;
super.dispose();
}

View File

@@ -388,6 +388,15 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_memory_info:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: a401e3f96dca6ab0ab07b6b2ec0b649833f04c14
url: "https://github.com/wgh136/flutter_memory_info"
source: git
version: "0.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -1070,10 +1079,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
url: "https://pub.dev"
source: hosted
version: "5.5.4"
version: "5.9.0"
window_manager:
dependency: "direct main"
description:

View File

@@ -72,6 +72,9 @@ dependencies:
pdf: ^3.11.1
dynamic_color: ^1.7.0
shimmer: ^3.0.0
flutter_memory_info:
git:
url: https://github.com/wgh136/flutter_memory_info
dev_dependencies:
flutter_test: