generate LVGL 9 binary
Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
@@ -2,7 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cinema/common/env_not_found_exception.dart';
|
import 'package:cinema/common/env_not_found_exception.dart';
|
||||||
import 'package:cinema/feature/middlewares/cors.dart';
|
import 'package:cinema/feature/middleware/cors.dart';
|
||||||
import 'package:cinema/feature/poster/data/repositories/image_loader.dart';
|
import 'package:cinema/feature/poster/data/repositories/image_loader.dart';
|
||||||
import 'package:cinema/feature/poster/data/services/poster.service.dart';
|
import 'package:cinema/feature/poster/data/services/poster.service.dart';
|
||||||
import 'package:cinema/feature/root/data/service/root.service.dart';
|
import 'package:cinema/feature/root/data/service/root.service.dart';
|
||||||
|
|||||||
45
server/cinema/lib/common/image_resizer.dart
Normal file
45
server/cinema/lib/common/image_resizer.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class ImageResizer {
|
||||||
|
static const int maxWidth = 480;
|
||||||
|
static const int maxHeight = 320;
|
||||||
|
|
||||||
|
img.Image resizeToFit(img.Image image) {
|
||||||
|
final w = image.width;
|
||||||
|
final h = image.height;
|
||||||
|
|
||||||
|
if (w <= maxWidth && h <= maxHeight) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
final aspectRatio = w / h;
|
||||||
|
int newWidth;
|
||||||
|
int newHeight;
|
||||||
|
|
||||||
|
if (w > h) {
|
||||||
|
newWidth = maxWidth;
|
||||||
|
newHeight = (maxWidth / aspectRatio).round();
|
||||||
|
if (newHeight > maxHeight) {
|
||||||
|
newHeight = maxHeight;
|
||||||
|
newWidth = (maxHeight * aspectRatio).round();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newHeight = maxHeight;
|
||||||
|
newWidth = (maxHeight * aspectRatio).round();
|
||||||
|
if (newWidth > maxWidth) {
|
||||||
|
newWidth = maxWidth;
|
||||||
|
newHeight = (maxWidth / aspectRatio).round();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img.copyResize(
|
||||||
|
image,
|
||||||
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
|
interpolation: img.Interpolation.linear,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
68
server/cinema/lib/common/lvgl_image_converter.dart
Normal file
68
server/cinema/lib/common/lvgl_image_converter.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
|
||||||
|
import 'image_resizer.dart';
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class LvglImageConverter {
|
||||||
|
final ImageResizer _imageResizer;
|
||||||
|
|
||||||
|
LvglImageConverter(this._imageResizer);
|
||||||
|
static const int _magic = 0x19;
|
||||||
|
static const int _colorFormatRgb565 = 0x12;
|
||||||
|
static const int _flags = 0x00;
|
||||||
|
|
||||||
|
int _toRgb565(int r, int g, int b) {
|
||||||
|
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> convertPngToBin(String imagePath, String binPath) async {
|
||||||
|
final imageFile = File(imagePath);
|
||||||
|
if (!await imageFile.exists()) {
|
||||||
|
throw FileSystemException('Image file not found', imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bytes = await imageFile.readAsBytes();
|
||||||
|
var image = img.decodeImage(bytes);
|
||||||
|
if (image == null) {
|
||||||
|
throw FormatException('Failed to decode image: $imagePath');
|
||||||
|
}
|
||||||
|
|
||||||
|
image = _imageResizer.resizeToFit(image);
|
||||||
|
|
||||||
|
final w = image.width;
|
||||||
|
final h = image.height;
|
||||||
|
final stride = (w * 16 + 7) ~/ 8;
|
||||||
|
|
||||||
|
final header = ByteData(12);
|
||||||
|
header.setUint8(0, _magic);
|
||||||
|
header.setUint8(1, _colorFormatRgb565);
|
||||||
|
header.setUint16(2, _flags, Endian.little);
|
||||||
|
header.setUint16(4, w, Endian.little);
|
||||||
|
header.setUint16(6, h, Endian.little);
|
||||||
|
header.setUint16(8, stride, Endian.little);
|
||||||
|
header.setUint16(10, 0, Endian.little); // reserved
|
||||||
|
|
||||||
|
final body = ByteData(w * h * 2);
|
||||||
|
var offset = 0;
|
||||||
|
for (var y = 0; y < h; y++) {
|
||||||
|
for (var x = 0; x < w; x++) {
|
||||||
|
final pixel = image.getPixel(x, y);
|
||||||
|
final rgb565 = _toRgb565(pixel.r.toInt(), pixel.g.toInt(), pixel.b.toInt());
|
||||||
|
body.setUint16(offset, rgb565, Endian.little);
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final binFile = File(binPath);
|
||||||
|
await binFile.parent.create(recursive: true);
|
||||||
|
await binFile.writeAsBytes([
|
||||||
|
...header.buffer.asUint8List(),
|
||||||
|
...body.buffer.asUint8List(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:cinema/common/dio_module.dart';
|
import 'package:cinema/common/dio_module.dart';
|
||||||
import 'package:cinema/common/domain/serializers.dart';
|
import 'package:cinema/common/domain/serializers.dart';
|
||||||
|
import 'package:cinema/common/lvgl_image_converter.dart';
|
||||||
import 'package:cinema/feature/poster/data/repositories/image_loader.dart';
|
import 'package:cinema/feature/poster/data/repositories/image_loader.dart';
|
||||||
import 'package:cinema/feature/poster/domain/movie.dart';
|
import 'package:cinema/feature/poster/domain/movie.dart';
|
||||||
import 'package:cinema/feature/poster/domain/tmdb_trending_response.dart';
|
import 'package:cinema/feature/poster/domain/tmdb_trending_response.dart';
|
||||||
@@ -12,9 +13,14 @@ import 'package:injectable/injectable.dart';
|
|||||||
class TmDBImageLoader implements ImageLoader {
|
class TmDBImageLoader implements ImageLoader {
|
||||||
final Dio api;
|
final Dio api;
|
||||||
final Dio images;
|
final Dio images;
|
||||||
|
final LvglImageConverter lvglConverter;
|
||||||
final String imageBaseUrl = 'https://image.tmdb.org/t/p/w500';
|
final String imageBaseUrl = 'https://image.tmdb.org/t/p/w500';
|
||||||
|
|
||||||
TmDBImageLoader(@Named(dioAPI) this.api, @Named(dioIMAGES) this.images);
|
TmDBImageLoader(
|
||||||
|
@Named(dioAPI) this.api,
|
||||||
|
@Named(dioIMAGES) this.images,
|
||||||
|
this.lvglConverter,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Movie>> getPosterURIs({String? language = 'de'}) async {
|
Future<List<Movie>> getPosterURIs({String? language = 'de'}) async {
|
||||||
@@ -41,8 +47,8 @@ class TmDBImageLoader implements ImageLoader {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> downloadImages(Movie movie) async {
|
Future<bool> downloadImages(Movie movie) async {
|
||||||
await _downloadImage(movie.poster, "cache/movie/${movie.id}/poster.png");
|
await _downloadImage(movie.poster, "cache/movie/${movie.id}/poster.jpeg");
|
||||||
await _downloadImage(movie.backdrop, "cache/movie/${movie.id}/backdrop.png");
|
await _downloadImage(movie.backdrop, "cache/movie/${movie.id}/backdrop.jpeg");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +61,10 @@ class TmDBImageLoader implements ImageLoader {
|
|||||||
await file.parent.create(recursive: true);
|
await file.parent.create(recursive: true);
|
||||||
final response = await images.get(url, options: Options(responseType: ResponseType.bytes));
|
final response = await images.get(url, options: Options(responseType: ResponseType.bytes));
|
||||||
await file.writeAsBytes(response.data);
|
await file.writeAsBytes(response.data);
|
||||||
|
|
||||||
|
final binFilename = filename.replaceAll('.jpeg', '.bin');
|
||||||
|
await lvglConverter.convertPngToBin(filename, binFilename);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user