diff --git a/sky/engine/core/dart/painting.dart b/sky/engine/core/dart/painting.dart index 9b987acf19cad..527044ddd3709 100644 --- a/sky/engine/core/dart/painting.dart +++ b/sky/engine/core/dart/painting.dart @@ -22,6 +22,7 @@ abstract class Image extends NativeFieldWrapperClass2 { /// after this method is called. void dispose() native "Image_dispose"; + @override String toString() => '[$width\u00D7$height]'; } @@ -56,7 +57,6 @@ void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) /// Paths can be drawn on canvases using [Canvas.drawPath], and can /// used to create clip regions using [Canvas.clipPath]. class Path extends NativeFieldWrapperClass2 { - /// Create a new empty [Path] object. Path() { _constructor(); } void _constructor() native "Path_constructor"; @@ -178,20 +178,55 @@ class Path extends NativeFieldWrapperClass2 { enum BlurStyle { // These mirror SkBlurStyle and must be kept in sync. - /// Fuzzy inside and outside. + /// Fuzzy inside and outside. This is useful for painting shadows that are + /// offset from the shape that ostensible is casting the shadow. normal, - /// Solid inside, fuzzy outside. + /// Solid inside, fuzzy outside. This corresponds to drawing the shape, and + /// additionally drawing the blur. This can make objects appear brighter, + /// maybe even as if they were fluorescent. solid, - /// Nothing inside, fuzzy outside. + /// Nothing inside, fuzzy outside. This is useful for painting shadows for + /// partially transparent shapes, when they are painted separately but without + /// an offset, so that the shadow doesn't paint below the shape. outer, - /// Fuzzy inside, nothing outside. + /// Fuzzy inside, nothing outside. This can make shapes appear to be lit from + /// within. inner, } +/// A mask filter to apply to shapes as they are painted. A mask filter is a +/// function that takes a bitmap of color pixels, and returns another bitmap of +/// color pixels. +/// +/// Instances of this class are used with [Paint.maskFilter] on [Paint] objects. class MaskFilter extends NativeFieldWrapperClass2 { + /// Creates a mask filter that takes the shape being drawn and blurs it. + /// + /// This is commonly used to approximate shadows. + /// + /// The `style` argument controls the kind of effect to draw; see [BlurStyle]. + /// + /// The `sigma` argument controls the size of the effect. It is the standard + /// deviation of the Gaussian blur to apply. The value must be greater than + /// zero. The sigma corresponds to very roughly half the radius of the effect + /// in pixels. + /// + /// If the `ignoreTransform` argument is set, then the current transform is + /// ignored when computing the blur. This makes the operation cheaper, but + /// lowers the quality of the effect. In particular, it means that the sigma + /// will be relative to the device pixel coordinate space, rather than the + /// logical pixel coordinate space, which means the blur will look different + /// on different devices. + /// + /// If the `highQuality` argument is set, then the quality of the blur may be + /// slightly improved, at the cost of making the operation even more + /// expensive. + /// + /// Even in the best conditions and with the lowest quality settings, a blur + /// is an expensive operation and blurs should therefore be used sparingly. MaskFilter.blur(BlurStyle style, double sigma, { bool ignoreTransform: false, bool highQuality: false @@ -211,10 +246,22 @@ class MaskFilter extends NativeFieldWrapperClass2 { } } -/// A description of a filter to apply when drawing with a particular [Paint]. +/// A description of a color filter to apply when drawing a shape or compositing +/// a layer with a particular [Paint]. A color filter is a function that takes +/// two colors, and outputs one color. When applied during compositing, it is +/// independently applied to each pixel of the layer being drawn before the +/// entire layer is merged with the destination. /// -/// See [Paint.colorFilter]. +/// Instances of this class are used with [Paint.colorFilter] on [Paint] +/// objects. class ColorFilter extends NativeFieldWrapperClass2 { + /// Creates a color filter that applies the transfer mode given as the second + /// argument. The source color is the one given as the first argument, and the + /// destination color is the one from the layer being composited. + /// + /// The output of this filter is then composited into the background according + /// to the [Paint.transferMode], using the output of this filter as the source + /// and the background as the destination. ColorFilter.mode(Color color, TransferMode transferMode) { _constructor(color, transferMode); } @@ -241,7 +288,7 @@ class ImageFilter extends NativeFieldWrapperClass2 { // } // void _initPicture(Picture picture) native "ImageFilter_initPicture"; - /// Applies a Gaussian blur. + /// Creates an image filter that applies a Gaussian blur. ImageFilter.blur({ double sigmaX: 0.0, double sigmaY: 0.0 }) { _constructor(); _initBlur(sigmaX, sigmaY); @@ -250,7 +297,7 @@ class ImageFilter extends NativeFieldWrapperClass2 { } /// Base class for objects such as [Gradient] and [ImageShader] which -/// correspond to shaders. +/// correspond to shaders as used by [Paint.shader]. abstract class Shader extends NativeFieldWrapperClass2 { } /// Defines what happens at the edge of the gradient. @@ -263,6 +310,10 @@ enum TileMode { mirror } +/// A shader (as used by [Paint.shader]) that renders a color gradient. +/// +/// There are two useful types of gradients, created by [new Gradient.linear] +/// and [new Griadent.radial]. class Gradient extends Shader { /// Creates a Gradient object that is not initialized. /// @@ -271,10 +322,13 @@ class Gradient extends Shader { Gradient(); void _constructor() native "Gradient_constructor"; - /// Creates a linear gradient from [endPoint[0]] to [endPoint[1]]. If - /// [colorStops] is provided, [colorStops[i]] is a number from 0 to 1 that - /// specifies where [color[i]] begins in the gradient. - // TODO(mpcomplete): Maybe pass a list of (color, colorStop) pairs instead? + /// Creates a linear gradient from `endPoint[0]` to `endPoint[1]`. If + /// `colorStops` is provided, `colorStops[i]` is a number from 0 to 1 that + /// specifies where `color[i]` begins in the gradient. If `colorStops` is not + /// provided, then two stops at 0.0 and 1.0 are implied. The behavior before + /// and after the radius is described by the `tileMode` argument. + // TODO(mpcomplete): Consider passing a list of (color, colorStop) pairs + // instead. Gradient.linear(List endPoints, List colors, [List colorStops = null, @@ -287,10 +341,12 @@ class Gradient extends Shader { } void _initLinear(List endPoints, List colors, List colorStops, int tileMode) native "Gradient_initLinear"; - /// Creates a radial gradient centered at [center] that ends at [radius] - /// distance from the center. If [colorStops] is provided, [colorStops[i]] is - /// a number from 0 to 1 that specifies where [color[i]] begins in the - /// gradient. + /// Creates a radial gradient centered at `center` that ends at `radius` + /// distance from the center. If `colorStops` is provided, `colorStops[i]` is + /// a number from 0 to 1 that specifies where `color[i]` begins in the + /// gradient. If `colorStops` is not provided, then two stops at 0.0 and 1.0 + /// are implied. The behavior before and after the radius is described by the + /// `tileMode` argument. Gradient.radial(Point center, double radius, List colors, @@ -308,7 +364,13 @@ class Gradient extends Shader { } } +/// A shader (as used by [Paint.shader]) that tiles an image. class ImageShader extends Shader { + /// Creates an image-tiling shader. The first argument specifies the image to + /// tile. The second and third arguments specify the [TileMode] for the x + /// direction and y direction respectively. The fourth argument gives the + /// matrix to apply to the effect. All the arguments are required and must not + /// be null. ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4) { if (image == null) throw new ArgumentError("[image] argument cannot be null"); @@ -338,6 +400,18 @@ enum VertexMode { /// [Canvas] objects are used in creating [Picture] objects, which can /// themselves be used with a [SceneBuilder] to build a [Scene]. In /// normal usage, however, this is all handled by the framework. +/// +/// A canvas has a current transformation matrix which is applied to all +/// operations. Initially, the transformation matrix is the identity transform. +/// It can be modified using the [translate], [scale], [rotate], [skew], +/// [transform], and [setMatrix] methods. +/// +/// A canvas also has a current clip region which is applied to all operations. +/// Initially, the clip region is infinite. It can be modified using the +/// [clipRect], [clipRRect], and [clipPath] methods. +/// +/// The current transform and clip can be saved and restored using the stack +/// managed by the [save], [saveLayer], and [restore] methods. class Canvas extends NativeFieldWrapperClass2 { /// Creates a canvas for recording graphical operations into the /// given picture recorder. @@ -369,21 +443,20 @@ class Canvas extends NativeFieldWrapperClass2 { /// Call [restore] to pop the save stack. void save() native "Canvas_save"; - /// Saves a copy of the current transform and clip on the save - /// stack, and then creates a new group which subsequent calls will - /// become a part of. When the save stack is later popped, the group - /// will be flattened and have the given paint applied. + /// Saves a copy of the current transform and clip on the save stack, and then + /// creates a new group which subsequent calls will become a part of. When the + /// save stack is later popped, the group will be flattened into a layer and + /// have the given `paint`'s [Paint.colorFilter] and [Paint.transferMode] + /// applied. /// - /// This lets you create composite effects, for example making a - /// group of drawing commands semi-transparent. Without using - /// [saveLayer], each part of the group would be painted - /// individually, so where they overlap would be darker than where - /// they do not. By using [saveLayer] to group them together, they - /// can be drawn with an opaque color at first, and then the entire - /// group can be made transparent using the [saveLayer]'s paint. + /// This lets you create composite effects, for example making a group of + /// drawing commands semi-transparent. Without using [saveLayer], each part of + /// the group would be painted individually, so where they overlap would be + /// darker than where they do not. By using [saveLayer] to group them + /// together, they can be drawn with an opaque color at first, and then the + /// entire group can be made transparent using the [saveLayer]'s paint. /// - /// Call [restore] to pop the save stack and apply the paint to the - /// group. + /// Call [restore] to pop the save stack and apply the paint to the group. void saveLayer(Rect bounds, Paint paint) { if (bounds == null) _saveLayerWithoutBounds(paint); @@ -402,6 +475,9 @@ class Canvas extends NativeFieldWrapperClass2 { /// Otherwise, does nothing. /// /// Use [save] and [saveLayer] to push state onto the stack. + /// + /// If the state was pushed with with [saveLayer], then this call will also + /// cause the new layer to be composited into the previous layer. void restore() native "Canvas_restore"; /// Returns the number of items on the save stack, including the @@ -412,11 +488,26 @@ class Canvas extends NativeFieldWrapperClass2 { /// This number cannot go below 1. int getSaveCount() native "Canvas_getSaveCount"; + /// Add a translation to the current transform, shifting the coordinate space + /// horizontally by the first argument and vertically by the second argument. void translate(double dx, double dy) native "Canvas_translate"; + + /// Add an axis-aligned scale to the current transform, scaling by the first + /// argument in the horizontal direction and the second in the vertical + /// direction. void scale(double sx, double sy) native "Canvas_scale"; + + /// Add a rotation to the current transform. The argument is in radians clockwise. void rotate(double radians) native "Canvas_rotate"; + + /// Add an axis-aligned skew to the current transform, with the first argument + /// being the horizontal skew in radians clockwise around the origin, and the + /// second argument being the vertical skew in radians clockwise around the + /// origin. void skew(double sx, double sy) native "Canvas_skew"; + /// Multiply the current transform by the specified 4⨉4 transformation matrix + /// specified as a list of values in column-major order. void transform(Float64List matrix4) { if (matrix4.length != 16) throw new ArgumentError("[matrix4] must have 16 entries."); @@ -424,6 +515,8 @@ class Canvas extends NativeFieldWrapperClass2 { } void _transform(Float64List matrix4) native "Canvas_transform"; + /// Replaces the current transform with the specified 4⨉4 transformation + /// matrix specified as a list of values in column-major order. void setMatrix(Float64List matrix4) { if (matrix4.length != 16) throw new ArgumentError("[matrix4] must have 16 entries."); @@ -431,7 +524,12 @@ class Canvas extends NativeFieldWrapperClass2 { } void _setMatrix(Float64List matrix4) native "Canvas_setMatrix"; + /// Returns the current 4⨉4 transformation matrix as a list of 16 values in + /// column-major order. Float64List getTotalMatrix() native "Canvas_getTotalMatrix"; + + /// Reduces the clip region to the intersection of the current clip and the + /// given rectangle. void clipRect(Rect rect) { _clipRect(rect.left, rect.top, rect.right, rect.bottom); } @@ -440,22 +538,37 @@ class Canvas extends NativeFieldWrapperClass2 { double right, double bottom) native "Canvas_clipRect"; + /// Reduces the clip region to the intersection of the current clip and the + /// given rounded rectangle. void clipRRect(RRect rrect) native "Canvas_clipRRect"; + /// Reduces the clip region to the intersection of the current clip and the + /// given [Path]. void clipPath(Path path) native "Canvas_clipPath"; + /// Paints the given [Color] onto the canvas, applying the given + /// [TransferMode], with the given color being the source and the background + /// being the destination. void drawColor(Color color, TransferMode transferMode) { _drawColor(color.value, transferMode.index); } void _drawColor(int color, int transferMode) native "Canvas_drawColor"; + /// Draws a line between the given [Point]s using the given paint. The line is + /// stroked, the value of the [Paint.style] is ignored for this call. void drawLine(Point p1, Point p2, Paint paint) { _drawLine(p1.x, p1.y, p2.x, p2.y, paint); } void _drawLine(double x1, double y1, double x2, double y2, Paint paint) native "Canvas_drawLine"; + /// Fills the canvas with the given [Paint]. + /// + /// To fill the canvas with a solid color and transfer mode, consider + /// [drawColor] instead. void drawPaint(Paint paint) native "Canvas_drawPaint"; + /// Draws a rectangle with the given [Paint]. Whether the rectangle is filled + /// or stroked (or both) is controlled by [Paint.style]. void drawRect(Rect rect, Paint paint) { _drawRect(rect.left, rect.top, rect.right, rect.bottom, paint); } @@ -465,10 +578,20 @@ class Canvas extends NativeFieldWrapperClass2 { double bottom, Paint paint) native "Canvas_drawRect"; + /// Draws a rounded rectangle with the given [Paint]. Whether the rectangle is + /// filled or stroked (or both) is controlled by [Paint.style]. void drawRRect(RRect rrect, Paint paint) native "Canvas_drawRRect"; + /// Draws a shape consisting of the difference between two rounded rectangles + /// with the given [Paint]. Whether this shape is filled or stroked (or both) + /// is controlled by [Paint.style]. + /// + /// This shape is almost but not quite entirely unlike an annulus. void drawDRRect(RRect outer, RRect inner, Paint paint) native "Canvas_drawDRRect"; + /// Draws an axis-aligned oval that fills the given axis-aligned rectangle + /// with the given [Paint]. Whether the oval is filled or stroked (or both) is + /// controlled by [Paint.style]. void drawOval(Rect rect, Paint paint) { _drawOval(rect.left, rect.top, rect.right, rect.bottom, paint); } @@ -478,22 +601,32 @@ class Canvas extends NativeFieldWrapperClass2 { double bottom, Paint paint) native "Canvas_drawOval"; + /// Draws a circle centered at the point given by the first two arguments and + /// that has the radius given by the third argument, with the [Paint] given in + /// the fourth argument. Whether the circle is filled or stroked (or both) is + /// controlled by [Paint.style]. void drawCircle(Point c, double radius, Paint paint) { _drawCircle(c.x, c.y, radius, paint); } void _drawCircle(double x, double y, double radius, Paint paint) native "Canvas_drawCircle"; + /// Draws the given [Path] with the given [Paint]. Whether this shape is + /// filled or stroked (or both) is controlled by [Paint.style]. If the path is + /// filled, then subpaths within it are implicitly closed (see [Path.close]). void drawPath(Path path, Paint paint) native "Canvas_drawPath"; + /// Draws the given [Image] into the canvas with its top-left corner at the + /// given [Point]. The image is composited into the canvas using the given [Paint]. void drawImage(Image image, Point p, Paint paint) { _drawImage(image, p.x, p.y, paint); } void _drawImage(Image image, double x, double y, Paint paint) native "Canvas_drawImage"; - /// Draws the src rect from the image into the canvas as dst rect. + /// Draws the subset of the given image described by the `src` argument into + /// the canvas in the axis-aligned rectangle given by the `dst` argument. /// - /// Might sample from outside the src rect by half the width of an applied - /// filter. + /// This might sample from outside the `src` rect by up to half the width of + /// an applied filter. void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { _drawImageRect(image, src.left, @@ -517,6 +650,19 @@ class Canvas extends NativeFieldWrapperClass2 { double dstBottom, Paint paint) native "Canvas_drawImageRect"; + /// Draws the given [Image] into the canvas using the given [Paint]. + /// + /// The image is drawn in nine portions described by splitting the image by + /// drawing two horizontal lines and two vertical lines, where the `center` + /// argument describes the rectangle formed by the four points where these + /// four lines intersect each other. (This forms a 3-by-3 grid of regions, + /// the center region being described by the `center` argument.) + /// + /// The four regions in the corners are drawn, without scaling, in the four + /// corners of the destination rectangle described by `dst`. The remaining + /// five regions are drawn by stretching them to fit such that they exactly + /// cover the destination rectangle while maintaining their relative + /// positions. void drawImageNine(Image image, Rect center, Rect dst, Paint paint) { _drawImageNine(image, center.left, diff --git a/sky/engine/core/painting/Color.dart b/sky/engine/core/painting/Color.dart index 9dbd16194446d..1778ea1e41e6b 100644 --- a/sky/engine/core/painting/Color.dart +++ b/sky/engine/core/painting/Color.dart @@ -99,6 +99,7 @@ class Color { ); } + @override bool operator ==(dynamic other) { if (other is! Color) return false; @@ -106,7 +107,9 @@ class Color { return value == typedOther.value; } + @override int get hashCode => _value.hashCode; + @override String toString() => "Color(0x${_value.toRadixString(16).padLeft(8, '0')})"; } diff --git a/sky/engine/core/painting/FilterQuality.dart b/sky/engine/core/painting/FilterQuality.dart index 97290012fa131..13a66f23535bf 100644 --- a/sky/engine/core/painting/FilterQuality.dart +++ b/sky/engine/core/painting/FilterQuality.dart @@ -11,8 +11,24 @@ part of dart_ui; /// /// See [Paint.filterQuality]. enum FilterQuality { + /// Fastest possible filtering, albeit also the lowest quality. + /// + /// Typically this implies nearest-neighbour filtering. none, + + /// Better quality than [none], faster than [medium]. + /// + /// Typically this implies bilinear interpolation. low, + + /// Better quality than [low], faster than [high]. + /// + /// Typically this implies a combination of bilinear interpolation and + /// pyramidal parametric prefiltering (mipmaps). medium, + + /// Best possible quality filtering, albeit also the slowest. + /// + /// Typically this implies bicubic interpolation or better. high, } diff --git a/sky/engine/core/painting/Offset.dart b/sky/engine/core/painting/Offset.dart index 523921b98966b..90e23ab82de16 100644 --- a/sky/engine/core/painting/Offset.dart +++ b/sky/engine/core/painting/Offset.dart @@ -6,8 +6,13 @@ part of dart_ui; /// An immutable 2D floating-point offset. /// -/// An Offset represents a vector from an unspecified point +/// An Offset represents a vector from an unspecified [Point]. +/// +/// Adding an offset to a [Point] returns the [Point] that is indicated by the +/// vector from that first point. class Offset extends OffsetBase { + /// Creates an offset. The first argument sets [dx], the horizontal component, + /// and the second sets [dy], the vertical component. const Offset(double dx, double dy) : super(dx, dy); /// The x component of the offset. @@ -20,6 +25,8 @@ class Offset extends OffsetBase { double get distance => math.sqrt(_dx * _dx + _dy * _dy); /// The square of the magnitude of the offset. + /// + /// This is cheaper than computing the [distance] itself. double get distanceSquared => _dx * _dx + _dy * _dy; /// An offset with zero magnitude. @@ -34,15 +41,52 @@ class Offset extends OffsetBase { /// Returns a new offset with translateX added to the x component and translateY added to the y component. Offset translate(double translateX, double translateY) => new Offset(dx + translateX, dy + translateY); + /// Unary negation operator. Returns an offset with the coordinates negated. Offset operator -() => new Offset(-dx, -dy); + + /// Binary subtraction operator. Returns an offset whose [dx] value is the + /// left-hand-side operand's [dx] minus the right-hand-side operand's [dx] and + /// whose [dy] value is the left-hand-side operand's [dy] minus the + /// right-hand-side operand's [dy]. Offset operator -(Offset other) => new Offset(dx - other.dx, dy - other.dy); + + /// Binary addition operator. Returns an offset whose [dx] value is the sum of + /// the [dx] values of the two operands, and whose [dy] value is the sum of + /// the [dy] values of the two operands. Offset operator +(Offset other) => new Offset(dx + other.dx, dy + other.dy); + + /// Multiplication operator. Returns an offset whose coordinates are the + /// coordinates of the left-hand-side operand (an Offset) multiplied by the + /// scalar right-hand-side operand (a double). Offset operator *(double operand) => new Offset(dx * operand, dy * operand); + + /// Division operator. Returns an offset whose coordinates are the + /// coordinates of the left-hand-side operand (an Offset) divided by the + /// scalar right-hand-side operand (a double). Offset operator /(double operand) => new Offset(dx / operand, dy / operand); + + /// Integer (truncating) division operator. Returns an offset whose + /// coordinates are the coordinates of the left-hand-side operand (an Offset) + /// divided by the scalar right-hand-side operand (a double), rounded towards + /// zero. Offset operator ~/(double operand) => new Offset((dx ~/ operand).toDouble(), (dy ~/ operand).toDouble()); + + /// Modulo (remainder) operator. Returns an offset whose coordinates are the + /// remainder of dividing the coordinates of the left-hand-side operand (an + /// Offset) by the scalar right-hand-side operand (a double). Offset operator %(double operand) => new Offset(dx % operand, dy % operand); - /// Returns a rect of the given size that starts at (0, 0) plus this offset. + /// Rectangle constructor operator. Combines an offset and a [Size] to form a + /// [Rect] whose top-left coordinate is the point given by adding this offset, + /// the left-hand-side operand, to the origin, and whose size is the + /// right-hand-side operand. + /// + /// ```dart + /// Rect myRect = Offset.zero & const Size(100.0, 100.0); + /// // same as: new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0) + /// ``` + /// + /// See also: [Point.&] Rect operator &(Size other) => new Rect.fromLTWH(dx, dy, other.width, other.height); /// Returns the point at (0, 0) plus this offset. @@ -62,7 +106,9 @@ class Offset extends OffsetBase { } /// Compares two Offsets for equality. + @override bool operator ==(dynamic other) => other is Offset && super == other; + @override String toString() => "Offset(${dx?.toStringAsFixed(1)}, ${dy?.toStringAsFixed(1)})"; } diff --git a/sky/engine/core/painting/OffsetBase.dart b/sky/engine/core/painting/OffsetBase.dart index f753d6434b87b..64be00df4292e 100644 --- a/sky/engine/core/painting/OffsetBase.dart +++ b/sky/engine/core/painting/OffsetBase.dart @@ -4,19 +4,70 @@ part of dart_ui; +/// Base class for [Size] and [Offset], which are both ways to describe +/// a distance as a two-dimensional axis-aligned vector. abstract class OffsetBase { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + /// + /// The first argument sets the horizontal dimension, and the second the + /// vertical dimension. const OffsetBase(this._dx, this._dy); final double _dx; final double _dy; + /// Returns true if either dimension is [double.INFINITY], and false if both + /// are finite (or negative infinity, or NaN). + /// + /// This is different than comparing for equality with an instance that has + /// _both_ dimensions set to [double.INFINITY]. bool get isInfinite => _dx >= double.INFINITY || _dy >= double.INFINITY; + /// Less-than operator. Compares an [Offset] or [Size] to another [Offset] or + /// [Size], and returns true if both the horizontal and vertical values of the + /// left-hand-side operand are smaller than the horizontal and vertical values + /// of the right-hand-side operand respectively. Returns false otherwise. + /// + /// This is a partial ordering. It is possible for two values to be neither + /// less, nor greater than, nor equal to, another. bool operator <(OffsetBase other) => _dx < other._dx && _dy < other._dy; + + /// Less-than-or-equal-to operator. Compares an [Offset] or [Size] to another + /// [Offset] or [Size], and returns true if both the horizontal and vertical + /// values of the left-hand-side operand are smaller than or equal to the + /// horizontal and vertical values of the right-hand-side operand + /// respectively. Returns false otherwise. + /// + /// This is a partial ordering. It is possible for two values to be neither + /// less, nor greater than, nor equal to, another. bool operator <=(OffsetBase other) => _dx <= other._dx && _dy <= other._dy; + + /// Greater-than operator. Compares an [Offset] or [Size] to another [Offset] + /// or [Size], and returns true if both the horizontal and vertical values of + /// the left-hand-side operand are bigger than the horizontal and vertical + /// values of the right-hand-side operand respectively. Returns false + /// otherwise. + /// + /// This is a partial ordering. It is possible for two values to be neither + /// less, nor greater than, nor equal to, another. bool operator >(OffsetBase other) => _dx > other._dx && _dy > other._dy; + + /// Less-than-or-equal-to operator. Compares an [Offset] or [Size] to another + /// [Offset] or [Size], and returns true if both the horizontal and vertical + /// values of the left-hand-side operand are bigger than or equal to the + /// horizontal and vertical values of the right-hand-side operand + /// respectively. Returns false otherwise. + /// + /// This is a partial ordering. It is possible for two values to be neither + /// less, nor greater than, nor equal to, another. bool operator >=(OffsetBase other) => _dx > other._dx && _dy >= other._dy; + /// Equality operator. Compares an [Offset] or [Size] to another [Offset] or + /// [Size], and returns true if the horizontal and vertical values of the + /// left-hand-side operand are equal to the horizontal and vertical values of + /// the right-hand-side operand respectively. Returns false otherwise. + @override bool operator ==(dynamic other) { if (other is! OffsetBase) return false; @@ -25,5 +76,6 @@ abstract class OffsetBase { _dy == typedOther._dy; } + @override int get hashCode => hashValues(_dx, _dy); } diff --git a/sky/engine/core/painting/Paint.dart b/sky/engine/core/painting/Paint.dart index 4c8eeeed02d75..b8941c072fc80 100644 --- a/sky/engine/core/painting/Paint.dart +++ b/sky/engine/core/painting/Paint.dart @@ -29,6 +29,7 @@ class Paint { /// /// If null, defaults to [PaintingStyle.fill]. PaintingStyle style; + static const PaintingStyle _kDefaultStyle = PaintingStyle.fill; /// How wide to make edges drawn when [style] is set to /// [PaintingStyle.stroke] or [PaintingStyle.strokeAndFill]. The @@ -37,6 +38,7 @@ class Paint { /// /// The values null and 0.0 correspond to a hairline width. double strokeWidth; + static const double _kDefaultStrokeWidth = 0.0; /// The kind of finish to place on the end of lines drawn when /// [style] is set to [PaintingStyle.stroke] or @@ -44,6 +46,7 @@ class Paint { /// /// If null, defaults to [StrokeCap.butt], i.e. no caps. StrokeCap strokeCap; + static const StrokeCap _kDefaultStrokeCap = StrokeCap.butt; /// Whether to apply anti-aliasing to lines and images drawn on the /// canvas. @@ -51,30 +54,79 @@ class Paint { /// Defaults to true. The value null is treated as false. bool isAntiAlias = true; + /// The color to use when stroking or filling a shape. + /// + /// Defaults to black. + /// + /// See also: + /// + /// * [style], which controls whether to stroke or fill (or both). + /// * [colorFilter], which overrides [color]. + /// * [shader], which overrides [color] with more elaborate effects. + /// + /// This color is not used when compositing. To colorize a layer, use + /// [colorFilter]. Color color = _kDefaultPaintColor; static const Color _kDefaultPaintColor = const Color(0xFF000000); - TransferMode transferMode; - - ColorFilter colorFilter; - + /// A mask filter (for example, a blur) to apply to a shape after it has been + /// drawn but before it has been composited into the image. + /// + /// See [MaskFilter] for details. MaskFilter maskFilter; + /// Controls the performance vs quality trade-off to use when applying + /// filters, such as [maskFilter], or when drawing images, as with + /// [Canvas.drawImageRect] or [Canvas.drawImageNine]. + // TODO(ianh): verify that the image drawing methods actually respect this FilterQuality filterQuality; + /// The shader to use when stroking or filling a shape. + /// + /// When this is null, the [color] is used instead. + /// + /// See also: + /// + /// * [Gradient], a shader that paints a color gradient. + /// * [ImageShader], a shader that tiles an [Image]. + /// * [colorFilter], which overrides [shader]. + /// * [color], which is used if [shader] and [colorFilter] are null. Shader shader; + /// A color filter to apply when a shape is drawn or when a layer is + /// composited. + /// + /// See [ColorFilter] for details. + /// + /// When a shape is being drawn, [colorFilter] overrides [color] and [shader]. + ColorFilter colorFilter; + + /// A transfer mode to apply when a shape is drawn or a layer is composited. + /// + /// The source colors are from the shape being drawn (e.g. from + /// [Canvas.drawPath]) or layer being composited (the graphics that were drawn + /// between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying + /// the [colorFilter], if any. + /// + /// The destination colors are from the background onto which the shape or + /// layer is being composited. + /// + /// If null, defaults to [TransferMode.srcOver]. + TransferMode transferMode; + static const TransferMode _kDefaultTransferMode = TransferMode.srcOver; + + // Must match PaintFields enum in Paint.cpp. dynamic get _value { // The most common usage is a Paint with no options besides a color and // anti-aliasing. In this case, save time by just returning the color // as an int. - if ((style == null || style == PaintingStyle.fill) && - (strokeWidth == null || strokeWidth == 0.0) && - (strokeCap == null || strokeCap == StrokeCap.butt) && + if ((style == null || style == _kDefaultStyle) && + (strokeWidth == null || strokeWidth == _kDefaultStrokeWidth) && + (strokeCap == null || strokeCap == _kDefaultStrokeCap) && isAntiAlias && color != null && - transferMode == null && + (transferMode == null || transferMode == _kDefaultTransferMode) && colorFilter == null && maskFilter == null && filterQuality == null && @@ -96,6 +148,7 @@ class Paint { ]; } + @override String toString() { StringBuffer result = new StringBuffer(); String semicolon = ''; @@ -106,7 +159,7 @@ class Paint { result.write(' $strokeWidth'); else result.write(' hairline'); - if (strokeCap != null && strokeCap != StrokeCap.butt) + if (strokeCap != null && strokeCap != _kDefaultStrokeCap) result.write(' $strokeCap'); semicolon = '; '; } diff --git a/sky/engine/core/painting/Point.dart b/sky/engine/core/painting/Point.dart index c9baf23514dfb..5317c6231b8b0 100644 --- a/sky/engine/core/painting/Point.dart +++ b/sky/engine/core/painting/Point.dart @@ -4,29 +4,77 @@ part of dart_ui; -/// Holds 2 floating-point coordinates. +/// An immutable 2D floating-point x,y coordinate pair. +/// +/// A Point represents a specific position in Cartesian space. +/// +/// Subtracting a point from another returns the [Offset] that represents the +/// vector between the two points. class Point { + /// Creates a point. The first argument sets [x], the horizontal component, + /// and the second sets [y], the vertical component. const Point(this.x, this.y); + /// The horizontal component of the point. final double x; + + /// The vertical component of the point. final double y; + /// The point at the origin, (0, 0). static const Point origin = const Point(0.0, 0.0); + /// Unary negation operator. Returns a point with the coordinates negated. Point operator -() => new Point(-x, -y); + + /// Binary subtraction operator. Returns an [Offset] representing the + /// direction and distance from the other point (the right-hand-side operand) + /// to this point (the left-hand-side operand). Offset operator -(Point other) => new Offset(x - other.x, y - other.y); + + /// Binary addition operator. Returns a point that is this point (the + /// left-hand-side operand) plus a vector [Offset] (the right-hand-side + /// operand). Point operator +(Offset other) => new Point(x + other.dx, y + other.dy); + + /// Rectangle constructor operator. Combines a point and a [Size] to form a + /// [Rect] whose top-left coordinate is this point, the left-hand-side + /// operand, and whose size is the right-hand-side operand. + /// + /// ```dart + /// Rect myRect = Point.origin & const Size(100.0, 100.0); + /// // same as: new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0) + /// ``` + /// + /// See also: [Offset.&] Rect operator &(Size other) => new Rect.fromLTWH(x, y, other.width, other.height); + /// Multiplication operator. Returns a point whose coordinates are the + /// coordinates of the left-hand-side operand (a Point) multiplied by the + /// scalar right-hand-side operand (a double). Point operator *(double operand) => new Point(x * operand, y * operand); + + /// Division operator. Returns a point whose coordinates are the + /// coordinates of the left-hand-side operand (a point) divided by the + /// scalar right-hand-side operand (a double). Point operator /(double operand) => new Point(x / operand, y / operand); + + /// Integer (truncating) division operator. Returns a point whose + /// coordinates are the coordinates of the left-hand-side operand (a point) + /// divided by the scalar right-hand-side operand (a double), rounded towards + /// zero. Point operator ~/(double operand) => new Point((x ~/ operand).toDouble(), (y ~/ operand).toDouble()); + + /// Modulo (remainder) operator. Returns a point whose coordinates are the + /// remainder of the coordinates of the left-hand-side operand (a point) + /// divided by the scalar right-hand-side operand (a double). Point operator %(double operand) => new Point(x % operand, y % operand); + /// Converts this point to an [Offset] with the same coordinates. // does the equivalent of "return this - Point.origin" Offset toOffset() => new Offset(x, y); - /// Linearly interpolate between two points + /// Linearly interpolate between two points. /// /// If either point is null, this function interpolates from [Point.origin]. static Point lerp(Point a, Point b, double t) { @@ -39,6 +87,7 @@ class Point { return new Point(lerpDouble(a.x, b.x, t), lerpDouble(a.y, b.y, t)); } + @override bool operator ==(dynamic other) { if (other is! Point) return false; @@ -47,7 +96,9 @@ class Point { y == typedOther.y; } + @override int get hashCode => hashValues(x, y); + @override String toString() => "Point(${x?.toStringAsFixed(1)}, ${y?.toStringAsFixed(1)})"; } diff --git a/sky/engine/core/painting/RRect.dart b/sky/engine/core/painting/RRect.dart index 0ec56bd28c08c..3bd8bbaa631e7 100644 --- a/sky/engine/core/painting/RRect.dart +++ b/sky/engine/core/painting/RRect.dart @@ -220,6 +220,7 @@ class RRect { ); } + @override bool operator ==(dynamic other) { if (identical(this, other)) return true; @@ -233,7 +234,9 @@ class RRect { return true; } + @override int get hashCode => hashList(_value); + @override String toString() => "RRect.fromLTRBXY(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, ${radiusX.toStringAsFixed(1)}, ${radiusY.toStringAsFixed(1)})"; } diff --git a/sky/engine/core/painting/RSTransform.dart b/sky/engine/core/painting/RSTransform.dart index 8d0baca9793a6..a6c3678336012 100644 --- a/sky/engine/core/painting/RSTransform.dart +++ b/sky/engine/core/painting/RSTransform.dart @@ -4,8 +4,36 @@ part of dart_ui; -// Modeled after Skia's SkRSXform +/// A transform consisting of a translation, a rotation, and a uniform scale. +/// +/// Used by [Canvas.drawAtlas]. This is a more efficient way to represent these +/// simple transformations than a full matrix. +// Modeled after Skia's SkRSXform. class RSTransform { + /// Creates an RSTransform. + /// + /// An [RSTransform] expresses the combination of a translation, a rotation + /// around a particular point, and a scale factor. + /// + /// The first argument, `scos`, is the cosine of the rotation, multiplied by + /// the scale factor. + /// + /// The second argument, `ssin`, is the sine of the rotation, multiplied by + /// that same scale factor. + /// + /// The third argument is the x coordinate of the translation, minus the + /// `scos` argument multiplied by the x-coordinate of the rotation point, plus + /// the `ssin` argument multiplied by the y-coordinate of the rotation point. + /// + /// The fourth argument is the y coordinate of the translation, minus the `ssin` + /// argument multiplied by the x-coordinate of the rotation point, minus the + /// `scos` argument multiplied by the y-coordinate of the rotation point. + /// + /// The [new RSTransform.fromComponents] method may be a simpler way to + /// construct these values. However, if there is a way to factor out the + /// computations of the sine and cosine of the rotation so that they can be + /// reused over multiple calls to this constructor, it may be more efficient + /// to directly use this constructor instead. RSTransform(double scos, double ssin, double tx, double ty) { _value ..[0] = scos @@ -14,9 +42,55 @@ class RSTransform { ..[3] = ty; } + /// Creates an RSTransform from its individual components. + /// + /// The `rotation` parameter gives the rotation in radians. + /// + /// The `scale` parameter describes the uniform scale factor. + /// + /// The `anchorX` and `anchorY` parameters give the coordinate of the point + /// around which to rotate. + /// + /// The `translateX` and `translateY` parameters give the coordinate of the + /// offset by which to translate. + /// + /// This constructor computes the arguments of the [new RSTransform] + /// constructor and then defers to that constructor to actually create the + /// object. If many [RSTransform] objects are being created and there is a way + /// to factor out the computations of the sine and cosine of the rotation + /// (which are computed each time this constructor is called) and reuse them + /// over multiple [RSTransform] objects, it may be more efficient to directly + /// use the more direct [new RSTransform] constructor instead. + factory RSTransform.fromComponents({ + double rotation, + double scale, + double anchorX, + double anchorY, + double translateX, + double translateY + }) { + final double scos = math.cos(rotation) * scale; + final double ssin = math.sin(rotation) * scale; + final double tx = translateX + -scos * anchorX + ssin * anchorY; + final double ty = translateY + -ssin * anchorX - scos * anchorY; + return new RSTransform(scos, ssin, tx, ty); + } + final Float32List _value = new Float32List(4); + + /// The cosine of the rotation multiplied by the scale factor. double get scos => _value[0]; + + /// The sine of the rotation multiplied by that same scale factor. double get ssin => _value[1]; + + /// The x coordinate of the translation, minus [scos] multiplied by the + /// x-coordinate of the rotation point, plus [ssin] multiplied by the + /// y-coordinate of the rotation point. double get tx => _value[2]; + + /// The y coordinate of the translation, minus [ssin] multiplied by the + /// x-coordinate of the rotation point, minus [scos] multiplied by the + /// y-coordinate of the rotation point. double get ty => _value[3]; } diff --git a/sky/engine/core/painting/Rect.dart b/sky/engine/core/painting/Rect.dart index 4e133649a16b1..3a40315f68839 100644 --- a/sky/engine/core/painting/Rect.dart +++ b/sky/engine/core/painting/Rect.dart @@ -19,6 +19,9 @@ class Rect { } /// Construct a rectangle from its left and top edges, its width, and its height. + /// + /// To construct a [Rect] from a [Point] or [Offset] and a [Size], you can use + /// the rectangle constructor operator `&`. See [Point.&] and [Offset.&]. Rect.fromLTWH(double left, double top, double width, double height) { _value ..[0] = left @@ -101,21 +104,53 @@ class Rect { return w < h ? w : h; } - /// The point halfway between the left and right and the top and bottom edges of this rectangle. - Point get center => new Point(left + width / 2.0, top + height / 2.0); /// The point at the intersection of the top and left edges of this rectangle. + /// + /// See also [Size.topLeft]. Point get topLeft => new Point(left, top); + /// The point at the center of the top edge of this rectangle. + /// + /// See also [Size.topCenter]. + Point get topCenter => new Point(left + width / 2.0, top); + /// The point at the intersection of the top and right edges of this rectangle. + /// + /// See also [Size.topRight]. Point get topRight => new Point(right, top); + /// The point at the center of the left edge of this rectangle. + /// + /// See also [Size.centerLeft]. + Point get centerLeft => new Point(left, top + height / 2.0); + + /// The point halfway between the left and right and the top and bottom edges of this rectangle. + /// + /// See also [Size.center]. + Point get center => new Point(left + width / 2.0, top + height / 2.0); + + /// The point at the center of the right edge of this rectangle. + /// + /// See also [Size.centerLeft]. + Point get centerRight => new Point(right, top + height / 2.0); + /// The point at the intersection of the bottom and left edges of this rectangle. + /// + /// See also [Size.bottomLeft]. Point get bottomLeft => new Point(left, bottom); + /// The point at the center of the bottom edge of this rectangle. + /// + /// See also [Size.bottomLeft]. + Point get bottomCenter => new Point(left + width / 2.0, bottom); + /// The point at the intersection of the bottom and right edges of this rectangle. + /// + /// See also [Size.bottomRight]. Point get bottomRight => new Point(right, bottom); + /// Whether the given point lies between the left and right and the top and bottom edges of this rectangle. /// /// Rectangles include their top and left edges but exclude their bottom and right edges. @@ -143,6 +178,7 @@ class Rect { ); } + @override bool operator ==(dynamic other) { if (identical(this, other)) return true; @@ -156,7 +192,9 @@ class Rect { return true; } + @override int get hashCode => hashList(_value); + @override String toString() => "Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})"; } diff --git a/sky/engine/core/painting/Size.dart b/sky/engine/core/painting/Size.dart index a09b4426ccc97..5ec026784a84f 100644 --- a/sky/engine/core/painting/Size.dart +++ b/sky/engine/core/painting/Size.dart @@ -5,38 +5,103 @@ part of dart_ui; /// Holds a 2D floating-point size. -/// Think of this as a vector from Point(0,0) to Point(size.width, size.height) +/// +/// You can think of this as a vector from Point(0,0) to Point(size.width, +/// size.height). class Size extends OffsetBase { + /// Creates a Size with the given width and height. const Size(double width, double height) : super(width, height); + + /// Creates an instance of Size that has the same values as another. + // Used by the rendering library's _DebugSize hack. Size.copy(Size source) : super(source.width, source.height); + + /// Creates a square Size whose width and height are the given dimension. + const Size.square(double dimension) : super(dimension, dimension); + + /// Creates a Size with the given width and an infinite height. const Size.fromWidth(double width) : super(width, double.INFINITY); + + /// Creates a Size with the given height and an infinite width. const Size.fromHeight(double height) : super(double.INFINITY, height); + + /// Creates a square Size whose width and height are twice the given dimension. + /// + /// This is a square that contains a circle with the given radius. const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0); + /// The horizontal extent of this size. double get width => _dx; + + /// The vertical extent of this size. double get height => _dy; + /// An empty size, one with a zero width and a zero height. static const Size zero = const Size(0.0, 0.0); + + /// A size whose width and height are infinite. + /// + /// See also [isInfinite], which checks whether either dimension is infinite. static const Size infinite = const Size(double.INFINITY, double.INFINITY); - dynamic operator -(dynamic other) { + /// Binary subtraction operator for Size. + /// + /// Subtracting a Size from a Size returns the [Offset] that describes how + /// much bigger the left-hand-side operand is than the right-hand-side + /// operand. Adding that resulting Offset to the Size that was the + /// right-hand-side operand would return a Size equal to the Size that was the + /// left-hand-side operand. (i.e. if `sizeA - sizeB -> offsetA`, then `offsetA + /// + sizeB -> sizeA`) + /// + /// Subtracting an [Offset] from a Size returns the Size that is smaller than + /// the Size operand by the difference given by the Offset operand. In other + /// words, the returned Size has a [width] consisting of the [width] of the + /// left-hand-side operand minus the [Offset.dx] dimension of the + /// right-hand-side operand, and a [height] consisting of the [height] of the + /// left-hand-side operand minus the [Offset.dy] dimension of the + /// right-hand-side operand. + dynamic operator -(OffsetBase other) { if (other is Size) return new Offset(width - other.width, height - other.height); if (other is Offset) return new Size(width - other.dx, height - other.dy); throw new ArgumentError(other); } + + /// Binary addition operator for adding an Offset to a Size. Returns a Size + /// whose [width] is the sum of the [width] of the left-hand-side operand, a + /// Size, and the [Offset.dx] dimension of the right-hand-side operand, an + /// [Offset], and whose [height] is the sum of the [height] of the + /// left-hand-side operand and the [Offset.dy] dimension of the + /// right-hand-side operand. Size operator +(Offset other) => new Size(width + other.dx, height + other.dy); + + /// Multiplication operator. Returns a size whose dimensions are the + /// dimensions of the left-hand-side operand (a Size) multiplied by the + /// scalar right-hand-side operand (a double). Size operator *(double operand) => new Size(width * operand, height * operand); + + /// Division operator. Returns a size whose dimensions are the dimensions of + /// the left-hand-side operand (a Size) divided by the scalar right-hand-side + /// operand (a double). Size operator /(double operand) => new Size(width / operand, height / operand); + + /// Integer (truncating) division operator. Returns a size whose dimensions + /// are the dimensions of the left-hand-side operand (a Size) divided by the + /// scalar right-hand-side operand (a double), rounded towards zero. Size operator ~/(double operand) => new Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble()); + + /// Modulo (remainder) operator. Returns a size whose dimensions are the + /// remainder of dividing the left-hand-side operand (a Size) by the scalar + /// right-hand-side operand (a double). Size operator %(double operand) => new Size(width % operand, height % operand); /// Whether this size encloses a non-zero area. + /// /// Negative areas are considered empty. bool get isEmpty => width <= 0.0 || height <= 0.0; - /// The lesser of the width and the height. + /// The lesser of the [width] and the [height]. double get shortestSide { double w = width.abs(); double h = height.abs(); @@ -45,10 +110,65 @@ class Size extends OffsetBase { // Convenience methods that do the equivalent of calling the similarly named // methods on a Rect constructed from the given origin and this size. - Point center(Point origin) => new Point(origin.x + width / 2.0, origin.y + height / 2.0); + + /// The point at the intersection of the top and left edges of the rectangle + /// described by the given point (which is interpreted as the top-left corner) + /// and this size. + /// + /// See also [Rect.topLeft]. Point topLeft(Point origin) => origin; + + /// The point at the center of the top edge of the rectangle described by the + /// given point (which is interpreted as the top-left corner) and this size. + /// + /// See also [Rect.topCenter]. + Point topCenter(Point origin) => new Point(origin.x + width / 2.0, origin.y); + + /// The point at the intersection of the top and right edges of the rectangle + /// described by the given point (which is interpreted as the top-left corner) + /// and this size. + /// + /// See also [Rect.topRight]. Point topRight(Point origin) => new Point(origin.x + width, origin.y); + + /// The point at the center of the left edge of the rectangle described by the + /// given point (which is interpreted as the top-left corner) and this size. + /// + /// See also [Rect.centerLeft]. + Point centerLeft(Point origin) => new Point(origin.x, origin.y + height / 2.0); + + /// The point halfway between the left and right and the top and bottom edges + /// of the rectangle described by the given point (which is interpreted as the + /// top-left corner) and this size. + /// + /// See also [Rect.center]. + Point center(Point origin) => new Point(origin.x + width / 2.0, origin.y + height / 2.0); + + /// The point at the center of the right edge of the rectangle described by the + /// given point (which is interpreted as the top-left corner) and this size. + /// + /// See also [Rect.centerLeft]. + Point centerRight(Point origin) => new Point(origin.x + width, origin.y + height / 2.0); + + /// The point at the intersection of the bottom and left edges of the + /// rectangle described by the given point (which is interpreted as the + /// top-left corner) and this size. + /// + /// See also [Rect.bottomLeft]. Point bottomLeft(Point origin) => new Point(origin.x, origin.y + height); + + /// The point at the center of the bottom edge of the rectangle described by + /// the given point (which is interpreted as the top-left corner) and this + /// size. + /// + /// See also [Rect.bottomLeft]. + Point bottomCenter(Point origin) => new Point(origin.x + width / 2.0, origin.y + height); + + /// The point at the intersection of the bottom and right edges of the + /// rectangle described by the given point (which is interpreted as the + /// top-left corner) and this size. + /// + /// See also [Rect.bottomRight]. Point bottomRight(Point origin) => new Point(origin.x + width, origin.y + height); /// Linearly interpolate between two sizes @@ -65,7 +185,9 @@ class Size extends OffsetBase { } /// Compares two Sizes for equality. + @override bool operator ==(dynamic other) => other is Size && super == other; + @override String toString() => "Size(${width?.toStringAsFixed(1)}, ${height?.toStringAsFixed(1)})"; } diff --git a/sky/engine/core/painting/TransferMode.dart b/sky/engine/core/painting/TransferMode.dart index 9fd48d43920e6..84b1478cd5055 100644 --- a/sky/engine/core/painting/TransferMode.dart +++ b/sky/engine/core/painting/TransferMode.dart @@ -14,7 +14,7 @@ part of dart_ui; /// can be used to blend the pixels. The image below shows the effects /// of these modes. /// -/// [![Open Skia fiddle to view image.](https://fiddle.skia.org/i/871ab434ce53a611e5eb2b662c69d154_raster.png)](https://fiddle.skia.org/c/871ab434ce53a611e5eb2b662c69d154) +/// [![Open Skia fiddle to view image.](https://flutter.io/images/transfer_mode.png)](https://fiddle.skia.org/c/864acd0659c7a866ea7296a3184b8bdd) /// /// See [Paint.transferMode]. enum TransferMode {