diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c074bd..cf0b973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.0.2 + +- `PCanvasEvent` now is abstract: + - `PCanvasClickEvent`: for mouse and touch events. + - `PCanvasKeyEvent`: for keyboatd events. +- `PCanvasPainter`: + - Added support to key events +- `PCanvas`: + - Added support to fill gradient operations. +- Added `PCanvasElement` + - Base class for personalized canvas elements. + ## 1.0.1 - Added interface `WithDimension` to `PDimension` and `PCanvas`. diff --git a/example/pcanvas_example_web/web/main.dart b/example/pcanvas_example_web/web/main.dart index a2c4f92..3543f48 100644 --- a/example/pcanvas_example_web/web/main.dart +++ b/example/pcanvas_example_web/web/main.dart @@ -137,6 +137,10 @@ class MyCanvasPainter extends PCanvasPainter { pCanvas.strokePath(path, PStyle(color: PColor.colorRed, size: 3), closePath: true); + // Fill the right side of the canvas with a linear gradient: + pCanvas.fillRightLeftGradient(pCanvas.width ~/ 2, 0, pCanvas.width ~/ 2, + pCanvas.height, PColorRGB(0, 32, 94), PColor.colorBlack); + return true; } diff --git a/lib/pcanvas.dart b/lib/pcanvas.dart index a9fa2d7..a51c85f 100644 --- a/lib/pcanvas.dart +++ b/lib/pcanvas.dart @@ -2,3 +2,4 @@ library pcanvas; export 'src/pcanvas_base.dart'; +export 'src/pcanvas_element.dart'; diff --git a/lib/src/pcanvas_base.dart b/lib/src/pcanvas_base.dart index c56668c..5c1c83a 100644 --- a/lib/src/pcanvas_base.dart +++ b/lib/src/pcanvas_base.dart @@ -1,39 +1,127 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; +import 'dart:math' as math; + +import 'package:pcanvas/pcanvas.dart'; + +import 'pcanvas_element.dart'; import 'pcanvas_impl_bitmap.dart' if (dart.library.html) 'pcanvas_impl_html.dart'; /// A [PCanvas] event. -/// See [PCanvas.onClick]. -class PCanvasEvent { +/// +/// See [PCanvasClickEvent]. +abstract class PCanvasEvent { /// The event type. final String type; + const PCanvasEvent(this.type); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PCanvasEvent && + runtimeType == other.runtimeType && + type == other.type; + + @override + int get hashCode => type.hashCode; +} + +/// A [PCanvas] click event. +/// See [PCanvas.onClick]. +class PCanvasClickEvent extends PCanvasEvent { /// The event X coordinate. final num x; /// The event Y coordinate. final num y; - const PCanvasEvent(this.type, this.x, this.y); + const PCanvasClickEvent(super.type, this.x, this.y) : super(); @override bool operator ==(Object other) => identical(this, other) || - other is PCanvasEvent && + super == other && + other is PCanvasClickEvent && runtimeType == other.runtimeType && - type == other.type && x == other.x && y == other.y; @override - int get hashCode => type.hashCode ^ x.hashCode ^ y.hashCode; + int get hashCode => super.hashCode ^ x.hashCode ^ y.hashCode; + + @override + String toString() { + return 'PCanvasClickEvent{type: $type, x: $x, y: $y}'; + } +} + +/// A [PCanvas] key event. +/// See [PCanvas.onKey]. +class PCanvasKeyEvent extends PCanvasEvent { + /// The Unicode value of the key: + final int charCode; + + /// The code of the key (the name of the key). + final String? code; + + /// The key value. + final String? key; + + /// Whether the "CTRL" key was pressed. + final bool ctrlKey; + + /// Whether the "ALT" key was pressed. + final bool altKey; + + /// Whether the "SHIFT" key was pressed. + final bool shiftKey; + + /// Whether the "META" key was pressed. + final bool metaKey; + + const PCanvasKeyEvent(super.type, this.charCode, this.code, this.key, + this.ctrlKey, this.altKey, this.shiftKey, this.metaKey) + : super(); + + @override + bool operator ==(Object other) => + identical(this, other) || + super == other && + other is PCanvasKeyEvent && + runtimeType == other.runtimeType && + charCode == other.charCode && + code == other.code && + key == other.key && + ctrlKey == other.ctrlKey && + altKey == other.altKey && + shiftKey == other.shiftKey && + metaKey == other.metaKey; + + @override + int get hashCode => + super.hashCode ^ + charCode.hashCode ^ + code.hashCode ^ + key.hashCode ^ + ctrlKey.hashCode ^ + altKey.hashCode ^ + shiftKey.hashCode ^ + metaKey.hashCode; @override String toString() { - return 'PCanvasEvent{type: $type, x: $x, y: $y}'; + var extra = [ + if (shiftKey) 'SHIFT', + if (ctrlKey) 'CTRL', + if (altKey) 'ALT', + if (metaKey) 'META', + ]; + return 'PCanvasKeyEvent{type: $type, key: <$key>, charCode: $charCode, code: <$code>}${extra.isNotEmpty ? '$extra' : ''}'; } } @@ -130,19 +218,39 @@ abstract class PCanvasPainter { return true; } + /// Paint the [elements]. + FutureOr paintElements( + PCanvas pCanvas, List elements, bool posPaint) { + for (var e in elements) { + e.paint(pCanvas); + } + return true; + } + /// The paint operations. FutureOr paint(PCanvas pCanvas); /// Canvas `onClickDown` handler. - void onClickDown(PCanvasEvent event) {} + void onClickDown(PCanvasClickEvent event) {} /// Canvas `onClickUp` handler. - void onClickUp(PCanvasEvent event) {} + void onClickUp(PCanvasClickEvent event) {} /// Canvas `onClick` handler. - void onClick(PCanvasEvent event) {} + void onClick(PCanvasClickEvent event) {} + + /// Canvas `onKeyDown` handler. + void onKeyDown(PCanvasKeyEvent event) {} + + /// Canvas `onKeyUp` handler. + void onKeyUp(PCanvasKeyEvent event) {} + + /// Canvas `onKey` handler. + void onKey(PCanvasKeyEvent event) {} } +typedef PaintFuntion = FutureOr Function(PCanvas pCanvas); + /// Portable Canvas. abstract class PCanvas with WithDimension { /// The painter of this canvas. @@ -170,6 +278,34 @@ abstract class PCanvas with WithDimension { return createPCanvasImpl(width, height, painter); } + final List _elements = []; + + List get elements => + UnmodifiableListView(_elements); + + bool get hasElements => _elements.isNotEmpty; + + void clearElements() { + if (_elements.isNotEmpty) { + _elements.clear(); + requestRepaint(); + } + } + + void addElement(PCanvasElement element) { + _elements.add(element); + _elements.sortByZIndex(); + requestRepaint(); + } + + bool removeElement(PCanvasElement element) { + var rm = _elements.remove(element); + if (rm) { + requestRepaint(); + } + return rm; + } + /// Waits the loading of the canvas and also the [painter.loadResources]. FutureOr waitLoading(); @@ -217,29 +353,86 @@ abstract class PCanvas with WithDimension { checkDimension(); try { + final painter = this.painter; + + onPrePaint(); + painter.clear(this); - FutureOr ret; - if (painter.isLoadingResources) { - ret = painter.paintLoading(this); - } else { - ret = painter.paint(this); - } + final ret = painter.isLoadingResources + ? _callPainterLoading() + : _callPainterImpl(); if (ret is Future) { return ret.whenComplete(() { _painting = false; + onPosPaint(); }); } else { _painting = false; + onPosPaint(); return ret; } } catch (e) { _painting = false; + onPosPaint(); rethrow; } } + FutureOr _callPainterLoading() { + return painter.paintLoading(this); + } + + FutureOr _callPainterImpl() { + var hasElements = _elements.isNotEmpty; + + List? elementsPrev; + List? elementsPos; + + if (hasElements) { + elementsPrev = _elements.where((e) { + var zIndex = e.zIndex; + return zIndex != null && zIndex < 0; + }).toList(); + + elementsPos = _elements.where((e) { + var zIndex = e.zIndex; + return zIndex == null || zIndex >= 0; + }).toList(); + } + + final painter = this.painter; + + FutureOr ret = true; + + if (elementsPrev != null) { + ret = painter.paintElements(this, elementsPrev, false); + } + + if (ret is Future) { + ret = ret.then((_) => painter.paint(this)); + } else { + ret = painter.paint(this); + } + + if (elementsPos != null) { + if (ret is Future) { + ret = ret.then((_) => painter.paintElements(this, elementsPos!, true)); + } else { + ret = painter.paintElements(this, elementsPos, true); + } + } + + return ret; + } + + void onPrePaint() {} + + void onPosPaint() {} + + Future requestRepaint(); + /// Refreshes the canvas asynchronously. Future refresh() => Future.microtask(callPainter); @@ -324,6 +517,28 @@ abstract class PCanvas with WithDimension { /// Fill a rectangle ([x],[y] , [width] x [height]). void fillRect(num x, num y, num width, num height, PStyle style); + /// Fill a rectangle ([x],[y] , [width] x [height]) with a top down linear gradient. + /// See [fillBottomUpGradient]. + void fillTopDownGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo); + + /// Fill a rectangle ([x],[y] , [width] x [height]) with a bottom up linear gradient. + /// See [fillTopDownGradient]. + void fillBottomUpGradient(num x, num y, num width, num height, + PColor colorFrom, PColor colorTo) => + fillTopDownGradient(x, y, width, height, colorTo, colorFrom); + + /// Fill a rectangle ([x],[y] , [width] x [height]) with a left right linear gradient. + /// See [fillRightLeftGradient]. + void fillLeftRightGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo); + + /// Fill a rectangle ([x],[y] , [width] x [height]) with a right left linear gradient. + /// See [fillLeftRightGradient]. + void fillRightLeftGradient(num x, num y, num width, num height, + PColor colorFrom, PColor colorTo) => + fillLeftRightGradient(x, y, width, height, colorTo, colorFrom); + /// Measure the [text] dimension. PTextMetric measureText(String text, PFont font); @@ -437,12 +652,18 @@ abstract class PCanvasPixels { /// Formats [color] to this instance [format]. int formatColor(PColor color); + /// Parse [pixel] to [PColor]; + PColorRGB parseColor(int pixel); + /// Index of a pixel ([x],[y]) at [pixels]. int pixelIndex(int x, int y) => (width * y) + x; /// Returns a pixel at ([x],[y]) in the format 4-byte Uint32 integer in #AABBGGRR channel order. int pixel(int x, int y) => pixels[pixelIndex(x, y)]; + /// Returns a pixel at ([x],[y]) as [PColor]. + PColorRGB pixelColor(int x, int y) => parseColor(pixel(x, y)); + /// Returns the Red channel of [pixel] at ([x],[y]). int pixelR(int x, int y); @@ -477,6 +698,9 @@ class PCanvasPixelsARGB extends PCanvasPixels { @override int formatColor(PColor color) => color.argb; + @override + PColorRGB parseColor(int pixel) => PColorRGBA.fromARGB(pixel); + /// Returns the Alpha channel of [pixel] at ([x],[y]). @override int pixelA(int x, int y) => ((pixel(x, y) >> 24) & 0xff); @@ -533,6 +757,9 @@ class PCanvasPixelsABGR extends PCanvasPixels { @override int formatColor(PColor color) => color.abgr; + @override + PColorRGB parseColor(int pixel) => PColorRGBA.fromABGR(pixel); + /// Returns the Alpha channel of [pixel] at ([x],[y]). @override int pixelA(int x, int y) => ((pixel(x, y) >> 24) & 0xff); @@ -589,6 +816,9 @@ class PCanvasPixelsRGBA extends PCanvasPixels { @override int formatColor(PColor color) => color.rgba; + @override + PColorRGB parseColor(int pixel) => PColorRGBA.fromRGBA(pixel); + /// Returns the Red channel of [pixel] at ([x],[y]). @override int pixelR(int x, int y) => ((pixel(x, y) >> 24) & 0xff); @@ -702,6 +932,12 @@ abstract class PColor { /// Returns `true` if this color has alpha. bool get hasAlpha; + /// Converts this intances to a [PColorRGB]. + PColorRGB toPColorRGB(); + + /// Converts this intances to a [PColorRGBA]. + PColorRGBA toPColorRGBA(); + PColorRGB copyWith({int? r, int? g, int? b, double? alpha}); /// Converts to the `RGB` format. @@ -743,10 +979,46 @@ class PColorRGB extends PColor { g = g.clamp(0, 255), b = b.clamp(0, 255); + PColorRGB.fromRGB(int p) + : this( + (p >> 16) & 0xff, + (p >> 8) & 0xff, + (p) & 0xff, + ); + + PColorRGB.fromRGBA(int p) + : this( + (p >> 24) & 0xff, + (p >> 16) & 0xff, + (p >> 8) & 0xff, + ); + + PColorRGB.fromBGR(int p) + : this( + (p) & 0xff, + (p >> 8) & 0xff, + (p >> 16) & 0xff, + ); + @override bool get hasAlpha => false; - String? _rgb; + int get a => 1; + + int maxDistance(PColorRGB other) { + var rd = (r - other.r).abs(); + var gd = (g - other.g).abs(); + var bd = (b - other.b).abs(); + var ad = (a - other.a).abs(); + + return math.max(rd, math.max(gd, math.max(bd, ad))); + } + + @override + PColorRGB toPColorRGB() => this; + + @override + PColorRGBA toPColorRGBA() => PColorRGBA(r, g, b, 1); @override PColorRGB copyWith({int? r, int? g, int? b, double? alpha}) { @@ -757,6 +1029,8 @@ class PColorRGB extends PColor { } } + String? _rgb; + @override String toRGB() => _rgb ??= 'rgb($r,$g,$b)'; @@ -792,19 +1066,50 @@ class PColorRGBA extends PColorRGB { PColorRGBA(super.r, super.g, super.b, double a) : alpha = ((a.clamp(0, 1) * 10000).toInt() / 10000); + PColorRGBA.fromARGB(int p) + : this( + (p >> 16) & 0xff, + (p >> 8) & 0xff, + (p) & 0xff, + ((p >> 24) & 0xff) / 255, + ); + + PColorRGBA.fromABGR(int p) + : this( + (p) & 0xff, + (p >> 8) & 0xff, + (p >> 16) & 0xff, + ((p >> 24) & 0xff) / 255, + ); + + PColorRGBA.fromRGBA(int p) + : this( + (p >> 24) & 0xff, + (p >> 16) & 0xff, + (p >> 8) & 0xff, + ((p) & 0xff) / 255, + ); + @override bool get hasAlpha => alpha != 1.0; int? _a; + @override int get a => _a ??= (alpha * 255).toInt(); - String? _rgba; + @override + PColorRGB toPColorRGB() => PColorRGB(r, g, b); + + @override + PColorRGBA toPColorRGBA() => this; @override PColorRGB copyWith({int? r, int? g, int? b, double? alpha}) => PColorRGBA(r ?? this.r, g ?? this.g, b ?? this.b, alpha ?? this.alpha); + String? _rgba; + @override String toRGBA() => _rgba ??= 'rgba($r,$g,$b,$alpha)'; @@ -908,6 +1213,12 @@ class PRectangle extends PDimension { PRectangle(this.x, this.y, super.width, super.height); + PRectangle.fromDimension(num x, num y, PDimension dimension) + : this(x, y, dimension.width, dimension.height); + + PRectangle copyWith({num? x, num? y, num? width, num? height}) => PRectangle( + x ?? this.x, y ?? this.y, width ?? this.width, height ?? this.height); + @override PRectangle get dimension => this; diff --git a/lib/src/pcanvas_bitmap.dart b/lib/src/pcanvas_bitmap.dart index d57f0c6..a93fb97 100644 --- a/lib/src/pcanvas_bitmap.dart +++ b/lib/src/pcanvas_bitmap.dart @@ -3,9 +3,9 @@ import 'dart:math' as math; import 'dart:typed_data'; import 'package:dio/dio.dart'; +import 'package:image/image.dart' as img; import 'pcanvas_base.dart'; -import 'package:image/image.dart' as img; /// An in-memory [PCanvas] implementation. class PCanvasBitmap extends PCanvas { @@ -51,6 +51,21 @@ class PCanvasBitmap extends PCanvas { print(o); } + Future? _requestedPaint; + + @override + Future requestRepaint() { + var requestedPaint = _requestedPaint; + if (requestedPaint != null) return requestedPaint; + + return _requestedPaint = refresh(); + } + + @override + void onPosPaint() { + _requestedPaint = null; + } + @override Object get canvasNative => _bitmap; @@ -176,6 +191,126 @@ class PCanvasBitmap extends PCanvas { (y + height).toInt(), color.rgba32); } + @override + void fillTopDownGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo) { + var cFrom = colorFrom.toPColorRGBA(); + var cTo = colorTo.toPColorRGBA(); + + var r1 = cFrom.r; + var g1 = cFrom.g; + var b1 = cFrom.b; + var a1 = cFrom.alpha; + + var r2 = cTo.r; + var g2 = cTo.g; + var b2 = cTo.b; + var a2 = cTo.alpha; + + var rd = r2 - r1; + var gd = g2 - g1; + var bd = b2 - b1; + var ad = a2 - a1; + + var w = width; + var h = height; + + var steps = 256; + + var s = h ~/ steps; + while (s == 0 && steps > 1) { + --steps; + s = h ~/ steps; + } + + var end = (h ~/ s) - 1; + + if (a1 == 1 && a1 == a2) { + for (var y = 0; y < h; y += s) { + var ratio = y / end; + + var r = (r1 + rd * ratio).toInt(); + var g = (g1 + gd * ratio).toInt(); + var b = (b1 + bd * ratio).toInt(); + + var c = PColorRGB(r, g, b); + fillRect(0, y, w, s, PStyle(color: c)); + } + } else { + for (var y = 0; y < h; y += s) { + var ratio = y / end; + + var r = (r1 + rd * ratio).toInt(); + var g = (g1 + gd * ratio).toInt(); + var b = (b1 + bd * ratio).toInt(); + var a = (a1 + ad * ratio); + + var c = PColorRGBA(r, g, b, a); + fillRect(x, y, w, s, PStyle(color: c)); + } + } + } + + @override + void fillLeftRightGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo) { + var cFrom = colorFrom.toPColorRGBA(); + var cTo = colorTo.toPColorRGBA(); + + var r1 = cFrom.r; + var g1 = cFrom.g; + var b1 = cFrom.b; + var a1 = cFrom.alpha; + + var r2 = cTo.r; + var g2 = cTo.g; + var b2 = cTo.b; + var a2 = cTo.alpha; + + var rd = r2 - r1; + var gd = g2 - g1; + var bd = b2 - b1; + var ad = a2 - a1; + + var w = width; + var h = height; + + var steps = 256; + + var s = w ~/ steps; + while (s == 0 && steps > 1) { + --steps; + s = w ~/ steps; + } + + var end = (w ~/ s) - 1; + + if (a1 == 1 && a1 == a2) { + for (var x = 0; x < w; x += s) { + var ratio = x / end; + + var r = (r1 + rd * ratio).toInt(); + var g = (g1 + gd * ratio).toInt(); + var b = (b1 + bd * ratio).toInt(); + + var c = PColorRGB(r, g, b); + fillRect(x, y, s, h, PStyle(color: c)); + } + } else { + for (var x = 0; x < w; x += s) { + var ratio = x / end; + + var r = (r1 + rd * ratio).toInt(); + var g = (g1 + gd * ratio).toInt(); + var b = (b1 + bd * ratio).toInt(); + var a = (a1 + ad * ratio); + + var c = PColorRGBA(r, g, b, a); + fillRect(x, y, s, h, PStyle(color: c)); + } + } + } + @override PTextMetric measureText(String text, PFont font) { var bitmapFont = font.toBitmapFont(); diff --git a/lib/src/pcanvas_element.dart b/lib/src/pcanvas_element.dart new file mode 100644 index 0000000..89730ac --- /dev/null +++ b/lib/src/pcanvas_element.dart @@ -0,0 +1,22 @@ +import 'pcanvas_base.dart'; + +/// A base class for [PCanvas] elements. +abstract class PCanvasElement { + /// The bounding box of this element. + PRectangle getBoundingBox(PCanvas pCanvas); + + /// The Z index of this element. + int? get zIndex; + + /// The paint operation of this element. + void paint(PCanvas pCanvas); +} + +extension PCanvasElementExtension on List { + /// Sorts the list by [PCanvasElement.zIndex]. + void sortByZIndex() => sort((a, b) { + var z1 = a.zIndex ?? 0; + var z2 = b.zIndex ?? 0; + return z1.compareTo(z2); + }); +} diff --git a/lib/src/pcanvas_html.dart b/lib/src/pcanvas_html.dart index fad722c..32f015d 100644 --- a/lib/src/pcanvas_html.dart +++ b/lib/src/pcanvas_html.dart @@ -53,6 +53,10 @@ class PCanvasHTML extends PCanvas { _canvas.onMouseUp.listen(_onMouseUp); _canvas.onClick.listen(_onClick); + window.onKeyDown.listen(_onKeyDown); + window.onKeyUp.listen(_onKeyUp); + window.onKeyPress.listen(_onKey); + _canvasRelations[_canvas] = this; _resizeObserver.observe(_canvas); @@ -135,6 +139,21 @@ class PCanvasHTML extends PCanvas { } } + Future? _requestedPaint; + + @override + Future requestRepaint() { + var requestedPaint = _requestedPaint; + if (requestedPaint != null) return requestedPaint; + + return _requestedPaint = refresh(); + } + + @override + void onPosPaint() { + _requestedPaint = null; + } + void _onMouseDown(MouseEvent mEvent) => painter.onClickDown(mEvent.toEvent('onMouseDown')); @@ -144,6 +163,14 @@ class PCanvasHTML extends PCanvas { void _onClick(MouseEvent mEvent) => painter.onClick(mEvent.toEvent('onClick')); + void _onKeyDown(KeyboardEvent kEvent) => + painter.onKeyDown(kEvent.toEvent('onKeyDown')); + + void _onKeyUp(KeyboardEvent kEvent) => + painter.onKeyUp(kEvent.toEvent('onKeyUp')); + + void _onKey(KeyboardEvent kEvent) => painter.onKey(kEvent.toEvent('onKey')); + @override CanvasElement get canvasNative => _canvas; @@ -264,6 +291,28 @@ class PCanvasHTML extends PCanvas { _ctx.fillRect(x, y, width, height); } + @override + void fillTopDownGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo) { + var grd = _ctx.createLinearGradient(x, y, x, y + height); + grd.addColorStop(0, colorFrom.toString()); + grd.addColorStop(1, colorTo.toString()); + + _setFillStyleGradient(grd); + _ctx.fillRect(x, y, x + width, y + height); + } + + @override + void fillLeftRightGradient( + num x, num y, num width, num height, PColor colorFrom, PColor colorTo) { + var grd = _ctx.createLinearGradient(x, y, x + width, y); + grd.addColorStop(0, colorFrom.toString()); + grd.addColorStop(1, colorTo.toString()); + + _setFillStyleGradient(grd); + _ctx.fillRect(x, y, x + width, y + height); + } + @override PTextMetric measureText(String text, PFont font, {num? pixelRatio}) { var m = _measureTextImpl(text, font); @@ -399,6 +448,11 @@ class PCanvasHTML extends PCanvas { _lastFillStyle = style; } + void _setFillStyleGradient(CanvasGradient grd) { + _ctx.fillStyle = grd; + _lastFillStyle = PStyle(); + } + void _clearSetStates() { _lastFont = PFont('', 0); _lastFontPixelRatio = 0; @@ -497,8 +551,15 @@ class _PCanvasImageElement extends PCanvasImage { } extension _MouseEventExtension on MouseEvent { - PCanvasEvent toEvent(String type) { + PCanvasClickEvent toEvent(String type) { var point = offset; - return PCanvasEvent(type, point.x, point.y); + return PCanvasClickEvent(type, point.x, point.y); + } +} + +extension _KeyboardEventExtension on KeyboardEvent { + PCanvasKeyEvent toEvent(String type) { + return PCanvasKeyEvent( + type, charCode, code, key, ctrlKey, altKey, shiftKey, metaKey); } } diff --git a/pubspec.yaml b/pubspec.yaml index 136f28f..e4c3f51 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: pcanvas description: A portable canvas that can work in many platforms (Flutter, Web, Desktop, in-memory Image). -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/gmpassos/pcanvas environment: diff --git a/test/pcanvas_test.dart b/test/pcanvas_test.dart index f496a0d..d14f549 100644 --- a/test/pcanvas_test.dart +++ b/test/pcanvas_test.dart @@ -199,6 +199,72 @@ void main() { test('pixelsRect[blue]', () => testCanvasPixelsRect(MyPainterRect2(), PColor.colorBlue)); + + test('Gradient: top-down', () async { + var pCanvas = PCanvas(3, 100, MyPainterGradient1()); + + print(pCanvas); + + await pCanvas.waitLoading(); + pCanvas.callPainter(); + + expect(pCanvas.painter.isLoadingResources, isFalse); + + expect(pCanvas.width, equals(3)); + expect(pCanvas.height, equals(100)); + + var pixels = await pCanvas.pixels; + + print(pixels); + + expect(pixels.length, equals(3 * 100)); + + expect(pixels.pixelColor(0, 0).maxDistance(PColorRGBA(0, 0, 0, 1)), + inInclusiveRange(0, 3)); + + expect(pixels.pixelColor(2, 0).maxDistance(PColorRGBA(0, 0, 0, 1)), + inInclusiveRange(0, 3)); + + expect(pixels.pixelColor(0, 99).maxDistance(PColorRGBA(255, 255, 255, 1)), + inInclusiveRange(0, 3)); + + expect(pixels.pixelColor(2, 99).maxDistance(PColorRGBA(255, 255, 255, 1)), + inInclusiveRange(0, 3)); + }); + + test('Gradient: left-right', () async { + var pCanvas = PCanvas(512, 3, MyPainterGradient2()); + + print(pCanvas); + + await pCanvas.waitLoading(); + pCanvas.callPainter(); + + expect(pCanvas.painter.isLoadingResources, isFalse); + + expect(pCanvas.width, equals(512)); + expect(pCanvas.height, equals(3)); + + var pixels = await pCanvas.pixels; + + print(pixels); + + expect(pixels.length, equals(3 * 512)); + + expect(pixels.pixelColor(0, 0).maxDistance(PColorRGBA(0, 0, 0, 1)), + inInclusiveRange(0, 3)); + + expect(pixels.pixelColor(0, 2).maxDistance(PColorRGBA(0, 0, 0, 1)), + inInclusiveRange(0, 3)); + + expect( + pixels.pixelColor(511, 0).maxDistance(PColorRGBA(255, 255, 255, 1)), + inInclusiveRange(0, 3)); + + expect( + pixels.pixelColor(511, 2).maxDistance(PColorRGBA(255, 255, 255, 1)), + inInclusiveRange(0, 3)); + }); }); } @@ -239,3 +305,23 @@ class MyPainterRect2 extends PCanvasPainter { return true; } } + +class MyPainterGradient1 extends PCanvasPainter { + @override + FutureOr paint(PCanvas pCanvas) { + pCanvas.fillTopDownGradient(0, 0, pCanvas.width, pCanvas.height, + PColorRGB(0, 0, 0), PColorRGB(255, 255, 255)); + + return true; + } +} + +class MyPainterGradient2 extends PCanvasPainter { + @override + FutureOr paint(PCanvas pCanvas) { + pCanvas.fillLeftRightGradient(0, 0, pCanvas.width, pCanvas.height, + PColorRGB(0, 0, 0), PColorRGB(255, 255, 255)); + + return true; + } +}