-
Notifications
You must be signed in to change notification settings - Fork 6k
Refactor ColorFilter to have a native wrapper #9668
Changes from 1 commit
ff4ed6b
4b820ed
8935830
1bfc97c
c8a8c35
884f0e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1056,13 +1056,10 @@ class Paint { | |
| static const int _kStrokeJoinIndex = 6; | ||
| static const int _kStrokeMiterLimitIndex = 7; | ||
| static const int _kFilterQualityIndex = 8; | ||
| static const int _kColorFilterIndex = 9; | ||
| static const int _kColorFilterColorIndex = 10; | ||
| static const int _kColorFilterBlendModeIndex = 11; | ||
| static const int _kMaskFilterIndex = 12; | ||
| static const int _kMaskFilterBlurStyleIndex = 13; | ||
| static const int _kMaskFilterSigmaIndex = 14; | ||
| static const int _kInvertColorIndex = 15; | ||
| static const int _kMaskFilterIndex = 9; | ||
| static const int _kMaskFilterBlurStyleIndex = 10; | ||
| static const int _kMaskFilterSigmaIndex = 11; | ||
| static const int _kInvertColorIndex = 12; | ||
|
|
||
| static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2; | ||
| static const int _kColorOffset = _kColorIndex << 2; | ||
|
|
@@ -1073,20 +1070,17 @@ class Paint { | |
| static const int _kStrokeJoinOffset = _kStrokeJoinIndex << 2; | ||
| static const int _kStrokeMiterLimitOffset = _kStrokeMiterLimitIndex << 2; | ||
| static const int _kFilterQualityOffset = _kFilterQualityIndex << 2; | ||
| static const int _kColorFilterOffset = _kColorFilterIndex << 2; | ||
| static const int _kColorFilterColorOffset = _kColorFilterColorIndex << 2; | ||
| static const int _kColorFilterBlendModeOffset = _kColorFilterBlendModeIndex << 2; | ||
| static const int _kMaskFilterOffset = _kMaskFilterIndex << 2; | ||
| static const int _kMaskFilterBlurStyleOffset = _kMaskFilterBlurStyleIndex << 2; | ||
| static const int _kMaskFilterSigmaOffset = _kMaskFilterSigmaIndex << 2; | ||
| static const int _kInvertColorOffset = _kInvertColorIndex << 2; | ||
| // If you add more fields, remember to update _kDataByteCount. | ||
| static const int _kDataByteCount = 75; | ||
| static const int _kDataByteCount = 52; | ||
|
|
||
| // Binary format must match the deserialization code in paint.cc. | ||
| List<dynamic> _objects; | ||
| static const int _kShaderIndex = 0; | ||
| static const int _kColorFilterMatrixIndex = 1; | ||
| static const int _kColorFilterIndex = 1; | ||
| static const int _kImageFilterIndex = 2; | ||
| static const int _kObjectCount = 3; // Must be one larger than the largest index. | ||
|
|
||
|
|
@@ -1342,48 +1336,23 @@ class Paint { | |
| /// | ||
| /// When a shape is being drawn, [colorFilter] overrides [color] and [shader]. | ||
| ColorFilter get colorFilter { | ||
| switch (_data.getInt32(_kColorFilterOffset, _kFakeHostEndian)) { | ||
| case ColorFilter._TypeNone: | ||
| return null; | ||
| case ColorFilter._TypeMode: | ||
| return ColorFilter.mode( | ||
| Color(_data.getInt32(_kColorFilterColorOffset, _kFakeHostEndian)), | ||
| BlendMode.values[_data.getInt32(_kColorFilterBlendModeOffset, _kFakeHostEndian)], | ||
| ); | ||
| case ColorFilter._TypeMatrix: | ||
| return ColorFilter.matrix(_objects[_kColorFilterMatrixIndex]); | ||
| case ColorFilter._TypeLinearToSrgbGamma: | ||
| return const ColorFilter.linearToSrgbGamma(); | ||
| case ColorFilter._TypeSrgbToLinearGamma: | ||
| return const ColorFilter.srgbToLinearGamma(); | ||
| if (_objects == null || _objects[_kColorFilterIndex] == null) { | ||
| return null; | ||
| } | ||
|
|
||
| return null; | ||
| return _objects[_kColorFilterIndex].creator; | ||
| } | ||
|
|
||
| set colorFilter(ColorFilter value) { | ||
| if (value == null) { | ||
| _data.setInt32(_kColorFilterOffset, ColorFilter._TypeNone, _kFakeHostEndian); | ||
| _data.setInt32(_kColorFilterColorOffset, 0, _kFakeHostEndian); | ||
| _data.setInt32(_kColorFilterBlendModeOffset, 0, _kFakeHostEndian); | ||
|
|
||
| if (_objects != null) { | ||
| _objects[_kColorFilterMatrixIndex] = null; | ||
| _objects[_kColorFilterIndex] = null; | ||
| } | ||
| } else { | ||
| _data.setInt32(_kColorFilterOffset, value._type, _kFakeHostEndian); | ||
|
|
||
| if (value._type == ColorFilter._TypeMode) { | ||
| assert(value._color != null); | ||
| assert(value._blendMode != null); | ||
|
|
||
| _data.setInt32(_kColorFilterColorOffset, value._color.value, _kFakeHostEndian); | ||
| _data.setInt32(_kColorFilterBlendModeOffset, value._blendMode.index, _kFakeHostEndian); | ||
| } else if (value._type == ColorFilter._TypeMatrix) { | ||
| assert(value._matrix != null); | ||
|
|
||
| _objects ??= List<dynamic>(_kObjectCount); | ||
| _objects[_kColorFilterMatrixIndex] = Float32List.fromList(value._matrix); | ||
| if (_objects == null) { | ||
| _objects = List<dynamic>(_kObjectCount); | ||
| _objects[_kColorFilterIndex] = value._toNativeColorFilter(); | ||
| } else if (_objects[_kColorFilterIndex]?.creator != value) { | ||
| _objects[_kColorFilterIndex] = value._toNativeColorFilter(); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -2520,7 +2489,9 @@ class ColorFilter { | |
| /// to the [Paint.blendMode], using the output of this filter as the source | ||
| /// and the background as the destination. | ||
| const ColorFilter.mode(Color color, BlendMode blendMode) | ||
| : _color = color, | ||
| : assert(color != null), | ||
| assert(blendMode != null), | ||
| _color = color, | ||
| _blendMode = blendMode, | ||
| _matrix = null, | ||
| _type = _TypeMode; | ||
|
|
@@ -2529,7 +2500,9 @@ class ColorFilter { | |
| /// matrix is in row-major order and the translation column is specified in | ||
| /// unnormalized, 0...255, space. | ||
| const ColorFilter.matrix(List<double> matrix) | ||
| : _color = null, | ||
| : assert(matrix != null), | ||
| assert(matrix.length == 20), | ||
| _color = null, | ||
| _blendMode = null, | ||
| _matrix = matrix, | ||
| _type = _TypeMatrix; | ||
|
|
@@ -2580,6 +2553,21 @@ class ColorFilter { | |
| return _color == typedOther._color && _blendMode == typedOther._blendMode; | ||
| } | ||
|
|
||
| _ColorFilter _toNativeColorFilter() { | ||
| switch (_type) { | ||
| case _TypeMode: | ||
| return _ColorFilter.mode(this); | ||
| case _TypeMatrix: | ||
| return _ColorFilter.matrix(this); | ||
| case _TypeLinearToSrgbGamma: | ||
| return _ColorFilter.linearToSrgbGamma(this); | ||
| case _TypeSrgbToLinearGamma: | ||
| return _ColorFilter.srgbToLinearGamma(this); | ||
| default: | ||
| throw StateError('Unknown mode $_type for ColorFilter.'); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
| int get hashCode => hashValues(_color, _blendMode, hashList(_matrix), _type); | ||
|
|
||
|
|
@@ -2600,6 +2588,48 @@ class ColorFilter { | |
| } | ||
| } | ||
|
|
||
| /// A [ColorFilter] that is backed by a native SkColorFilter. | ||
| // We could not make [ColorFilter] be this, because it would no longer be const | ||
| // constructible and would complicate the == and hashCode implementations. | ||
|
||
| class _ColorFilter extends NativeFieldWrapperClass2 { | ||
| _ColorFilter.mode(this.creator) | ||
| : assert(creator != null), | ||
| assert(creator._type == ColorFilter._TypeMode) { | ||
| _constructor(); | ||
| _initMode(creator._color.value, creator._blendMode.index); | ||
| } | ||
|
|
||
| _ColorFilter.matrix(this.creator) | ||
| : assert(creator != null), | ||
| assert(creator._type == ColorFilter._TypeMatrix) { | ||
| _constructor(); | ||
| _initMatrix(Float32List.fromList(creator._matrix)); | ||
| } | ||
| _ColorFilter.linearToSrgbGamma(this.creator) | ||
| : assert(creator != null), | ||
| assert(creator._type == ColorFilter._TypeLinearToSrgbGamma) { | ||
| _constructor(); | ||
| _initLinearToSrgbGamma(); | ||
| } | ||
|
|
||
| _ColorFilter.srgbToLinearGamma(this.creator) | ||
| : assert(creator != null), | ||
| assert(creator._type == ColorFilter._TypeSrgbToLinearGamma) { | ||
| _constructor(); | ||
| _initSrgbToLinearGamma(); | ||
| } | ||
|
|
||
| /// The original Dart object that created the native wrapper, which retains | ||
| /// the values used for the filter. | ||
| final ColorFilter creator; | ||
|
|
||
| void _constructor() native 'ColorFilter_constructor'; | ||
| void _initMode(int color, int blendMode) native 'ColorFilter_initMode'; | ||
| void _initMatrix(Float32List matrix) native 'ColorFilter_initMatrix'; | ||
| void _initLinearToSrgbGamma() native 'ColorFilter_initLinearToSrgbGamma'; | ||
| void _initSrgbToLinearGamma() native 'ColorFilter_initSrgToLinearGamma'; | ||
| } | ||
|
|
||
| /// A filter operation to apply to a raster image. | ||
| /// | ||
| /// See also: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #include "flutter/lib/ui/painting/color_filter.h" | ||
|
|
||
| #include "third_party/tonic/converter/dart_converter.h" | ||
| #include "third_party/tonic/dart_args.h" | ||
| #include "third_party/tonic/dart_binding_macros.h" | ||
| #include "third_party/tonic/dart_library_natives.h" | ||
|
|
||
| namespace flutter { | ||
|
|
||
| static void ColorFilter_constructor(Dart_NativeArguments args) { | ||
| DartCallConstructor(&ColorFilter::Create, args); | ||
| } | ||
|
|
||
| IMPLEMENT_WRAPPERTYPEINFO(ui, ColorFilter); | ||
|
|
||
| #define FOR_EACH_BINDING(V) \ | ||
| V(ColorFilter, initMode) \ | ||
| V(ColorFilter, initMatrix) \ | ||
| V(ColorFilter, initSrgbToLinearGamma) \ | ||
| V(ColorFilter, initLinearToSrgbGamma) | ||
|
|
||
| FOR_EACH_BINDING(DART_NATIVE_CALLBACK) | ||
|
|
||
| void ColorFilter::RegisterNatives(tonic::DartLibraryNatives* natives) { | ||
| natives->Register( | ||
| {{"ColorFilter_constructor", ColorFilter_constructor, 1, true}, | ||
| FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); | ||
| } | ||
|
|
||
| fml::RefPtr<ColorFilter> ColorFilter::Create() { | ||
| return fml::MakeRefCounted<ColorFilter>(); | ||
| } | ||
|
|
||
| void ColorFilter::initMode(int color, int blend_mode) { | ||
| filter_ = SkColorFilters::Blend(static_cast<SkColor>(color), | ||
| static_cast<SkBlendMode>(blend_mode)); | ||
| } | ||
|
|
||
| sk_sp<SkColorFilter> ColorFilter::MakeColorMatrixFilter255( | ||
| const float array[20]) { | ||
| float tmp[20]; | ||
| memcpy(tmp, array, sizeof(tmp)); | ||
| tmp[4] *= 1.0f / 255; | ||
| tmp[9] *= 1.0f / 255; | ||
| tmp[14] *= 1.0f / 255; | ||
| tmp[19] *= 1.0f / 255; | ||
| return SkColorFilters::Matrix(tmp); | ||
| } | ||
|
|
||
| void ColorFilter::initMatrix(const tonic::Float32List& color_matrix) { | ||
| FML_CHECK(color_matrix.num_elements() == 20); | ||
|
|
||
| filter_ = MakeColorMatrixFilter255(color_matrix.data()); | ||
| } | ||
|
|
||
| void ColorFilter::initLinearToSrgbGamma() { | ||
| filter_ = SkColorFilters::LinearToSRGBGamma(); | ||
| } | ||
|
|
||
| void ColorFilter::initSrgbToLinearGamma() { | ||
| filter_ = SkColorFilters::SRGBToLinearGamma(); | ||
| } | ||
|
|
||
| ColorFilter::~ColorFilter() = default; | ||
|
|
||
| } // namespace flutter |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #ifndef FLUTTER_LIB_UI_COLOR_FILTER_H_ | ||
| #define FLUTTER_LIB_UI_COLOR_FILTER_H_ | ||
|
|
||
| #include "flutter/lib/ui/dart_wrapper.h" | ||
| #include "third_party/skia/include/core/SkColorFilter.h" | ||
| #include "third_party/tonic/typed_data/typed_list.h" | ||
|
|
||
| using tonic::DartPersistentValue; | ||
|
|
||
| namespace tonic { | ||
| class DartLibraryNatives; | ||
| } // namespace tonic | ||
|
|
||
| namespace flutter { | ||
|
|
||
| // A handle to an SkCodec object. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is right? I don't see any SkCodec usage in this class |
||
| // | ||
| // Doesn't mirror SkCodec's API but provides a simple sequential access API. | ||
| class ColorFilter : public RefCountedDartWrappable<ColorFilter> { | ||
| DEFINE_WRAPPERTYPEINFO(); | ||
| FML_FRIEND_MAKE_REF_COUNTED(ColorFilter); | ||
|
|
||
| public: | ||
| static fml::RefPtr<ColorFilter> Create(); | ||
|
|
||
| // Flutter still defines the matrix to be biased by 255 in the last column | ||
| // (translate). skia is normalized, treating the last column as 0...1, so we | ||
| // post-scale here before calling the skia factory. | ||
| static sk_sp<SkColorFilter> MakeColorMatrixFilter255(const float array[20]); | ||
|
|
||
| void initMode(int color, int blend_mode); | ||
| void initMatrix(const tonic::Float32List& color_matrix); | ||
| void initSrgbToLinearGamma(); | ||
| void initLinearToSrgbGamma(); | ||
|
|
||
| ~ColorFilter() override; | ||
|
|
||
| sk_sp<SkColorFilter> filter() const { return filter_; } | ||
|
|
||
| static void RegisterNatives(tonic::DartLibraryNatives* natives); | ||
|
|
||
| private: | ||
| sk_sp<SkColorFilter> filter_; | ||
| }; | ||
|
|
||
| } // namespace flutter | ||
|
|
||
| #endif // FLUTTER_LIB_UI_COLOR_FILTER_H_ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the overall approach! My only concern is the test coverage. We could have a C++ unit test to cover paint.cc.
It might be a little more challenging to add a unit test for the Dart code. I wonder if we can use the fixture similar to the
shell_test.dartthat used in https://github.com/flutter/engine/blob/master/shell/common/shell_unittests.cc#L156. It allows the C++ unit test to specify a Dart function as the entry point, and a C++ native callback. I've used it to set aonReportTimingscallback on the Dart side, and check its results on the C++ side (https://github.com/flutter/engine/blob/master/shell/common/shell_unittests.cc#L315).I think it would be nice if we can create the
ColorFilteron the Dart side, and check theSkColorFilterobject is properly set on the C++ side. CC @chinmaygarde who might have more suggestions how to do this kind of Dart fixture test.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added dart tests that will serve to test the C++ side, and that cover the same code. LMK if you think we need more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8935830