Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/vector_graphics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 1.2.3

* Adds support for SVG filters (blur, offset, feMerge) and mask-type=alpha.
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.

## 1.2.2
Expand Down
9 changes: 9 additions & 0 deletions packages/vector_graphics/lib/src/listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,15 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
_paints.add(paint);
}

@override
void onPaintBlur(int paintId, double sigmaX, double sigmaY) {
final double clampedX = sigmaX < 0.0 ? 0.0 : sigmaX;
final double clampedY = sigmaY < 0.0 ? 0.0 : sigmaY;
if (clampedX > 0.0 || clampedY > 0.0) {
_paints[paintId].imageFilter = ImageFilter.blur(sigmaX: clampedX, sigmaY: clampedY);
}
}
Comment thread
edwin-hollen marked this conversation as resolved.
Comment thread
edwin-hollen marked this conversation as resolved.
Comment thread
edwin-hollen marked this conversation as resolved.
Comment thread
edwin-hollen marked this conversation as resolved.
Comment thread
edwin-hollen marked this conversation as resolved.

@override
void onPathClose() {
_currentPath!.close();
Expand Down
4 changes: 2 additions & 2 deletions packages/vector_graphics/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: vector_graphics
description: A vector graphics rendering package for Flutter using a binary encoding.
repository: https://github.com/flutter/packages/tree/main/packages/vector_graphics
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+vector_graphics%22
version: 1.2.2
version: 1.2.3

environment:
sdk: ^3.10.0
Expand All @@ -12,7 +12,7 @@ dependencies:
flutter:
sdk: flutter
http: ^1.0.0
vector_graphics_codec: ^1.1.11+1
vector_graphics_codec: ^1.1.14

dev_dependencies:
flutter_test:
Expand Down
150 changes: 150 additions & 0 deletions packages/vector_graphics/test/listener_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,156 @@ void main() {
await listener.waitForImageDecode();
expect(() => listener.onDrawImage(2, 10, 10, 100, 100, null), throwsAssertionError);
});

