Skip to content

Commit

Permalink
transform shaders into correct coordinate space (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonahwilliams authored Mar 17, 2022
1 parent cdb483f commit c70e0d7
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class Point {
return Point(x * multiplicand, y * multiplicand);
}

/// Returns a point whose coordinates are the coordinates of the
/// left-hand-side operand (a Point) added to the right-hand-side
/// coordinates (a Point).
Point operator +(Point other) {
return Point(x + other.x, y + other.y);
}

@override
String toString() => 'Point($x, $y)';
}
Expand Down
47 changes: 47 additions & 0 deletions packages/vector_graphics_compiler/lib/src/geometry/path.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math' as math;

import 'package:meta/meta.dart';
import 'package:path_parsing/path_parsing.dart';

Expand Down Expand Up @@ -453,6 +455,51 @@ class Path {
other.fillType == fillType;
}

/// Compute the bounding box for the given path segment.
Rect bounds() {
if (_commands.isEmpty) {
return Rect.zero;
}
double smallestX = double.maxFinite;
double smallestY = double.maxFinite;
double largestX = 0;
double largestY = 0;
for (final PathCommand command in _commands) {
switch (command.type) {
case PathCommandType.move:
final MoveToCommand move = command as MoveToCommand;
smallestX = math.min(move.x, smallestX);
smallestY = math.min(move.y, smallestY);
largestX = math.max(move.x, largestX);
largestY = math.max(move.y, largestY);
break;
case PathCommandType.line:
final LineToCommand move = command as LineToCommand;
smallestX = math.min(move.x, smallestX);
smallestY = math.min(move.y, smallestY);
largestX = math.max(move.x, largestX);
largestY = math.max(move.y, largestY);
break;
case PathCommandType.cubic:
final CubicToCommand cubic = command as CubicToCommand;
for (List<double> pair in <List<double>>[
<double>[cubic.x1, cubic.y1],
<double>[cubic.x2, cubic.y2],
<double>[cubic.x3, cubic.y3],
]) {
smallestX = math.min(pair[0], smallestX);
smallestY = math.min(pair[1], smallestY);
largestX = math.max(pair[0], largestX);
largestY = math.max(pair[1], largestY);
}
break;
case PathCommandType.close:
break;
}
}
return Rect.fromLTRB(smallestX, smallestY, largestX, largestY);
}

