mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-20 19:12:04 +00:00
feature: fix crop border & archive image
This commit is contained in:
parent
3ebbcc5e32
commit
01b743dade
5 changed files with 159 additions and 115 deletions
|
|
@ -17,12 +17,12 @@ Future<LocalArchive> getArchiveDataFromFile(
|
|||
return compute(_extractArchive, path);
|
||||
}
|
||||
|
||||
List<LocalArchive> _extract(String data) {
|
||||
return _searchForArchive(Directory(data));
|
||||
Future<List<LocalArchive>> _extract(String data) async {
|
||||
return await _searchForArchive(Directory(data));
|
||||
}
|
||||
|
||||
List<LocalArchive> _list = [];
|
||||
List<LocalArchive> _searchForArchive(Directory dir) {
|
||||
Future<List<LocalArchive>> _searchForArchive(Directory dir) async {
|
||||
List<FileSystemEntity> entities = dir.listSync();
|
||||
for (FileSystemEntity entity in entities) {
|
||||
if (entity is Directory) {
|
||||
|
|
@ -30,7 +30,7 @@ List<LocalArchive> _searchForArchive(Directory dir) {
|
|||
} else if (entity is File) {
|
||||
String path = entity.path;
|
||||
if (_isArchiveFile(path)) {
|
||||
final dd = _extractArchive(path);
|
||||
final dd = await compute(_extractArchive, path);
|
||||
_list.add(dd);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,6 @@ bool _isArchiveFile(String path) {
|
|||
}
|
||||
|
||||
LocalArchive _extractArchive(String path) {
|
||||
final bytes = File(path).readAsBytesSync();
|
||||
final localArchive = LocalArchive()
|
||||
..path = path
|
||||
..extensionType =
|
||||
|
|
@ -73,15 +72,16 @@ LocalArchive _extractArchive(String path) {
|
|||
.last
|
||||
.replaceAll(RegExp(r'\.(cbz|zip|cbt|tar)'), '');
|
||||
Archive? archive;
|
||||
final inputStream = InputFileStream(path);
|
||||
final extensionType = localArchive.extensionType;
|
||||
if (extensionType == LocalExtensionType.cbt ||
|
||||
extensionType == LocalExtensionType.tar) {
|
||||
archive = TarDecoder().decodeBytes(bytes);
|
||||
archive = TarDecoder().decodeBuffer(inputStream);
|
||||
} else {
|
||||
archive = ZipDecoder().decodeBytes(bytes);
|
||||
archive = ZipDecoder().decodeBuffer(inputStream);
|
||||
}
|
||||
|
||||
for (final file in archive) {
|
||||
for (final file in archive.files) {
|
||||
final filename = file.name;
|
||||
if (file.isFile) {
|
||||
if (_isImageFile(filename)) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
import 'package:draggable_menu/draggable_menu.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -29,7 +29,6 @@ import 'package:mangayomi/modules/manga/reader/providers/reader_controller_provi
|
|||
import 'package:mangayomi/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart';
|
||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.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';
|
||||
|
|
@ -152,7 +151,7 @@ class MangaChapterPageGallery extends ConsumerStatefulWidget {
|
|||
required this.archiveImages});
|
||||
final ReaderController readerController;
|
||||
final Directory path;
|
||||
final List url;
|
||||
final List<String> url;
|
||||
final List<bool> isLocaleList;
|
||||
final Chapter chapter;
|
||||
final List<Uint8List> archiveImages;
|
||||
|
|
@ -220,7 +219,7 @@ class _MangaChapterPageGalleryState
|
|||
"pageIndex": posIndex,
|
||||
"path": _dir!.path,
|
||||
};
|
||||
compute(_isolateService, jsonEncode(datas));
|
||||
Isolate.spawn(_isarIsolateService, jsonEncode(datas));
|
||||
_currentIndex = posIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +243,7 @@ class _MangaChapterPageGalleryState
|
|||
"pageIndex": index,
|
||||
"path": _dir!.path,
|
||||
};
|
||||
compute(_isolateService, jsonEncode(datas));
|
||||
Isolate.spawn(_isarIsolateService, jsonEncode(datas));
|
||||
_currentIndex = index;
|
||||
if (_imageDetailY != 0) {
|
||||
_imageDetailY = 0;
|
||||
|
|
@ -322,45 +321,18 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
}
|
||||
|
||||
Future _cropImageFuture(Uint8List? image, String? url) async {
|
||||
final cropImage = await ref.watch(autoCropImageProvider(
|
||||
url,
|
||||
image,
|
||||
).future);
|
||||
if (cropImage != null) {
|
||||
cropImagesList.add(cropImage);
|
||||
} else {
|
||||
cropImagesList.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
List<Uint8List?> cropImagesList = [];
|
||||
List<Uint8List?> _cropImagesList = [];
|
||||
bool isOk = false;
|
||||
_cropImage() async {
|
||||
List<Future> futures = [];
|
||||
if (!isOk) {
|
||||
isOk = true;
|
||||
if (widget.archiveImages.isNotEmpty) {
|
||||
for (var image in widget.archiveImages) {
|
||||
futures.add(_cropImageFuture(image, null));
|
||||
}
|
||||
} else if (widget.isLocaleList.contains(true)) {
|
||||
for (var i = 0; i < widget.isLocaleList.length; i++) {
|
||||
if (widget.isLocaleList[i] == true) {
|
||||
Uint8List? image = File('${widget.path.path}${padIndex(i + 1)}.jpg')
|
||||
.readAsBytesSync();
|
||||
futures.add(_cropImageFuture(image, null));
|
||||
} else {
|
||||
futures.add(_cropImageFuture(null, null));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var url in widget.url) {
|
||||
futures.add(_cropImageFuture(null, url));
|
||||
}
|
||||
}
|
||||
await Future.wait(futures);
|
||||
setState(() {});
|
||||
_cropImagesList = await ref.watch(autoCropBorderProvider(
|
||||
archiveImages: widget.archiveImages,
|
||||
isLocaleList: widget.isLocaleList,
|
||||
path: widget.path,
|
||||
url: widget.url)
|
||||
.future);
|
||||
// print(cropImage.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -766,8 +738,7 @@ class _MangaChapterPageGalleryState
|
|||
),
|
||||
if (cropBorders)
|
||||
Positioned(
|
||||
left: 3,
|
||||
right: 0,
|
||||
right: 8,
|
||||
child: Transform.scale(
|
||||
scaleX: 2.5,
|
||||
child: const Row(
|
||||
|
|
@ -776,7 +747,7 @@ class _MangaChapterPageGalleryState
|
|||
children: [
|
||||
Text(
|
||||
'\\',
|
||||
style: TextStyle(fontSize: 25),
|
||||
style: TextStyle(fontSize: 17),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1016,8 +987,8 @@ class _MangaChapterPageGalleryState
|
|||
onDoubleTap: () {},
|
||||
child: ImageViewVertical(
|
||||
archiveImage:
|
||||
cropImagesList.isNotEmpty && cropBorders == true
|
||||
? cropImagesList[index]
|
||||
_cropImagesList.isNotEmpty && cropBorders == true
|
||||
? _cropImagesList[index]
|
||||
: widget.archiveImages.isNotEmpty
|
||||
? widget.archiveImages[index]
|
||||
: null,
|
||||
|
|
@ -1034,7 +1005,7 @@ class _MangaChapterPageGalleryState
|
|||
length:
|
||||
widget.readerController.getPageLength(widget.url),
|
||||
isLocale:
|
||||
cropImagesList.isNotEmpty && cropBorders == true
|
||||
_cropImagesList.isNotEmpty && cropBorders == true
|
||||
? true
|
||||
: widget.isLocaleList[index],
|
||||
),
|
||||
|
|
@ -1062,8 +1033,8 @@ class _MangaChapterPageGalleryState
|
|||
itemBuilder: (BuildContext context, int index) {
|
||||
return ImageViewCenter(
|
||||
archiveImage:
|
||||
cropImagesList.isNotEmpty && cropBorders == true
|
||||
? cropImagesList[index]
|
||||
_cropImagesList.isNotEmpty && cropBorders == true
|
||||
? _cropImagesList[index]
|
||||
: widget.archiveImages.isNotEmpty
|
||||
? widget.archiveImages[index]
|
||||
: null,
|
||||
|
|
@ -1212,7 +1183,7 @@ class _MangaChapterPageGalleryState
|
|||
|
||||
_doubleClickAnimationController.forward();
|
||||
},
|
||||
isLocale: cropImagesList.isNotEmpty &&
|
||||
isLocale: _cropImagesList.isNotEmpty &&
|
||||
cropBorders == true
|
||||
? true
|
||||
: _isReversHorizontal
|
||||
|
|
@ -1286,7 +1257,7 @@ class _MangaChapterPageGalleryState
|
|||
}
|
||||
}
|
||||
|
||||
_isolateService(String data) async {
|
||||
_isarIsolateService(String data) async {
|
||||
late Isar isarIsolate;
|
||||
isarIsolate = await StorageProvider().initDB(jsonDecode(data)["path"]);
|
||||
Chapter? chapter =
|
||||
|
|
|
|||
|
|
@ -1,15 +1,51 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:image/image.dart';
|
||||
import 'package:mangayomi/utils/reg_exp_matcher.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'auto_crop_image_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<Uint8List?> autoCropImage(
|
||||
AutoCropImageRef ref, String? url, Uint8List? data) async {
|
||||
Future<List<Uint8List?>> autoCropBorder(AutoCropBorderRef ref,
|
||||
{required List<String?> url,
|
||||
required List<Uint8List?> archiveImages,
|
||||
required List<bool> isLocaleList,
|
||||
required Directory path}) async {
|
||||
List<Future<CropBorderClassRes?>> futures = [];
|
||||
if (archiveImages.isNotEmpty) {
|
||||
for (var i = 0; i < archiveImages.length; i++) {
|
||||
futures.add(_cropImageFuture(archiveImages[i], null, i));
|
||||
}
|
||||
} else if (isLocaleList.contains(true)) {
|
||||
for (var i = 0; i < isLocaleList.length; i++) {
|
||||
if (isLocaleList[i] == true) {
|
||||
Uint8List? image =
|
||||
File('${path.path}${padIndex(i + 1)}.jpg').readAsBytesSync();
|
||||
futures.add(_cropImageFuture(image, null, i));
|
||||
} else {
|
||||
futures.add(_cropImageFuture(null, null, i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < url.length; i++) {
|
||||
futures.add(_cropImageFuture(null, url[i], i));
|
||||
}
|
||||
}
|
||||
List<CropBorderClassRes?> result = await Future.wait(futures);
|
||||
|
||||
result.sort((a, b) => a!.index.compareTo(b!.index));
|
||||
List<Uint8List?> cropImageRes = [];
|
||||
for (var image in result) {
|
||||
cropImageRes.add(image!.image);
|
||||
}
|
||||
return cropImageRes;
|
||||
}
|
||||
|
||||
Future<CropBorderClassRes?> _cropImageFuture(
|
||||
Uint8List? image, String? url, int index) async {
|
||||
Uint8List? oldImage;
|
||||
Uint8List? newImage;
|
||||
String path = "";
|
||||
File? cachedImage;
|
||||
if (url != null) {
|
||||
|
|
@ -20,18 +56,37 @@ Future<Uint8List?> autoCropImage(
|
|||
}
|
||||
if (path.isNotEmpty) {
|
||||
oldImage = File(path).readAsBytesSync();
|
||||
} else if (data != null) {
|
||||
oldImage = data;
|
||||
} else if (image != null) {
|
||||
oldImage = image;
|
||||
}
|
||||
if (oldImage != null) {
|
||||
newImage = await compute(autocropImageIsolate, oldImage);
|
||||
var receiverPort = ReceivePort();
|
||||
await Isolate.spawn(_autocropImageIsolate,
|
||||
CropBorderClass(oldImage, receiverPort.sendPort));
|
||||
final newImage = await receiverPort.first as Uint8List?;
|
||||
return CropBorderClassRes(newImage, index);
|
||||
}
|
||||
return newImage;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Uint8List?> autocropImageIsolate(List<int> data) async {
|
||||
class CropBorderClassRes {
|
||||
final Uint8List? image;
|
||||
final int index;
|
||||
|
||||
CropBorderClassRes(this.image, this.index);
|
||||
}
|
||||
|
||||
class CropBorderClass {
|
||||
final Uint8List? image;
|
||||
final SendPort sendPort;
|
||||
|
||||
CropBorderClass(this.image, this.sendPort);
|
||||
}
|
||||
|
||||
void _autocropImageIsolate(CropBorderClass cropData) async {
|
||||
Image? croppedImage;
|
||||
Image? image = decodeImage(data);
|
||||
Image? image = decodeImage(cropData.image!);
|
||||
final old = image;
|
||||
image = copyCrop(image!, 0, 0, image.width, image.height);
|
||||
|
||||
|
|
@ -109,7 +164,8 @@ Future<Uint8List?> autocropImageIsolate(List<int> data) async {
|
|||
bottom - top + 1,
|
||||
);
|
||||
if (old != croppedImage) {
|
||||
return encodeJpg(croppedImage) as Uint8List;
|
||||
cropData.sendPort.send(encodeJpg(croppedImage) as Uint8List);
|
||||
} else {
|
||||
cropData.sendPort.send(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'auto_crop_image_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$autoCropImageHash() => r'aae86e74203def1027400fa81e06e8e10344487d';
|
||||
String _$autoCropBorderHash() => r'3e283f997cd9de62a7cf39d2045c249b2a6c027e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -29,35 +29,41 @@ class _SystemHash {
|
|||
}
|
||||
}
|
||||
|
||||
typedef AutoCropImageRef = AutoDisposeFutureProviderRef<Uint8List?>;
|
||||
typedef AutoCropBorderRef = AutoDisposeFutureProviderRef<List<Uint8List?>>;
|
||||
|
||||
/// See also [autoCropImage].
|
||||
@ProviderFor(autoCropImage)
|
||||
const autoCropImageProvider = AutoCropImageFamily();
|
||||
/// See also [autoCropBorder].
|
||||
@ProviderFor(autoCropBorder)
|
||||
const autoCropBorderProvider = AutoCropBorderFamily();
|
||||
|
||||
/// See also [autoCropImage].
|
||||
class AutoCropImageFamily extends Family<AsyncValue<Uint8List?>> {
|
||||
/// See also [autoCropImage].
|
||||
const AutoCropImageFamily();
|
||||
/// See also [autoCropBorder].
|
||||
class AutoCropBorderFamily extends Family<AsyncValue<List<Uint8List?>>> {
|
||||
/// See also [autoCropBorder].
|
||||
const AutoCropBorderFamily();
|
||||
|
||||
/// See also [autoCropImage].
|
||||
AutoCropImageProvider call(
|
||||
String? url,
|
||||
Uint8List? data,
|
||||
) {
|
||||
return AutoCropImageProvider(
|
||||
url,
|
||||
data,
|
||||
/// See also [autoCropBorder].
|
||||
AutoCropBorderProvider call({
|
||||
required List<String?> url,
|
||||
required List<Uint8List?> archiveImages,
|
||||
required List<bool> isLocaleList,
|
||||
required Directory path,
|
||||
}) {
|
||||
return AutoCropBorderProvider(
|
||||
url: url,
|
||||
archiveImages: archiveImages,
|
||||
isLocaleList: isLocaleList,
|
||||
path: path,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoCropImageProvider getProviderOverride(
|
||||
covariant AutoCropImageProvider provider,
|
||||
AutoCropBorderProvider getProviderOverride(
|
||||
covariant AutoCropBorderProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.url,
|
||||
provider.data,
|
||||
url: provider.url,
|
||||
archiveImages: provider.archiveImages,
|
||||
isLocaleList: provider.isLocaleList,
|
||||
path: provider.path,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -73,47 +79,58 @@ class AutoCropImageFamily extends Family<AsyncValue<Uint8List?>> {
|
|||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'autoCropImageProvider';
|
||||
String? get name => r'autoCropBorderProvider';
|
||||
}
|
||||
|
||||
/// See also [autoCropImage].
|
||||
class AutoCropImageProvider extends AutoDisposeFutureProvider<Uint8List?> {
|
||||
/// See also [autoCropImage].
|
||||
AutoCropImageProvider(
|
||||
this.url,
|
||||
this.data,
|
||||
) : super.internal(
|
||||
(ref) => autoCropImage(
|
||||
/// See also [autoCropBorder].
|
||||
class AutoCropBorderProvider
|
||||
extends AutoDisposeFutureProvider<List<Uint8List?>> {
|
||||
/// See also [autoCropBorder].
|
||||
AutoCropBorderProvider({
|
||||
required this.url,
|
||||
required this.archiveImages,
|
||||
required this.isLocaleList,
|
||||
required this.path,
|
||||
}) : super.internal(
|
||||
(ref) => autoCropBorder(
|
||||
ref,
|
||||
url,
|
||||
data,
|
||||
url: url,
|
||||
archiveImages: archiveImages,
|
||||
isLocaleList: isLocaleList,
|
||||
path: path,
|
||||
),
|
||||
from: autoCropImageProvider,
|
||||
name: r'autoCropImageProvider',
|
||||
from: autoCropBorderProvider,
|
||||
name: r'autoCropBorderProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$autoCropImageHash,
|
||||
dependencies: AutoCropImageFamily._dependencies,
|
||||
: _$autoCropBorderHash,
|
||||
dependencies: AutoCropBorderFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AutoCropImageFamily._allTransitiveDependencies,
|
||||
AutoCropBorderFamily._allTransitiveDependencies,
|
||||
);
|
||||
|
||||
final String? url;
|
||||
final Uint8List? data;
|
||||
final List<String?> url;
|
||||
final List<Uint8List?> archiveImages;
|
||||
final List<bool> isLocaleList;
|
||||
final Directory path;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AutoCropImageProvider &&
|
||||
return other is AutoCropBorderProvider &&
|
||||
other.url == url &&
|
||||
other.data == data;
|
||||
other.archiveImages == archiveImages &&
|
||||
other.isLocaleList == isLocaleList &&
|
||||
other.path == path;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, url.hashCode);
|
||||
hash = _SystemHash.combine(hash, data.hashCode);
|
||||
hash = _SystemHash.combine(hash, archiveImages.hashCode);
|
||||
hash = _SystemHash.combine(hash, isLocaleList.hashCode);
|
||||
hash = _SystemHash.combine(hash, path.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import 'package:permission_handler/permission_handler.dart';
|
|||
import 'package:path/path.dart' as path;
|
||||
|
||||
class StorageProvider {
|
||||
RegExp regExpChar = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
final RegExp _regExpChar = RegExp(r'[^a-zA-Z0-9 .()\-\s]');
|
||||
Future<bool> requestPermission() async {
|
||||
Permission permission = Permission.manageExternalStorage;
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
|
|
@ -61,18 +61,18 @@ class StorageProvider {
|
|||
) async {
|
||||
final manga = chapter.manga.value!;
|
||||
String scanlator = chapter.scanlator!.isNotEmpty
|
||||
? "${chapter.scanlator!.replaceAll(regExpChar, '_')}_"
|
||||
? "${chapter.scanlator!.replaceAll(_regExpChar, '_')}_"
|
||||
: "";
|
||||
final dir = await getDirectory();
|
||||
return Directory(
|
||||
"${dir!.path}/downloads/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(regExpChar, '_')}/$scanlator${chapter.name!.replaceAll(regExpChar, '_')}/");
|
||||
"${dir!.path}/downloads/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_regExpChar, '_')}/$scanlator${chapter.name!.replaceAll(_regExpChar, '_')}/");
|
||||
}
|
||||
|
||||
Future<Directory?> getMangaMainDirectory(Chapter chapter) async {
|
||||
final manga = chapter.manga.value!;
|
||||
final dir = await getDirectory();
|
||||
return Directory(
|
||||
"${dir!.path}/downloads/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(regExpChar, '_')}/");
|
||||
"${dir!.path}/downloads/${manga.source} (${manga.lang!.toUpperCase()})/${manga.name!.replaceAll(_regExpChar, '_')}/");
|
||||
}
|
||||
|
||||
Future<Directory?> getDatabaseDirectory() async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue