blob: 3bd9f79968edf6492228b614dc7dfa77392b0bb9 [file] [log] [blame]
// 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.
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;
import '../color_filter.dart';
import '../shader_data.dart';
import '../vector_math.dart';
import 'canvaskit_api.dart';
import 'color_filter.dart';
import 'image_filter.dart';
import 'mask_filter.dart';
import 'native_memory.dart';
import 'shader.dart';
/// The implementation of [ui.Paint] used by the CanvasKit backend.
///
/// This class is backed by a Skia object that must be explicitly
/// deleted to avoid a memory leak. This is done by extending [SkiaObject].
class CkPaint implements ui.Paint {
CkPaint() : skiaObject = SkPaint() {
skiaObject.setAntiAlias(_isAntiAlias);
skiaObject.setColorInt(_defaultPaintColor);
_ref = UniqueRef<SkPaint>(this, skiaObject, 'Paint');
}
final SkPaint skiaObject;
late final UniqueRef<SkPaint> _ref;
CkManagedSkImageFilterConvertible? _imageFilter;
static const int _defaultPaintColor = 0xFF000000;
/// Returns the native reference to the underlying [SkPaint] object.
///
/// This should only be used in tests.
@visibleForTesting
UniqueRef<SkPaint> get debugRef => _ref;
@override
ui.BlendMode get blendMode => _blendMode;
@override
set blendMode(ui.BlendMode value) {
if (_blendMode == value) {
return;
}
_blendMode = value;
skiaObject.setBlendMode(toSkBlendMode(value));
}
ui.BlendMode _blendMode = ui.BlendMode.srcOver;
@override
ui.PaintingStyle get style => _style;
@override
set style(ui.PaintingStyle value) {
if (_style == value) {
return;
}
_style = value;
skiaObject.setStyle(toSkPaintStyle(value));
}
ui.PaintingStyle _style = ui.PaintingStyle.fill;
@override
double get strokeWidth => _strokeWidth;
@override
set strokeWidth(double value) {
if (_strokeWidth == value) {
return;
}
_strokeWidth = value;
skiaObject.setStrokeWidth(value);
}
double _strokeWidth = 0.0;
@override
ui.StrokeCap get strokeCap => _strokeCap;
@override
set strokeCap(ui.StrokeCap value) {
if (_strokeCap == value) {
return;
}
_strokeCap = value;
skiaObject.setStrokeCap(toSkStrokeCap(value));
}
ui.StrokeCap _strokeCap = ui.StrokeCap.butt;
@override
ui.StrokeJoin get strokeJoin => _strokeJoin;
@override
set strokeJoin(ui.StrokeJoin value) {
if (_strokeJoin == value) {
return;
}
_strokeJoin = value;
skiaObject.setStrokeJoin(toSkStrokeJoin(value));
}
ui.StrokeJoin _strokeJoin = ui.StrokeJoin.miter;
@override
bool get isAntiAlias => _isAntiAlias;
@override
set isAntiAlias(bool value) {
if (_isAntiAlias == value) {
return;
}
_isAntiAlias = value;
skiaObject.setAntiAlias(value);
}
bool _isAntiAlias = true;
@override
ui.Color get color => ui.Color(_color);
@override
set color(ui.Color value) {
if (_color == value.value) {
return;
}
_color = value.value;
skiaObject.setColorInt(value.value);
}
int _color = _defaultPaintColor;
@override
bool get invertColors => _invertColors;
@override
set invertColors(bool value) {
if (value == _invertColors) {
return;
}
if (!value) {
_effectiveColorFilter = _originalColorFilter;
_originalColorFilter = null;
} else {
_originalColorFilter = _effectiveColorFilter;
if (_effectiveColorFilter == null) {
_effectiveColorFilter = _invertColorFilter;
} else {
_effectiveColorFilter = ManagedSkColorFilter(
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
);
}
}
skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject);
_invertColors = value;
}
bool _invertColors = false;
// The original color filter before we inverted colors. If we set
// `invertColors` back to `false`, then restore this filter rather than
// invert the color filter again.
ManagedSkColorFilter? _originalColorFilter;
@override
ui.Shader? get shader => _shader;
@override
set shader(ui.Shader? value) {
if (_shader == value) {
return;
}
_shader = value as CkShader?;
skiaObject.setShader(_shader?.getSkShader(_filterQuality));
}
CkShader? _shader;
@override
ui.MaskFilter? get maskFilter => _maskFilter;
@override
set maskFilter(ui.MaskFilter? value) {
if (value == _maskFilter) {
return;
}
_maskFilter = value;
if (value != null) {
// CanvasKit returns `null` if the sigma is `0` or infinite.
if (!(value.webOnlySigma.isFinite && value.webOnlySigma > 0)) {
// Don't create a [CkMaskFilter].
_ckMaskFilter = null;
} else {
_ckMaskFilter = CkMaskFilter.blur(
value.webOnlyBlurStyle,
value.webOnlySigma,
);
}
} else {
_ckMaskFilter = null;
}
skiaObject.setMaskFilter(_ckMaskFilter?.skiaObject);
}
ui.MaskFilter? _maskFilter;
CkMaskFilter? _ckMaskFilter;
@override
ui.FilterQuality get filterQuality => _filterQuality;
@override
set filterQuality(ui.FilterQuality value) {
if (_filterQuality == value) {
return;
}
_filterQuality = value;
skiaObject.setShader(_shader?.getSkShader(value));
}
ui.FilterQuality _filterQuality = ui.FilterQuality.none;
EngineColorFilter? _engineColorFilter;
@override
ui.ColorFilter? get colorFilter => _engineColorFilter;
@override
set colorFilter(ui.ColorFilter? value) {
if (_engineColorFilter == value) {
return;
}
_engineColorFilter = value as EngineColorFilter?;
_originalColorFilter = null;
if (value == null) {
_effectiveColorFilter = null;
} else {
final CkColorFilter ckColorFilter = createCkColorFilter(value)!;
_effectiveColorFilter = ManagedSkColorFilter(ckColorFilter);
}
if (invertColors) {
_originalColorFilter = _effectiveColorFilter;
if (_effectiveColorFilter == null) {
_effectiveColorFilter = _invertColorFilter;
} else {
_effectiveColorFilter = ManagedSkColorFilter(
CkComposeColorFilter(_invertColorFilter, _effectiveColorFilter!)
);
}
}
skiaObject.setColorFilter(_effectiveColorFilter?.skiaObject);
}
/// The effective color filter.
///
/// This is a combination of the `colorFilter` and `invertColors` properties.
ManagedSkColorFilter? _effectiveColorFilter;
@override
double get strokeMiterLimit => _strokeMiterLimit;
@override
set strokeMiterLimit(double value) {
if (_strokeMiterLimit == value) {
return;
}
_strokeMiterLimit = value;
skiaObject.setStrokeMiter(value);
}
double _strokeMiterLimit = 0.0;
@override
ui.ImageFilter? get imageFilter => _imageFilter;
@override
set imageFilter(ui.ImageFilter? value) {
if (_imageFilter == value) {
return;
}
final CkManagedSkImageFilterConvertible? filter;
if (value is ui.ColorFilter) {
filter = createCkColorFilter(value as EngineColorFilter);
}
else {
filter = value as CkManagedSkImageFilterConvertible?;
}
if (filter != null) {
filter.imageFilter((SkImageFilter skImageFilter) {
skiaObject.setImageFilter(skImageFilter);
});
}
_imageFilter = filter;
}
/// Disposes of this paint object.
///
/// This object cannot be used again after calling this method.
void dispose() {
_ref.dispose();
}
// Must be kept in sync with the default in paint.cc.
static const double _kStrokeMiterLimitDefault = 4.0;
// Must be kept in sync with the default in paint.cc.
static const int _kColorDefault = 0xFF000000;
// Must be kept in sync with the default in paint.cc.
static final int _kBlendModeDefault = ui.BlendMode.srcOver.index;
@override
String toString() {
String resultString = 'Paint()';
assert(() {
final StringBuffer result = StringBuffer();
String semicolon = '';
result.write('Paint(');
if (style == ui.PaintingStyle.stroke) {
result.write('$style');
if (strokeWidth != 0.0) {
result.write(' ${strokeWidth.toStringAsFixed(1)}');
} else {
result.write(' hairline');
}
if (strokeCap != ui.StrokeCap.butt) {
result.write(' $strokeCap');
}
if (strokeJoin == ui.StrokeJoin.miter) {
if (strokeMiterLimit != _kStrokeMiterLimitDefault) {
result.write(' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}');
}
} else {
result.write(' $strokeJoin');
}
semicolon = '; ';
}
if (!isAntiAlias) {
result.write('${semicolon}antialias off');
semicolon = '; ';
}
if (color != const ui.Color(_kColorDefault)) {
result.write('$semicolon$color');
semicolon = '; ';
}
if (blendMode.index != _kBlendModeDefault) {
result.write('$semicolon$blendMode');
semicolon = '; ';
}
if (colorFilter != null) {
result.write('${semicolon}colorFilter: $colorFilter');
semicolon = '; ';
}
if (maskFilter != null) {
result.write('${semicolon}maskFilter: $maskFilter');
semicolon = '; ';
}
if (filterQuality != ui.FilterQuality.none) {
result.write('${semicolon}filterQuality: $filterQuality');
semicolon = '; ';
}
if (shader != null) {
result.write('${semicolon}shader: $shader');
semicolon = '; ';
}
if (imageFilter != null) {
result.write('${semicolon}imageFilter: $imageFilter');
semicolon = '; ';
}
if (invertColors) {
result.write('${semicolon}invert: $invertColors');
}
result.write(')');
resultString = result.toString();
return true;
}());
return resultString;
}
}
final Float32List _invertColorMatrix = Float32List.fromList(const <double>[
-1.0, 0, 0, 1.0, 0, // row
0, -1.0, 0, 1.0, 0, // row
0, 0, -1.0, 1.0, 0, // row
1.0, 1.0, 1.0, 1.0, 0
]);
final ManagedSkColorFilter _invertColorFilter = ManagedSkColorFilter(CkMatrixColorFilter(_invertColorMatrix));
class CkFragmentProgram implements ui.FragmentProgram {
CkFragmentProgram(this.name, this.effect, this.uniforms, this.floatCount,
this.textureCount);
factory CkFragmentProgram.fromBytes(String name, Uint8List data) {
final ShaderData shaderData = ShaderData.fromBytes(data);
final SkRuntimeEffect? effect = MakeRuntimeEffect(shaderData.source);
if (effect == null) {
throw const FormatException('Invalid Shader Source');
}
return CkFragmentProgram(
name,
effect,
shaderData.uniforms,
shaderData.floatCount,
shaderData.textureCount,
);
}
final String name;
final SkRuntimeEffect effect;
final List<UniformData> uniforms;
final int floatCount;
final int textureCount;
@override
ui.FragmentShader fragmentShader() {
return CkFragmentShader(name, effect, floatCount, textureCount);
}
}
class CkFragmentShader implements ui.FragmentShader, CkShader {
CkFragmentShader(this.name, this.effect, int floatCount, int textureCount)
: floats = List<double>.filled(floatCount + textureCount * 2, 0),
samplers = List<SkShader?>.filled(textureCount, null),
lastFloatIndex = floatCount;
final String name;
final SkRuntimeEffect effect;
final int lastFloatIndex;
final List<double> floats;
final List<SkShader?> samplers;
@visibleForTesting
UniqueRef<SkShader>? ref;
@override
SkShader getSkShader(ui.FilterQuality contextualQuality) {
assert(!_debugDisposed, 'FragmentShader has been disposed of.');
ref?.dispose();
final SkShader? result = samplers.isEmpty
? effect.makeShader(floats)
: effect.makeShaderWithChildren(floats, samplers);
if (result == null) {
throw Exception('Invalid uniform data for shader $name:'
' floatUniforms: $floats \n'
' samplerUniforms: $samplers \n');
}
ref = UniqueRef<SkShader>(this, result, 'FragmentShader');
return result;
}
@override
void setFloat(int index, double value) {
assert(!_debugDisposed, 'FragmentShader has been disposed of.');
floats[index] = value;
}
@override
void setImageSampler(int index, ui.Image image) {
assert(!_debugDisposed, 'FragmentShader has been disposed of.');
final ui.ImageShader sampler = ui.ImageShader(image, ui.TileMode.clamp,
ui.TileMode.clamp, toMatrix64(Matrix4.identity().storage));
samplers[index] = (sampler as CkShader).getSkShader(ui.FilterQuality.none);
setFloat(lastFloatIndex + 2 * index, (sampler as CkImageShader).imageWidth.toDouble());
setFloat(lastFloatIndex + 2 * index + 1, sampler.imageHeight.toDouble());
}
@override
void dispose() {
assert(!_debugDisposed, 'Cannot dispose FragmentShader more than once.');
assert(() {
_debugDisposed = true;
return true;
}());
ref?.dispose();
ref = null;
}
bool _debugDisposed = false;
@override
bool get debugDisposed => _debugDisposed;
}