/// Returns a string that prints the dart:ui code to create this path.
String toFlutterString() {
final StringBuffer buffer = StringBuffer('Path()');
Expand Down
83 changes: 83 additions & 0 deletions packages/vector_graphics_compiler/lib/src/paint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class Color {
abstract class Shader {
/// Allows subclasses to be const.
const Shader();

/// Apply the bounds and transform the the shader.
Shader applyBounds(Rect bounds, AffineMatrix transform);
}

/// A [Shader] that describes a linear gradient from [from] to [to].
Expand Down Expand Up @@ -136,6 +139,31 @@ class LinearGradient extends Shader {
/// space this object occupies or or not.
final GradientUnitMode unitMode;

@override
LinearGradient applyBounds(Rect bounds, AffineMatrix transform) {
return LinearGradient(
from: transform.transformPoint(
Point(from.x * bounds.width, from.y * bounds.height) +
Point(
bounds.left,
bounds.top,
),
),
to: transform.transformPoint(
Point(to.x * bounds.width, to.y * bounds.height) +
Point(
bounds.left,
bounds.top,
),
),
colors: colors,
offsets: offsets,
tileMode: tileMode,
transform: this.transform,
unitMode: unitMode,
);
}

@override
int get hashCode => Object.hash(from, to, Object.hashAll(colors),
Object.hashAll(offsets ?? <double>[]), tileMode, unitMode);
Expand Down Expand Up @@ -243,6 +271,40 @@ class RadialGradient extends Shader {
/// space this object occupies or or not.
final GradientUnitMode unitMode;

@override
RadialGradient applyBounds(Rect bounds, AffineMatrix transform) {
return RadialGradient(
center: transform.transformPoint(
Point(
center.x * bounds.width,
center.y * bounds.height,
) +
Point(
bounds.left,
bounds.top,
),
),
radius: radius,
colors: colors,
offsets: offsets,
tileMode: tileMode,
transform: this.transform,
focalPoint: focalPoint == null
? focalPoint
: transform.transformPoint(
Point(
focalPoint!.x * bounds.width,
focalPoint!.y * bounds.height,
) +
Point(
bounds.left,
bounds.top,
),
),
unitMode: unitMode,
);
}

@override
int get hashCode => Object.hash(
center,
Expand Down Expand Up @@ -363,6 +425,27 @@ class Paint {
other.pathFillType == pathFillType;
}

/// Apply the bounds to the given paint.
///
/// May be a no-op if no properties of the paint are impacted by
/// the bounds.
Paint applyBounds(Rect bounds, AffineMatrix transform) {
final Shader? shader = fill?.shader;
if (shader == null) {
return this;
}
final Shader newShader = shader.applyBounds(bounds, transform);
return Paint(
blendMode: blendMode,
stroke: stroke,
pathFillType: pathFillType,
fill: Fill(
color: fill!.color,
shader: newShader,
),
);
}

@override
String toString() {
final StringBuffer buffer = StringBuffer('Paint(');
Expand Down
4 changes: 3 additions & 1 deletion packages/vector_graphics_compiler/lib/src/svg/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ class PathNode extends Node {

@override
void build(DrawCommandBuilder builder, AffineMatrix transform) {
builder.addPath(path.transformed(transform), paint!, id);
final Path transformedPath = path.transformed(transform);
final Rect bounds = transformedPath.bounds();
builder.addPath(transformedPath, paint!.applyBounds(bounds, transform), id);
}
}
36 changes: 36 additions & 0 deletions packages/vector_graphics_compiler/test/paint_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,40 @@ void main() {
' ..style = PaintingStyle.stroke;\n',
);
});

test('LinearGradient can be converted to local coordinates', () {
const LinearGradient gradient = LinearGradient(
from: Point(0, 0),
to: Point(1, 1),
colors: <Color>[Color.opaqueBlack, Color(0xFFABCDEF)],
tileMode: TileMode.mirror,
offsets: <double>[0.0, 1.0],
transform: AffineMatrix.identity,
unitMode: GradientUnitMode.userSpaceOnUse,
);

final LinearGradient transformed = gradient.applyBounds(
const Rect.fromLTWH(5, 5, 100, 100), AffineMatrix.identity);

expect(transformed.from, const Point(5, 5));
expect(transformed.to, const Point(105, 105));
});

test('RadialGradient can be converted to local coordinates', () {
const RadialGradient gradient = RadialGradient(
center: Point(0.5, 0.5),
radius: 10,
colors: <Color>[Color(0xFFFFFFAA), Color(0xFFABCDEF)],
tileMode: TileMode.clamp,
transform: AffineMatrix.identity,
focalPoint: Point(0.6, 0.6),
offsets: <double>[.1, .9],
);

final RadialGradient transformed = gradient.applyBounds(
const Rect.fromLTWH(5, 5, 100, 100), AffineMatrix.identity);

expect(transformed.center, const Point(55, 55));
expect(transformed.focalPoint, const Point(65, 65));
});
}
31 changes: 31 additions & 0 deletions packages/vector_graphics_compiler/test/path_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,35 @@ void main() {
' ..lineTo(30.0, 30.0);',
);
});

test('Compute path bounds with rect', () {
final PathBuilder builder = PathBuilder()
..addRect(const Rect.fromLTWH(5, 5, 95, 95))
..close();
final Path path = builder.toPath();

expect(path.bounds(), const Rect.fromLTWH(5, 5, 95, 95));
});

test('Compute path bounds with lines', () {
final PathBuilder builder = PathBuilder()
..moveTo(0, 0)
..lineTo(25, 0)
..lineTo(25, 25)
..lineTo(0, 25)
..close();
final Path path = builder.toPath();

expect(path.bounds(), const Rect.fromLTWH(0, 0, 25, 25));
});

test('Compute path bounds with cubics', () {
final PathBuilder builder = PathBuilder()
..moveTo(0, 0)
..cubicTo(10, 10, 20, 20, -10, -10)
..close();
final Path path = builder.toPath();

expect(path.bounds(), const Rect.fromLTRB(-10.0, -10.0, 20.0, 20.0));
});
}

0 comments on commit c70e0d7

Please sign in to comment.