test('Paint blur is applied to paint', () async {
final factory = TestPictureFactory();
final listener = FlutterVectorGraphicsListener(pictureFactory: factory);

listener.onPaintObject(
color: const ui.Color(0xff000000).toARGB32(),
strokeCap: null,
strokeJoin: null,
blendMode: BlendMode.srcIn.index,
strokeMiterLimit: null,
strokeWidth: null,
paintStyle: ui.PaintingStyle.fill.index,
id: 0,
shaderId: null,
);

listener.onPaintBlur(0, 5.0, 10.0);

listener.onPathStart(0, 0);
listener.onPathMoveTo(0, 0);
listener.onPathLineTo(10, 10);
listener.onPathFinished();

await listener.onDrawPath(0, 0, null);

final Invocation drawPath = factory.fakeCanvases.single.invocations.single;
expect(drawPath.isMethod, true);
expect(drawPath.memberName, #drawPath);
final paint = drawPath.positionalArguments[1] as ui.Paint;
expect(paint.imageFilter, isNotNull);
expect(paint.imageFilter, ui.ImageFilter.blur(sigmaX: 5.0, sigmaY: 10.0));
});

test('onPaintBlur with 0.0 standard deviations does not set imageFilter', () async {
final TestPictureFactory factory = TestPictureFactory();
final FlutterVectorGraphicsListener listener = FlutterVectorGraphicsListener(
id: 0,
locale: null,
textDirection: null,
clipViewbox: true,
pictureFactory: factory,
);

listener.onPaintObject(
color: const ui.Color(0xff000000).toARGB32(),
strokeCap: null,
strokeJoin: null,
blendMode: BlendMode.srcIn.index,
strokeMiterLimit: null,
strokeWidth: null,
paintStyle: ui.PaintingStyle.fill.index,
id: 0,
shaderId: null,
);

listener.onPaintBlur(0, 0.0, 0.0);

listener.onPathStart(0, 0);
listener.onPathMoveTo(0, 0);
listener.onPathLineTo(10, 10);
listener.onPathFinished();

await listener.onDrawPath(0, 0, null);

final Invocation drawPath = factory.fakeCanvases.single.invocations.single;
expect(drawPath.isMethod, true);
expect(drawPath.memberName, #drawPath);
final paint = drawPath.positionalArguments[1] as ui.Paint;
expect(paint.imageFilter, isNull);
});

test('onPaintBlur with negative standard deviations clamps them to 0.0', () async {
final TestPictureFactory factory = TestPictureFactory();
final FlutterVectorGraphicsListener listener = FlutterVectorGraphicsListener(
id: 0,
locale: null,
textDirection: null,
clipViewbox: true,
pictureFactory: factory,
);

listener.onPaintObject(
color: const ui.Color(0xff000000).toARGB32(),
strokeCap: null,
strokeJoin: null,
blendMode: BlendMode.srcIn.index,
strokeMiterLimit: null,
strokeWidth: null,
paintStyle: ui.PaintingStyle.fill.index,
id: 0,
shaderId: null,
);

// One negative, one positive -> positive preserved, negative clamped to 0.0
listener.onPaintBlur(0, -5.0, 10.0);

listener.onPathStart(0, 0);
listener.onPathMoveTo(0, 0);
listener.onPathLineTo(10, 10);
listener.onPathFinished();

await listener.onDrawPath(0, 0, null);

final Invocation drawPath = factory.fakeCanvases.single.invocations.single;
expect(drawPath.isMethod, true);
expect(drawPath.memberName, #drawPath);
final paint = drawPath.positionalArguments[1] as ui.Paint;
expect(paint.imageFilter, isNotNull);
expect(paint.imageFilter, ui.ImageFilter.blur(sigmaX: 0.0, sigmaY: 10.0));
});

test('onPaintBlur with both negative standard deviations does not set imageFilter', () async {
final TestPictureFactory factory = TestPictureFactory();
final FlutterVectorGraphicsListener listener = FlutterVectorGraphicsListener(
id: 0,
locale: null,
textDirection: null,
clipViewbox: true,
pictureFactory: factory,
);

listener.onPaintObject(
color: const ui.Color(0xff000000).toARGB32(),
strokeCap: null,
strokeJoin: null,
blendMode: BlendMode.srcIn.index,
strokeMiterLimit: null,
strokeWidth: null,
paintStyle: ui.PaintingStyle.fill.index,
id: 0,
shaderId: null,
);

// Both negative -> both clamped to 0.0 -> no filter
listener.onPaintBlur(0, -5.0, -10.0);

listener.onPathStart(0, 0);
listener.onPathMoveTo(0, 0);
listener.onPathLineTo(10, 10);
listener.onPathFinished();

await listener.onDrawPath(0, 0, null);

final Invocation drawPath = factory.fakeCanvases.single.invocations.single;
expect(drawPath.isMethod, true);
expect(drawPath.memberName, #drawPath);
final paint = drawPath.positionalArguments[1] as ui.Paint;
expect(paint.imageFilter, isNull);
});
}

class TestPictureFactory implements PictureFactory {
Expand Down
3 changes: 2 additions & 1 deletion packages/vector_graphics_codec/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 1.1.14

* Adds support for SVG filters (blur, offset, feMerge) and mask-type=alpha.
* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.

## 1.1.13
Expand Down
23 changes: 23 additions & 0 deletions packages/vector_graphics_codec/lib/vector_graphics_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class VectorGraphicsCodec {
static const int _textPositionTag = 50;
static const int _updateTextPositionTag = 51;
static const int _pathTagHalfPrecision = 52;
static const int _paintBlurTag = 53;

static const int _version = 1;
static const int _magicNumber = 0x00882d62;
Expand Down Expand Up @@ -230,6 +231,9 @@ class VectorGraphicsCodec {
case _updateTextPositionTag:
_readUpdateTextPosition(buffer, listener);
continue;
case _paintBlurTag:
_readPaintBlur(buffer, listener);
continue;
default:
throw StateError('Unknown type tag $type');
}
Expand Down Expand Up @@ -959,6 +963,22 @@ class VectorGraphicsCodec {
final Float64List? transform = buffer.getTransform();
listener?.onPatternStart(patternId, x, y, width, height, transform!);
}

/// Write a paint blur command to the buffer.
void writePaintBlur(VectorGraphicsBuffer buffer, int paintId, double sigmaX, double sigmaY) {
buffer._checkPhase(_CurrentSection.paints);
buffer._putUint8(_paintBlurTag);
buffer._putUint16(paintId);
buffer._putFloat32(sigmaX);
buffer._putFloat32(sigmaY);
}

void _readPaintBlur(_ReadBuffer buffer, VectorGraphicsCodecListener? listener) {
final int paintId = buffer.getUint16();
final double sigmaX = buffer.getFloat32();
final double sigmaY = buffer.getFloat32();
listener?.onPaintBlur(paintId, sigmaX, sigmaY);
}
}

/// Implement this listener class to support decoding of vector_graphics binary
Expand Down Expand Up @@ -1028,6 +1048,9 @@ abstract class VectorGraphicsCodecListener {
/// Prepare to draw a new mask, until the next [onRestoreLayer] command.
void onMask();

/// A paint blur has been decoded.
void onPaintBlur(int paintId, double sigmaX, double sigmaY) {}

/// A radial gradient shader has been parsed.
///
/// [focalX] and [focalY] are either both `null` or `non-null`.
Expand Down
2 changes: 1 addition & 1 deletion packages/vector_graphics_codec/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: vector_graphics_codec
description: An encoding library for the binary format used in `package:vector_graphics`
repository: https://github.com/flutter/packages/tree/main/packages/vector_graphics_codec
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+vector_graphics%22
version: 1.1.13
version: 1.1.14

environment:
sdk: ^3.10.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ void main() {
]);
});

test('Can encode and decode paint blur', () {
final buffer = VectorGraphicsBuffer();
final listener = TestListener();
final int paintId = codec.writeFill(buffer, 23, 0);
codec.writePaintBlur(buffer, paintId, 2.5, 3.5);
final int pathId = codec.writePath(
buffer,
Uint8List.fromList(<int>[ControlPointTypes.moveTo, ControlPointTypes.close]),
Float32List.fromList(<double>[1, 2]),
0,
);
codec.writeDrawPath(buffer, pathId, paintId, null);

codec.decode(buffer.done(), listener);

expect(listener.commands, contains(OnPaintBlur(paintId, 2.5, 3.5)));
});

test('Basic message encode and decode with shaded path', () {
final buffer = VectorGraphicsBuffer();
final listener = TestListener();
Expand Down Expand Up @@ -875,6 +893,11 @@ class TestListener extends VectorGraphicsCodecListener {
);
}

@override
void onPaintBlur(int paintId, double sigmaX, double sigmaY) {
commands.add(OnPaintBlur(paintId, sigmaX, sigmaY));
}

@override
void onPathClose() {
commands.add(const OnPathClose());
Expand Down Expand Up @@ -1339,6 +1362,28 @@ class OnPaintObject {
'paintStyle: $paintStyle, id: $id, shaderId: $shaderId)';
}

@immutable
class OnPaintBlur {
const OnPaintBlur(this.paintId, this.sigmaX, this.sigmaY);

final int paintId;
final double sigmaX;
final double sigmaY;

@override
int get hashCode => Object.hash(paintId, sigmaX, sigmaY);

@override
bool operator ==(Object other) =>
other is OnPaintBlur &&
other.paintId == paintId &&
other.sigmaX == sigmaX &&
other.sigmaY == sigmaY;

@override
String toString() => 'OnPaintBlur(paintId: $paintId, sigmaX: $sigmaX, sigmaY: $sigmaY)';
}

@immutable
class OnPathClose {
const OnPathClose();
Expand Down
4 changes: 4 additions & 0 deletions packages/vector_graphics_compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.2.6

* Adds support for SVG filters (blur, offset, feMerge) and mask-type=alpha.

## 1.2.5

* Updates allowed version range of `xml` to include up to 7.0.1.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@ class AffineMatrix {
/// Translations can affect this value, so we have to track it.
final double _m4_10;

/// The scale factor along the X axis, including rotation.
double get xScale => math.sqrt(a * a + c * c);

/// The scale factor along the Y axis, including rotation.
double get yScale => math.sqrt(b * b + d * d);

/// Calculates the scale for a stroke width based on the average of the x- and
/// y-axis scales of this matrix.
double? scaleStrokeWidth(double? width) {
if (width == null || (a == 1 && d == 1)) {
return width;
}

final double xScale = math.sqrt(a * a + c * c);
final double yScale = math.sqrt(b * b + d * d);

return (xScale + yScale) / 2 * width;
}

Expand Down
Loading