resize large image
This commit is contained in:
parent
173c19d557
commit
fdbce75afd
|
@ -3,11 +3,13 @@ package niuhuan.pikapi
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Matrix
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
@ -23,6 +25,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.newSingleThreadContext
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import mobile.Mobile
|
import mobile.Mobile
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
@ -100,6 +103,7 @@ class MainActivity : FlutterActivity() {
|
||||||
// 获取可以迁移数据地址
|
// 获取可以迁移数据地址
|
||||||
"androidGetExtendDirs" -> androidGetExtendDirs()
|
"androidGetExtendDirs" -> androidGetExtendDirs()
|
||||||
"androidSecureFlag" -> androidSecureFlag(call.argument("flag")!!)
|
"androidSecureFlag" -> androidSecureFlag(call.argument("flag")!!)
|
||||||
|
"convertToPNG" -> convertToPNG(call.argument("path")!!)
|
||||||
else -> {
|
else -> {
|
||||||
notImplementedToken
|
notImplementedToken
|
||||||
}
|
}
|
||||||
|
@ -350,4 +354,36 @@ class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun convertToPNG(path: String): ByteArray {
|
||||||
|
BitmapFactory.decodeFile(path)?.let { bitmap ->
|
||||||
|
val maxWidth =
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> windowManager.currentWindowMetrics.bounds.width()
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 -> {
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
windowManager.defaultDisplay.getRealMetrics(displayMetrics)
|
||||||
|
displayMetrics.widthPixels
|
||||||
|
}
|
||||||
|
else -> throw Exception("not support")
|
||||||
|
}
|
||||||
|
if (bitmap.width > maxWidth) {
|
||||||
|
val newHeight = maxWidth * bitmap.height / bitmap.width
|
||||||
|
val newImage = Bitmap.createScaledBitmap(bitmap, maxWidth, newHeight, true)
|
||||||
|
return compressBitMap(newImage)
|
||||||
|
}
|
||||||
|
return compressBitMap(bitmap)
|
||||||
|
}
|
||||||
|
throw Exception("error pic")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compressBitMap(bitmap: Bitmap): ByteArray {
|
||||||
|
val bos = ByteArrayOutputStream()
|
||||||
|
bos.use { bos ->
|
||||||
|
Log.d("BITMAP", bitmap.width.toString())
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
|
||||||
|
}
|
||||||
|
return bos.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,23 +95,30 @@ List<T> filteredList<T>(List<T> list, bool Function(T) filter) {
|
||||||
|
|
||||||
/// 创建一个单选对话框, 用户取消选择返回null, 否则返回所选内容
|
/// 创建一个单选对话框, 用户取消选择返回null, 否则返回所选内容
|
||||||
Future<T?> chooseListDialog<T>(
|
Future<T?> chooseListDialog<T>(
|
||||||
BuildContext context,
|
BuildContext context, String title, List<T> items,
|
||||||
String title,
|
{String? tips}) async {
|
||||||
List<T> items,
|
List<Widget> widgets = [];
|
||||||
) async {
|
if (tips != null) {
|
||||||
|
widgets.add(
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(15, 5, 15, 15),
|
||||||
|
child: Text(tips),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
widgets.addAll(items.map((e) => SimpleDialogOption(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(e);
|
||||||
|
},
|
||||||
|
child: Text('$e'),
|
||||||
|
)));
|
||||||
|
|
||||||
return showDialog<T>(
|
return showDialog<T>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
children: items
|
children: widgets,
|
||||||
.map((e) => SimpleDialogOption(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(e);
|
|
||||||
},
|
|
||||||
child: Text('$e'),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:pikapi/basic/Entities.dart';
|
import 'package:pikapi/basic/Entities.dart';
|
||||||
|
@ -607,4 +608,9 @@ class Method {
|
||||||
"comicId": comicId,
|
"comicId": comicId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 转化为PNG
|
||||||
|
Future<Uint8List> convertToPNG(String path) async {
|
||||||
|
return await _channel.invokeMethod("convertToPNG", {"path": path});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../Common.dart';
|
||||||
|
import '../Method.dart';
|
||||||
|
|
||||||
|
const _propertyName = "convertToPNG";
|
||||||
|
var _convertToPNG = false;
|
||||||
|
|
||||||
|
Future initConvertToPNG() async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
_convertToPNG =
|
||||||
|
(await method.loadProperty(_propertyName, "false")) == "true";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool convertToPNG() {
|
||||||
|
return _convertToPNG;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _chooseConvertToPNGSetting(BuildContext context) async {
|
||||||
|
String? result = await chooseListDialog<String>(context, "超大图片缩放", ["是", "否"],
|
||||||
|
tips: "会增加耗电\n可以解决部分漫画崩溃的问题");
|
||||||
|
if (result != null) {
|
||||||
|
var target = result == "是";
|
||||||
|
await method.saveProperty(_propertyName, "$target");
|
||||||
|
_convertToPNG = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget convertToPNGSetting() {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (BuildContext context, void Function(void Function()) setState) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text("读取到超大图片时进行缩放(防止崩溃)"),
|
||||||
|
subtitle: Text(_convertToPNG ? "是" : "否"),
|
||||||
|
onTap: () async {
|
||||||
|
await _chooseConvertToPNGSetting(context);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import 'package:pikapi/basic/config/AutoClean.dart';
|
||||||
import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
||||||
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
||||||
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
||||||
|
import 'package:pikapi/basic/config/ConvertToPNG.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
||||||
|
@ -67,6 +68,7 @@ class _InitScreenState extends State<InitScreen> {
|
||||||
await initDownloadAndExportPath();
|
await initDownloadAndExportPath();
|
||||||
await initAndroidSecureFlag();
|
await initAndroidSecureFlag();
|
||||||
await initDownloadThreadCount();
|
await initDownloadThreadCount();
|
||||||
|
await initConvertToPNG();
|
||||||
// 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
|
// 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
|
||||||
if (await method.preLogin()) {
|
if (await method.preLogin()) {
|
||||||
// 如果token或username+password有效则直接进入登录好的界面
|
// 如果token或username+password有效则直接进入登录好的界面
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:pikapi/basic/config/AutoClean.dart';
|
||||||
import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
||||||
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
||||||
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
||||||
|
import 'package:pikapi/basic/config/ConvertToPNG.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
||||||
|
@ -37,6 +38,7 @@ class SettingsScreen extends StatelessWidget {
|
||||||
NetworkSetting(),
|
NetworkSetting(),
|
||||||
Divider(),
|
Divider(),
|
||||||
qualitySetting(),
|
qualitySetting(),
|
||||||
|
convertToPNGSetting(),
|
||||||
readerTypeSetting(),
|
readerTypeSetting(),
|
||||||
readerDirectionSetting(),
|
readerDirectionSetting(),
|
||||||
autoFullScreenSetting(),
|
autoFullScreenSetting(),
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:pikapi/basic/Method.dart';
|
import 'package:pikapi/basic/Method.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:pikapi/basic/config/ConvertToPNG.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui show Codec;
|
import 'dart:ui' as ui show Codec;
|
||||||
|
|
||||||
|
Future<Uint8List> _loadImageFile(String path) {
|
||||||
|
if (convertToPNG()) {
|
||||||
|
return method.convertToPNG(path);
|
||||||
|
}
|
||||||
|
return File(path).readAsBytes();
|
||||||
|
}
|
||||||
|
|
||||||
// 从本地加载图片
|
// 从本地加载图片
|
||||||
class ResourceFileImageProvider extends ImageProvider<ResourceFileImageProvider> {
|
class ResourceFileImageProvider
|
||||||
|
extends ImageProvider<ResourceFileImageProvider> {
|
||||||
final String path;
|
final String path;
|
||||||
final double scale;
|
final double scale;
|
||||||
|
|
||||||
ResourceFileImageProvider(this.path, {this.scale = 1.0});
|
ResourceFileImageProvider(this.path, {this.scale = 1.0});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ImageStreamCompleter load(ResourceFileImageProvider key, DecoderCallback decode) {
|
ImageStreamCompleter load(
|
||||||
|
ResourceFileImageProvider key, DecoderCallback decode) {
|
||||||
return MultiFrameImageStreamCompleter(
|
return MultiFrameImageStreamCompleter(
|
||||||
codec: _loadAsync(key),
|
codec: _loadAsync(key),
|
||||||
scale: key.scale,
|
scale: key.scale,
|
||||||
|
@ -22,14 +33,15 @@ class ResourceFileImageProvider extends ImageProvider<ResourceFileImageProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ResourceFileImageProvider> obtainKey(ImageConfiguration configuration) {
|
Future<ResourceFileImageProvider> obtainKey(
|
||||||
|
ImageConfiguration configuration) {
|
||||||
return SynchronousFuture<ResourceFileImageProvider>(this);
|
return SynchronousFuture<ResourceFileImageProvider>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ui.Codec> _loadAsync(ResourceFileImageProvider key) async {
|
Future<ui.Codec> _loadAsync(ResourceFileImageProvider key) async {
|
||||||
assert(key == this);
|
assert(key == this);
|
||||||
return PaintingBinding.instance!
|
return PaintingBinding.instance!
|
||||||
.instantiateImageCodec(await File(path).readAsBytes());
|
.instantiateImageCodec(await _loadImageFile(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -96,7 +108,8 @@ class ResourceDownloadFileImageProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从远端加载图片 暂时未使用 (现在都是先获取路径然后再通过file显示)
|
// 从远端加载图片 暂时未使用 (现在都是先获取路径然后再通过file显示)
|
||||||
class ResourceRemoteImageProvider extends ImageProvider<ResourceRemoteImageProvider> {
|
class ResourceRemoteImageProvider
|
||||||
|
extends ImageProvider<ResourceRemoteImageProvider> {
|
||||||
final String fileServer;
|
final String fileServer;
|
||||||
final String path;
|
final String path;
|
||||||
final double scale;
|
final double scale;
|
||||||
|
@ -113,7 +126,8 @@ class ResourceRemoteImageProvider extends ImageProvider<ResourceRemoteImageProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ResourceRemoteImageProvider> obtainKey(ImageConfiguration configuration) {
|
Future<ResourceRemoteImageProvider> obtainKey(
|
||||||
|
ImageConfiguration configuration) {
|
||||||
return SynchronousFuture<ResourceRemoteImageProvider>(this);
|
return SynchronousFuture<ResourceRemoteImageProvider>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue