improve image api & update version code

This commit is contained in:
2024-11-05 13:13:32 +08:00
parent 07f8f2a4af
commit b49e528ff4
9 changed files with 231 additions and 107 deletions

View File

@@ -224,7 +224,25 @@ let Convert = {
key: key,
isEncode: false
});
},
/** Encode bytes to hex string
* @param bytes {ArrayBuffer}
* @return {string}
*/
hexEncode: (bytes) => {
const hexDigits = '0123456789abcdef';
const view = new Uint8Array(bytes);
let charCodes = new Uint8Array(view.length * 2);
let j = 0;
for (let i = 0; i < view.length; i++) {
let byte = view[i];
charCodes[j++] = hexDigits.charCodeAt((byte >> 4) & 0xF);
charCodes[j++] = hexDigits.charCodeAt(byte & 0xF);
}
return String.fromCharCode(...charCodes);
},
}
/**
@@ -1064,26 +1082,28 @@ class Image {
}
/**
* Create a new image based on the current image without copying the data.
* Modifying the new image will affect the current image.
* fill [image] with range(srcX, srcY, width, height) to this image at (x, y)
* @param x
* @param y
* @param image
* @param srcX
* @param srcY
* @param width
* @param height
* @returns {Image|null}
*/
subImage(x, y, width, height) {
let key = sendMessage({
fillImageRangeAt(x, y, image, srcX, srcY, width, height) {
sendMessage({
method: "image",
function: "subImage",
function: "fillImageRangeAt",
key: this.key,
x: x,
y: y,
image: image.key,
srcX: srcX,
srcY: srcY,
width: width,
height: height
})
if(key == null) return null;
return new Image(key);
}
get width() {

View File

@@ -10,7 +10,7 @@ export "widget_utils.dart";
export "context.dart";
class _App {
final version = "1.0.1";
final version = "1.0.2";
bool get isAndroid => Platform.isAndroid;

View File

@@ -87,17 +87,16 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
return await decode(buffer);
} catch (e) {
await CacheManager().delete(this.key);
Object error = e;
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);
error = Exception("Expected image data, but got text: $text");
throw Exception("Expected image data, but got text: $text");
} catch (e) {
// ignore
}
}
throw error;
rethrow;
}
} catch (e) {
scheduleMicrotask(() {

View File

@@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:venera/foundation/cache_manager.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/consts.dart';
import 'package:venera/utils/image.dart';
import 'app_dio.dart';
@@ -27,8 +28,8 @@ class ImageDownloader {
configs = comicSource?.getThumbnailLoadingConfig?.call(url) ?? {};
}
configs['headers'] ??= {};
if(configs['headers']['user-agent'] == null
&& configs['headers']['User-Agent'] == null) {
if (configs['headers']['user-agent'] == null &&
configs['headers']['User-Agent'] == null) {
configs['headers']['user-agent'] = webUA;
}
@@ -120,11 +121,22 @@ class ImageDownloader {
buffer = configs['onResponse'](buffer);
}
await CacheManager().writeCache(cacheKey, buffer);
var data = Uint8List.fromList(buffer);
buffer.clear();
if (configs['modifyImage'] != null) {
var newData = await modifyImageWithScript(
data,
configs['modifyImage'],
);
data = newData;
}
await CacheManager().writeCache(cacheKey, data);
yield ImageDownloadProgress(
currentBytes: buffer.length,
totalBytes: buffer.length,
imageBytes: Uint8List.fromList(buffer),
currentBytes: data.length,
totalBytes: data.length,
imageBytes: data,
);
}
}

View File

@@ -161,6 +161,7 @@ class _BodyState extends State<_Body> {
for (var item in source.settings!.entries) {
var key = item.key;
String type = item.value['type'];
try {
if (type == "select") {
var current = source.data['settings'][key];
if (current == null) {
@@ -228,6 +229,10 @@ class _BodyState extends State<_Body> {
);
}
}
catch(e, s) {
Log.error("ComicSourcePage", "Failed to build a setting\n$e\n$s");
}
}
}
void delete(ComicSource source) {

View File

@@ -389,7 +389,7 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
Widget buildPageInfoText() {
var epName = context.reader.widget.chapters?.values
.elementAt(context.reader.chapter - 1) ??
.elementAtOrNull(context.reader.chapter - 1) ??
"E${context.reader.chapter}";
if (epName.length > 8) {
epName = "${epName.substring(0, 8)}...";

View File

@@ -1,3 +1,4 @@
import 'dart:ffi';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;
@@ -12,7 +13,12 @@ class Image {
final int height;
Image(this._data, this.width, this.height);
Image(this._data, this.width, this.height) {
if (_data.length != width * height) {
throw ArgumentError(
'Invalid argument: data length must be equal to width * height.');
}
}
Image.empty(this.width, this.height) : _data = Uint32List(width * height);
@@ -25,7 +31,7 @@ class Image {
throw Exception('Failed to decode image');
}
var image = Image(
Uint32List.fromList(info.buffer.asUint32List()),
info.buffer.asUint32List(),
frame.image.width,
frame.image.height,
);
@@ -34,6 +40,20 @@ class Image {
}
Image copyRange(int x, int y, int width, int height) {
if (width + x > this.width) {
throw ArgumentError('''
Invalid argument: x + width must be less than or equal to the image width.
x: $x, width: $width, image width: ${this.width}
'''
.trim());
}
if (height + y > this.height) {
throw ArgumentError('''
Invalid argument: y + height must be less than or equal to the image height.
y: $y, height: $height, image height: ${this.height}
'''
.trim());
}
var data = Uint32List(width * height);
for (var j = 0; j < height; j++) {
for (var i = 0; i < width; i++) {
@@ -44,6 +64,20 @@ class Image {
}
void fillImageAt(int x, int y, Image image) {
if (x + image.width > width) {
throw ArgumentError('''
Invalid argument: x + image width must be less than or equal to the image width.
x: $x, image width: ${image.width}, image width: $width
'''
.trim());
}
if (y + image.height > height) {
throw ArgumentError('''
Invalid argument: y + image height must be less than or equal to the image height.
y: $y, image height: ${image.height}, image height: $height
'''
.trim());
}
for (var j = 0; j < image.height && (j + y) < height; j++) {
for (var i = 0; i < image.width && (i + x) < width; i++) {
_data[(j + y) * width + i + x] = image._data[j * image.width + i];
@@ -51,6 +85,44 @@ class Image {
}
}
void fillImageRangeAt(
int x, int y, Image image, int srcX, int srcY, int width, int height) {
if (x + width > this.width) {
throw ArgumentError('''
Invalid argument: x + width must be less than or equal to the image width.
x: $x, width: $width, image width: ${this.width}
'''
.trim());
}
if (y + height > this.height) {
throw ArgumentError('''
Invalid argument: y + height must be less than or equal to the image height.
y: $y, height: $height, image height: ${this.height}
'''
.trim());
}
if (srcX + width > image.width) {
throw ArgumentError('''
Invalid argument: srcX + width must be less than or equal to the image width.
srcX: $srcX, width: $width, image width: ${image.width}
'''
.trim());
}
if (srcY + height > image.height) {
throw ArgumentError('''
Invalid argument: srcY + height must be less than or equal to the image height.
srcY: $srcY, height: $height, image height: ${image.height}
'''
.trim());
}
for (var j = 0; j < height; j++) {
for (var i = 0; i < width; i++) {
_data[(j + y) * this.width + i + x] =
image._data[(j + srcY) * image.width + i + srcX];
}
}
}
Image copyAndRotate90() {
var data = Uint32List(width * height);
for (var j = 0; j < height; j++) {
@@ -62,28 +134,37 @@ class Image {
}
Color getPixel(int x, int y) {
if (x < 0 || x >= width) {
throw ArgumentError(
'Invalid argument: x must be in the range of [0, $width).');
}
if (y < 0 || y >= height) {
throw ArgumentError(
'Invalid argument: y must be in the range of [0, $height).');
}
return Color.fromValue(_data[y * width + x]);
}
void setPixel(int x, int y, Color color) {
if (x < 0 || x >= width) {
throw ArgumentError(
'Invalid argument: x must be in the range of [0, $width).');
}
if (y < 0 || y >= height) {
throw ArgumentError(
'Invalid argument: y must be in the range of [0, $height).');
}
_data[y * width + x] = color.value;
}
Image subImage(int x, int y, int width, int height) {
var data = Uint32List.sublistView(
_data,
y * this.width + x,
width * height,
);
return Image(data, width, height);
}
Uint8List encodePng() {
return lodepng.encodePng(lodepng.Image(
var data = lodepng.encodePngToPointer(lodepng.Image(
_data.buffer.asUint8List(),
width,
height,
));
return Pointer<Uint8>.fromAddress(data.address).asTypedList(data.length,
finalizer: lodepng.ByteBuffer.finalizer);
}
}
@@ -166,16 +247,21 @@ class JsEngine {
if (image2 == null) return null;
image.fillImageAt(x, y, image2);
return null;
case 'subImage':
case 'fillImageRangeAt':
var key = message['key'];
var image = images[key];
if (image == null) return null;
var x = message['x'];
var y = message['y'];
var key2 = message['image'];
var image2 = images[key2];
if (image2 == null) return null;
var srcX = message['srcX'];
var srcY = message['srcY'];
var width = message['width'];
var height = message['height'];
var newImage = image.subImage(x, y, width, height);
return setImage(newImage);
image.fillImageRangeAt(x, y, image2, srcX, srcY, width, height);
return null;
case 'getWidth':
var key = message['key'];
var image = images[key];
@@ -213,16 +299,18 @@ Future<Uint8List> modifyImageWithScript(Uint8List data, String script) async {
jsEngine.runCode(script);
var key = jsEngine.setImage(image);
var res = jsEngine.runCode('''
let func = () => {
let image = new Image($key);
let result = modifyImage(image);
return result.key;
}
func();
''');
var newImage = jsEngine.images[res];
var data = newImage!.encodePng();
return data;
return Uint8List.fromList(data);
});
}
finally {
} finally {
_tasksCount--;
}
}

View File

@@ -461,7 +461,7 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: "115b896a04c270a8d6d5d7bea09dcd04047bfad3"
resolved-ref: "5223cf4ce8aad1c2315db0093db3cc5c6c7191a8"
url: "https://github.com/venera-app/lodepng_flutter"
source: git
version: "0.0.1"

View File

@@ -2,7 +2,7 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.0.1+101
version: 1.0.2+102
environment:
sdk: '>=3.5.0 <4.0.0'