diff --git a/lib/util/epd/gdey037z03.dart b/lib/util/epd/gdey037z03.dart index e6af1350..54abc50d 100644 --- a/lib/util/epd/gdey037z03.dart +++ b/lib/util/epd/gdey037z03.dart @@ -25,7 +25,11 @@ class Gdey037z03 extends Epd { get controller => Uc8253() as Driver; Gdey037z03() { - processingMethods.add(ImageProcessing.rwbTriColorDither); - processingMethods.add(ImageProcessing.colorHalftone); + processingMethods.add(ImageProcessing.bwrFloydSteinbergDither); + processingMethods.add(ImageProcessing.bwrFalseFloydSteinbergDither); + processingMethods.add(ImageProcessing.bwrStuckiDither); + processingMethods.add(ImageProcessing.bwrTriColorAtkinsonDither); + processingMethods.add(ImageProcessing.bwrHalftone); + processingMethods.add(ImageProcessing.bwrThreshold); } } diff --git a/lib/util/epd/gdey037z03bw.dart b/lib/util/epd/gdey037z03bw.dart index 17fda93f..c62a72a7 100644 --- a/lib/util/epd/gdey037z03bw.dart +++ b/lib/util/epd/gdey037z03bw.dart @@ -25,7 +25,11 @@ class Gdey037z03BW extends Epd { get controller => Uc8253() as Driver; Gdey037z03BW() { - processingMethods.add(ImageProcessing.binaryDither); - processingMethods.add(ImageProcessing.halftone); + processingMethods.add(ImageProcessing.bwFloydSteinbergDither); + processingMethods.add(ImageProcessing.bwFalseFloydSteinbergDither); + processingMethods.add(ImageProcessing.bwStuckiDither); + processingMethods.add(ImageProcessing.bwAtkinsonDither); + processingMethods.add(ImageProcessing.bwHalftoneDither); + processingMethods.add(ImageProcessing.bwThreshold); } } diff --git a/lib/util/image_processing/image_processing.dart b/lib/util/image_processing/image_processing.dart index 2a16a31c..cef88a1e 100644 --- a/lib/util/image_processing/image_processing.dart +++ b/lib/util/image_processing/image_processing.dart @@ -5,45 +5,82 @@ import 'extract_quantizer.dart'; import 'remap_quantizer.dart'; class ImageProcessing { - static img.Image binaryDither(img.Image orgImg) { + static img.Image bwFloydSteinbergDither(img.Image orgImg) { var image = img.Image.from(orgImg); return img.ditherImage(image, quantizer: img.BinaryQuantizer()); } - static img.Image halftone(img.Image orgImg) { + static img.Image bwFalseFloydSteinbergDither(img.Image orgImg) { + var image = img.Image.from(orgImg); + return img.ditherImage(image, + quantizer: img.BinaryQuantizer(), + kernel: img.DitherKernel.falseFloydSteinberg); + } + + static img.Image bwStuckiDither(img.Image orgImg) { + var image = img.Image.from(orgImg); + return img.ditherImage(image, + quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.stucki); + } + + static img.Image bwAtkinsonDither(img.Image orgImg) { + var image = img.Image.from(orgImg); + return img.ditherImage(image, + quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.atkinson); + } + + static img.Image bwThreshold(img.Image orgImg) { + var image = img.Image.from(orgImg); + return img.ditherImage(image, + quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.none); + } + + static img.Image bwHalftoneDither(img.Image orgImg) { final image = img.Image.from(orgImg); img.grayscale(image); - img.colorHalftone(image); + img.colorHalftone(image, size: 3); return img.ditherImage(image, quantizer: img.BinaryQuantizer()); } - static img.Image colorHalftone(img.Image orgImg) { + static img.Image bwrHalftone(img.Image orgImg) { var image = img.Image.from(orgImg); - // Tri-color palette - final palette = img.PaletteUint8(3, 3); - palette.setRgb(0, 255, 0, 0); // red - palette.setRgb(1, 0, 0, 0); // black - palette.setRgb(2, 255, 255, 255); // white + img.colorHalftone(image, size: 3); + return img.ditherImage(image, + quantizer: RemapQuantizer(palette: _createTriColorPalette()), + kernel: img.DitherKernel.floydSteinberg); + } + + static img.Image bwrFloydSteinbergDither(img.Image orgImg) { + var image = img.Image.from(orgImg); - img.colorHalftone(image); return img.ditherImage(image, - quantizer: RemapQuantizer(palette: palette), + quantizer: RemapQuantizer(palette: _createTriColorPalette()), kernel: img.DitherKernel.floydSteinberg); } - static img.Image rwbTriColorDither(img.Image orgImg) { + static img.Image bwrFalseFloydSteinbergDither(img.Image orgImg) { var image = img.Image.from(orgImg); - // Tri-color palette - final palette = img.PaletteUint8(3, 3); - palette.setRgb(0, 255, 0, 0); // red - palette.setRgb(1, 0, 0, 0); // black - palette.setRgb(2, 255, 255, 255); // white + return img.ditherImage(image, + quantizer: RemapQuantizer(palette: _createTriColorPalette()), + kernel: img.DitherKernel.falseFloydSteinberg); + } + + static img.Image bwrStuckiDither(img.Image orgImg) { + var image = img.Image.from(orgImg); return img.ditherImage(image, - quantizer: RemapQuantizer(palette: palette), - kernel: img.DitherKernel.floydSteinberg); + quantizer: RemapQuantizer(palette: _createTriColorPalette()), + kernel: img.DitherKernel.stucki); + } + + static img.Image bwrTriColorAtkinsonDither(img.Image orgImg) { + var image = img.Image.from(orgImg); + + return img.ditherImage(image, + quantizer: RemapQuantizer(palette: _createTriColorPalette()), + kernel: img.DitherKernel.atkinson); } static img.Image extract(Color toBeExtract, img.Image orgImg) { @@ -54,17 +91,19 @@ class ImageProcessing { kernel: img.DitherKernel.none); } - static img.Image experiment(img.Image orgImg) { + static img.Image bwrThreshold(img.Image orgImg) { var image = img.Image.from(orgImg); - // Tri-color palette - final palette = img.PaletteUint8(3, 3); - palette.setRgb(0, 255, 0, 0); // red - palette.setRgb(1, 0, 0, 0); // black - palette.setRgb(2, 255, 255, 255); // white - return img.ditherImage(image, - quantizer: RemapQuantizer(palette: palette), + quantizer: RemapQuantizer(palette: _createTriColorPalette()), kernel: img.DitherKernel.none); } } + +img.PaletteUint8 _createTriColorPalette() { + final palette = img.PaletteUint8(3, 3); + palette.setRgb(0, 255, 0, 0); // red + palette.setRgb(1, 0, 0, 0); // black + palette.setRgb(2, 255, 255, 255); // white + return palette; +} diff --git a/lib/view/image_editor.dart b/lib/view/image_editor.dart index 691c848f..f8eb54ce 100644 --- a/lib/view/image_editor.dart +++ b/lib/view/image_editor.dart @@ -8,6 +8,7 @@ import 'package:image/image.dart' as img; import 'package:magic_epaper_app/provider/image_loader.dart'; import 'package:magic_epaper_app/util/epd/epd.dart'; +import 'package:magic_epaper_app/constants.dart'; class ImageEditor extends StatelessWidget { final Epd epd; @@ -31,15 +32,50 @@ class ImageEditor extends StatelessWidget { imgList: processedImgs, epd: epd, ); + return Scaffold( + backgroundColor: Colors.white, appBar: AppBar( - title: const Text('Edit Image'), + iconTheme: const IconThemeData( + color: Colors.white, + ), + backgroundColor: colorAccent, + elevation: 0, + title: const Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0), + child: Text( + 'Select Your Filter', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + toolbarHeight: 85, actions: [ - TextButton( - onPressed: () { - imgLoader.pickImage(width: epd.width, height: epd.height); - }, - child: const Text("Import Image"), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.white.withValues(alpha: 0.2), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: () { + imgLoader.pickImage(width: epd.width, height: epd.height); + }, + child: const Text( + "Import Image", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), ), TextButton( onPressed: () async { @@ -58,7 +94,12 @@ class ImageEditor extends StatelessWidget { ), ], ), - body: Center(child: imgList), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: imgList, + ), + ), ); } } diff --git a/lib/view/widget/image_list.dart b/lib/view/widget/image_list.dart index 02b85af4..e662f220 100644 --- a/lib/view/widget/image_list.dart +++ b/lib/view/widget/image_list.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:image/image.dart' as img; import 'package:magic_epaper_app/util/epd/epd.dart'; import 'package:magic_epaper_app/util/protocol.dart'; +import 'package:magic_epaper_app/util/image_processing/image_processing.dart'; +import 'package:magic_epaper_app/constants.dart'; class ImageList extends StatefulWidget { final List imgList; @@ -17,43 +19,191 @@ class ImageList extends StatefulWidget { class _ImageList extends State { int imgSelection = 0; + Map getFilterName() { + return { + ImageProcessing.bwFloydSteinbergDither: 'Floyd-Steinberg', + ImageProcessing.bwFalseFloydSteinbergDither: 'False Floyd-Steinberg', + ImageProcessing.bwStuckiDither: 'Stucki', + ImageProcessing.bwAtkinsonDither: 'Atkinson', + ImageProcessing.bwThreshold: 'Threshold', + ImageProcessing.bwHalftoneDither: 'Halftone', + ImageProcessing.bwrHalftone: 'Color Halftone', + ImageProcessing.bwrFloydSteinbergDither: 'BWR Floyd-Steinberg', + ImageProcessing.bwrFalseFloydSteinbergDither: 'BWR False Floyd-Steinberg', + ImageProcessing.bwrStuckiDither: 'BWR Stucki', + ImageProcessing.bwrTriColorAtkinsonDither: 'BWR Atkinson', + ImageProcessing.bwrThreshold: 'Threshold', + }; + } + + String getFilterNameByIndex(int index) { + var methods = widget.epd.processingMethods; + if (index < 0 || index >= methods.length) return "Unknown"; + + var filterMap = getFilterName(); + return filterMap[methods[index]] ?? "Unknown"; + } + @override Widget build(BuildContext context) { List imgWidgets = List.empty(growable: true); + List filterCards = List.empty(growable: true); if (widget.imgList.isEmpty) { - return const Text("Please import an image to continue!"); + return const Center( + child: Text( + "Please import an image to continue!", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + ); } - for (var i in widget.imgList) { - var rotatedImg = img.copyRotate(i, angle: 90); - var uiImage = Image.memory(img.encodePng(rotatedImg), - height: 100, isAntiAlias: false); + for (var i = 0; i < widget.imgList.length; i++) { + var rotatedImg = img.copyRotate(widget.imgList[i], angle: 90); + var uiImage = Image.memory( + img.encodePng(rotatedImg), + height: 100, + isAntiAlias: false, + ); imgWidgets.add(uiImage); + + filterCards.add( + GestureDetector( + onTap: () { + setState(() { + imgSelection = i; + }); + }, + child: Card( + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + elevation: imgSelection == i ? 3 : 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: imgSelection == i ? colorPrimary : Colors.grey.shade300, + width: imgSelection == i ? 2 : 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + getFilterNameByIndex(i), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: + imgSelection == i ? colorPrimary : Colors.black87, + ), + ), + ), + ), + Expanded( + flex: 3, + child: imgWidgets[i], + ), + ], + ), + ), + ), + ), + ); } - return Column( + Widget previewWidget = Column( children: [ - for (int index = 0; index < imgWidgets.length; index++) - ListTile( - title: imgWidgets[index], - leading: Radio( - value: index, - groupValue: imgSelection, - onChanged: (int? value) { - setState(() { - imgSelection = value!; - }); - }, + const Padding( + padding: EdgeInsets.only(top: 12.0, bottom: 8.0), + child: Text( + "Preview", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, ), ), - Expanded(child: Container()), - ElevatedButton( - onPressed: () { - Protocol(epd: widget.epd).writeImages(widget.imgList[imgSelection]); - }, - child: const Text('Start Transfer'), - ) + ), + Container( + width: double.infinity, + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + child: imgSelection < imgWidgets.length + ? Image.memory( + img.encodePng( + img.copyRotate(widget.imgList[imgSelection], angle: 90)), + fit: BoxFit.contain, + ) + : const Center(child: Text("No filter selected")), + ), + const SizedBox(height: 16), + const Divider(), + ], + ); + + return Stack( + children: [ + Column( + children: [ + previewWidget, + Expanded( + child: ListView( + padding: const EdgeInsets.only(bottom: 80), + children: filterCards, + ), + ), + ], + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, -2), + ), + ], + ), + padding: const EdgeInsets.all(16), + child: SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: colorPrimary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + onPressed: () { + Protocol(epd: widget.epd) + .writeImages(widget.imgList[imgSelection]); + }, + child: const Text( + 'Start Transfer', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), ], ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 14b5f7c7..02b89ac5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import app_settings import file_selector_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 48db45a0..7d8235ec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + app_settings: + dependency: "direct main" + description: + name: app_settings + sha256: "3e46c561441e5820d3a25339bf8b51b9e45a5f686873851a20c257a530917795" + url: "https://pub.dev" + source: hosted + version: "6.1.1" archive: dependency: transitive description: @@ -197,6 +205,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" http: dependency: transitive description: