Skip to content

Commit

Permalink
remove Sampling extensions from Flutter API, but remove as much relia…
Browse files Browse the repository at this point in the history
…nce on SkFilterQuality as can currently be achieved
  • Loading branch information
flar committed Mar 19, 2021
1 parent 7ee51f9 commit ef24722
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 362 deletions.
4 changes: 2 additions & 2 deletions lib/ui/compositing.dart
Expand Up @@ -545,7 +545,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
maskRect.top,
maskRect.bottom,
blendMode.index,
filterQuality._nativeIndex,
filterQuality.index,
oldLayer?._nativeLayer,
);
final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(engineLayer);
Expand Down Expand Up @@ -731,7 +731,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
FilterQuality filterQuality = FilterQuality.low,
}) {
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
_addTexture(offset.dx, offset.dy, width, height, textureId, freeze, filterQuality._nativeIndex);
_addTexture(offset.dx, offset.dy, width, height, textureId, freeze, filterQuality.index);
}

void _addTexture(double dx, double dy, double width, double height, int textureId, bool freeze,
Expand Down
5 changes: 2 additions & 3 deletions lib/ui/compositing/scene_builder.cc
Expand Up @@ -232,8 +232,7 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle,
fml::RefPtr<EngineLayer> oldLayer) {
SkRect rect = SkRect::MakeLTRB(maskRectLeft, maskRectTop, maskRectRight,
maskRectBottom);
SkSamplingOptions sampling =
ImageFilter::fromFilterQualityIndex(filterQualityIndex);
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
auto layer = std::make_shared<flutter::ShaderMaskLayer>(
shader->shader(sampling), rect, static_cast<SkBlendMode>(blendMode));
PushLayer(layer);
Expand Down Expand Up @@ -288,7 +287,7 @@ void SceneBuilder::addTexture(double dx,
int64_t textureId,
bool freeze,
int filterQualityIndex) {
auto sampling = ImageFilter::fromFilterQualityIndex(filterQualityIndex);
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
auto layer = std::make_unique<flutter::TextureLayer>(
SkPoint::Make(dx, dy), SkSize::Make(width, height), textureId, freeze,
sampling);
Expand Down
207 changes: 41 additions & 166 deletions lib/ui/painting.dart
Expand Up @@ -823,72 +823,19 @@ enum BlendMode {
luminosity,
}

/// Method for sampling bitmaps when the bitmap pixels do not line up exactly on device pixels.
enum BitmapFilterSampling {
// This list comes from SkFilterMode in SkSamplingOptions.h and the values (order) should
// be kept in sync.

/// Choose the nearest pixel to the sample location.
///
/// This technique can make the image look blocky when scaled up in size.
nearest,

/// Linearly blend between the nearest 2 or 4 pixels to the sample location.
///
/// This technique is much smoother than [nearest] with only a modest performance impact
/// on modern hardware.
bilinear,
}

/// Method for using an optional mipmap to improve quality when scaling an image to less
/// than half of its original size.
///
/// A mipmap is a series of images at multiple resolutions - typically each half of the
/// next larger resolution - that allows a sampling algorithm to choose an image that is
/// closer to the rendering scale to reduce the amount of missing detail when scaling
/// down by ratios a lot less than 50%.
enum MipmapFilterSampling {
// This list comes from SkMipmapMode in SkSamplingOptions.h and the values (order) should
// be kept in sync.

/// Do not use a mipmap.
///
/// All samples will come from the full-sized version of the bitmap using any associated
/// [BitmapFilterSampling] specified.
none,

/// Choose the nearest scale level in the bitmap's mipmap.
///
/// First the operation chooses the mipmap that is closest in scale to the 2D scale at
/// the given sample position and then uses the specified [BitmapFilterSampling] to sample
/// from that chosen bitmap.
nearest,

/// Choose the two nearest scale levels in the bitmap's mipmap and linearly blend
/// matching pixel samples from each into a single result.
///
/// First the operation chooses the two mipmap levels that bracket the 2D scale at the
/// given sample position, uses the specified [BitmapFilterSampling] to get a sample from
/// each of those chosen bitmaps, and then linear interpolates between those two mipmap
/// samples depending on how close the sample scale was to the scales of each mipmap level.
bilinear,
}

/// Quality levels for image sampling in [ImageFilter] and [Shader] objects that sample
/// images and for [Canvas] operations that render images.
///
/// A few instances of this class are provided as class constants to make it easier
/// to quickly dial in a reasonable level of quality for most operations.
///
/// When scaling up typically the quality is lowest at [none], higher at [low] and [medium],
/// and the highest at [high].
/// and for very large scale factors (over 10x) the highest at [high].
///
/// When scaling down, the higher the scale factor the less effective the [high]
/// level is. Beyond a certain scale level [medium] may provide better visual
/// result than [high].
/// When scaling down, [medium] provides the best quality especially when scaling an
/// image to less than half its size or for animating the scale factor between such
/// reductions. Otherwise, [low] and [high] provide similar effects for reductions of
/// between 50% and 100% but the image may lose detail and have dropouts below 50%.
///
/// To get high quality when scaling images up and down, or when the scale is
/// unknown, [medium] is typically a good enough choice.
/// unknown, [medium] is typically a good balanced choice.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/filter_quality.png)
///
Expand All @@ -906,120 +853,52 @@ enum MipmapFilterSampling {
/// * [Canvas.drawImageRect].
/// * [Canvas.drawImageNine].
/// * [Canvas.drawAtlas].
abstract class FilterQuality {
enum FilterQuality {
// This list and the values (order) should be kept in sync with the equivalent list
// in lib/ui/painting/image_filter.cc

/// The fastest filtering method, albeit also the lowest quality.
///
/// This value is equivalent to a mipmap filter using
/// [BitmapFilterSampling.nearest] and [MipmapFilterSampling.none].
static const FilterQuality none = FilterQuality.mipmap(
bitmapSampling: BitmapFilterSampling.nearest,
mipmapSampling: MipmapFilterSampling.none,
);
/// This value results in a "Nearest Neighbor" algorithm which just
/// repeats or eliminates pixels as an image is scaled up or down.
none,

/// Better quality than [none], faster than [medium].
///
/// This value is equivalent to a mipmap filter using
/// [BitmapFilterSampling.bilinear] and [MipmapFilterSampling.none].
static const FilterQuality low = FilterQuality.mipmap(
bitmapSampling: BitmapFilterSampling.bilinear,
mipmapSampling: MipmapFilterSampling.none,
);
/// This value results in a "Bilinear" algorithm which smoothly
/// interpolates between pixels in an image.
low,

/// When scaling down images, this is frequently better than [high].
/// The best all around filtering method that is only worse than [high]
/// at extremely large scale factors.
///
/// This value is equivalent to a mipmap filter using
/// [BitmapFilterSampling.bilinear] and [MipmapFilterSampling.bilinear].
/// This value improves upon the "Bilinear" algorithm specified by [low]
/// by utilizing a Mipmap that pre-computes high quality lower resolutions
/// of the image at half (and quarter and eighth, etc.) sizes and then
/// blends between those to prevent loss of detail at small scale sizes.
///
/// {@template dart.ui.filterQuality.seeAlso}
/// See also:
///
/// * [FilterQuality] class-level documentation that goes into detail about
/// relative qualities of the constant values.
/// {@endtemplate}
static const FilterQuality medium = FilterQuality.mipmap(
bitmapSampling: BitmapFilterSampling.bilinear,
mipmapSampling: MipmapFilterSampling.bilinear,
);
medium,

/// Best possible quality when scaling up images.
/// Best possible quality when scaling up images by scale factors larger than
/// 5-10x.
///
/// When images are scaled down, this can be worse than [medium] for scales
/// smaller than 0.5x.
/// smaller than 0.5x, or when animating the scale factor.
///
/// This option is the slowest.
/// This option is also the slowest.
///
/// This value is equivalent to [FilterQuality.cubic].
/// This value results in a standard "Bicubic" algorithm which uses a 3rd order
/// equation to smooth the abrupt transitions between pixels while preserving
/// some of the sense of an edge and avoiding sharp peaks in the result.
///
/// {@macro dart.ui.filterQuality.seeAlso}
static const FilterQuality high = FilterQuality.cubic();

/// Creates a FilterQuality with the specified [BitmapFilterSampling] method and
/// an optional [MipmapFilterSampling] method.
const factory FilterQuality.mipmap({
required BitmapFilterSampling bitmapSampling,
MipmapFilterSampling mipmapSampling,
}) = _MipmapFilterQuality;

/// Creates a FilterQuality that specifies the use of a [Mitchell-Netravali cubic interpolation
/// function](https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters) with the standard
/// coefficients recommended by the authors of `(B=1.0/3.0, C=1.0/3.0)`.
const factory FilterQuality.cubic() = _CubicFilterQuality;

int get _nativeIndex;
}

// This list and the values (order) should be kept in sync with the equivalent list
// in lib/ui/painting/paint.cc
const List<FilterQuality> _filterQualitiesFromNative = <FilterQuality>[
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.nearest, mipmapSampling: MipmapFilterSampling.none),
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.bilinear, mipmapSampling: MipmapFilterSampling.none),
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.nearest, mipmapSampling: MipmapFilterSampling.nearest),
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.bilinear, mipmapSampling: MipmapFilterSampling.nearest),
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.nearest, mipmapSampling: MipmapFilterSampling.bilinear),
FilterQuality.mipmap(bitmapSampling: BitmapFilterSampling.bilinear, mipmapSampling: MipmapFilterSampling.bilinear),
FilterQuality.cubic(),
];

class _MipmapFilterQuality implements FilterQuality {
const _MipmapFilterQuality({ required this.bitmapSampling, this.mipmapSampling = MipmapFilterSampling.none });

final BitmapFilterSampling bitmapSampling;
final MipmapFilterSampling mipmapSampling;

@override
int get _nativeIndex {
final int index = mipmapSampling.index * 2 + bitmapSampling.index;
assert(_filterQualitiesFromNative[index] == this);
return index;
}

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _MipmapFilterQuality &&
runtimeType == other.runtimeType &&
bitmapSampling == other.bitmapSampling &&
mipmapSampling == other.mipmapSampling;

@override
int get hashCode => bitmapSampling.hashCode ^ mipmapSampling.hashCode;
}

class _CubicFilterQuality implements FilterQuality {
const _CubicFilterQuality();

@override
int get _nativeIndex {
assert(_filterQualitiesFromNative[6] == this);
return 6;
}

@override
bool operator ==(Object other) =>
identical(this, other) || other is _CubicFilterQuality && runtimeType == other.runtimeType;

@override
int get hashCode => 0;
high,
}

/// Styles to use for line endings.
Expand Down Expand Up @@ -1507,11 +1386,11 @@ class Paint {
/// Defaults to [FilterQuality.none].
// TODO(ianh): verify that the image drawing methods actually respect this
FilterQuality get filterQuality {
return _filterQualitiesFromNative[_data.getInt32(_kFilterQualityOffset, _kFakeHostEndian)];
return FilterQuality.values[_data.getInt32(_kFilterQualityOffset, _kFakeHostEndian)];
}
set filterQuality(FilterQuality value) {
assert(value != null); // ignore: unnecessary_null_comparison
final int encoded = value._nativeIndex;
final int encoded = value.index;
_data.setInt32(_kFilterQualityOffset, encoded, _kFakeHostEndian);
}

Expand Down Expand Up @@ -3475,7 +3354,7 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
if (filter.data.length != 16)
throw ArgumentError('"matrix4" must have 16 entries.');
_constructor();
_initMatrix(filter.data, filter.filterQuality._nativeIndex);
_initMatrix(filter.data, filter.filterQuality.index);
}
void _initMatrix(Float64List matrix4, int filterQuality) native 'ImageFilter_initMatrix';

Expand Down Expand Up @@ -3830,7 +3709,7 @@ class ImageShader extends Shader {
if (matrix4.length != 16)
throw ArgumentError('"matrix4" must have 16 entries.');
_constructor();
_initWithImage(image._image, tmx.index, tmy.index, filterQuality?._nativeIndex ?? -1, matrix4);
_initWithImage(image._image, tmx.index, tmy.index, filterQuality?.index ?? -1, matrix4);
}
void _constructor() native 'ImageShader_constructor';
void _initWithImage(_Image image, int tmx, int tmy, int filterQualityIndex, Float64List matrix4) native 'ImageShader_initWithImage';
Expand Down Expand Up @@ -4434,7 +4313,7 @@ class Canvas extends NativeFieldWrapperClass2 {
assert(image != null); // image is checked on the engine side
assert(_offsetIsValid(offset));
assert(paint != null); // ignore: unnecessary_null_comparison
_drawImage(image._image, offset.dx, offset.dy, paint._objects, paint._data, paint.filterQuality._nativeIndex);
_drawImage(image._image, offset.dx, offset.dy, paint._objects, paint._data, paint.filterQuality.index);
}
void _drawImage(_Image image,
double x,
Expand Down Expand Up @@ -4469,7 +4348,7 @@ class Canvas extends NativeFieldWrapperClass2 {
dst.bottom,
paint._objects,
paint._data,
paint.filterQuality._nativeIndex);
paint.filterQuality.index);
}
void _drawImageRect(_Image image,
double srcLeft,
Expand Down Expand Up @@ -4503,10 +4382,6 @@ class Canvas extends NativeFieldWrapperClass2 {
assert(_rectIsValid(center));
assert(_rectIsValid(dst));
assert(paint != null); // ignore: unnecessary_null_comparison
final BitmapFilterSampling sampling = (paint.filterQuality is _MipmapFilterQuality)
? (paint.filterQuality as _MipmapFilterQuality).bitmapSampling
: BitmapFilterSampling.bilinear;
// TODO: add filtering to public API, since native paint's quality is deprecated
_drawImageNine(image._image,
center.left,
center.top,
Expand All @@ -4518,7 +4393,7 @@ class Canvas extends NativeFieldWrapperClass2 {
dst.bottom,
paint._objects,
paint._data,
sampling.index);
paint.filterQuality.index);
}
void _drawImageNine(_Image image,
double centerLeft,
Expand All @@ -4531,7 +4406,7 @@ class Canvas extends NativeFieldWrapperClass2 {
double dstBottom,
List<dynamic>? paintObjects,
ByteData paintData,
int bitmapSamplingIndex) native 'Canvas_drawImageNine';
int filterQualityIndex) native 'Canvas_drawImageNine';

/// Draw the given picture onto the canvas. To create a picture, see
/// [PictureRecorder].
Expand Down Expand Up @@ -4796,7 +4671,7 @@ class Canvas extends NativeFieldWrapperClass2 {

final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors);
final Float32List? cullRectBuffer = cullRect?._value32;
final int qualityIndex = paint.filterQuality._nativeIndex;
final int qualityIndex = paint.filterQuality.index;

_drawAtlas(
paint._objects, paint._data, qualityIndex, atlas._image, rstTransformBuffer, rectBuffer,
Expand Down Expand Up @@ -4966,7 +4841,7 @@ class Canvas extends NativeFieldWrapperClass2 {
throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.');
if (colors != null && colors.length * 4 != rectCount)
throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
final int qualityIndex = paint.filterQuality._nativeIndex;
final int qualityIndex = paint.filterQuality.index;

_drawAtlas(
paint._objects, paint._data, qualityIndex, atlas._image, rstTransforms, rects,
Expand Down
11 changes: 4 additions & 7 deletions lib/ui/painting/canvas.cc
Expand Up @@ -326,8 +326,7 @@ void Canvas::drawImage(const CanvasImage* image,
ToDart("Canvas.drawImage called with non-genuine Image."));
return;
}
SkSamplingOptions sampling =
ImageFilter::fromFilterQualityIndex(filterQualityIndex);
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
canvas_->drawImage(image->image(), x, y, sampling, paint.paint());
}

Expand All @@ -353,8 +352,7 @@ void Canvas::drawImageRect(const CanvasImage* image,
}
SkRect src = SkRect::MakeLTRB(src_left, src_top, src_right, src_bottom);
SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
SkSamplingOptions sampling =
ImageFilter::fromFilterQualityIndex(filterQualityIndex);
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
canvas_->drawImageRect(image->image(), src, dst, sampling, paint.paint(),
SkCanvas::kFast_SrcRectConstraint);
}
Expand Down Expand Up @@ -384,7 +382,7 @@ void Canvas::drawImageNine(const CanvasImage* image,
SkIRect icenter;
center.round(&icenter);
SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
SkFilterMode filter = static_cast<SkFilterMode>(bitmapSamplingIndex);
auto filter = ImageFilter::FilterModeFromIndex(bitmapSamplingIndex);
canvas_->drawImageNine(image->image().get(), icenter, dst, filter,
paint.paint());
}
Expand Down Expand Up @@ -459,8 +457,7 @@ void Canvas::drawAtlas(const Paint& paint,
static_assert(sizeof(SkRect) == sizeof(float) * 4,
"SkRect doesn't use floats.");

SkSamplingOptions sampling =
ImageFilter::fromFilterQualityIndex(filterQualityIndex);
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);

canvas_->drawAtlas(
skImage.get(), reinterpret_cast<const SkRSXform*>(transforms.data()),
Expand Down

0 comments on commit ef24722

Please sign in to comment.