Skip to content

Commit

Permalink
Complete blit.
Browse files Browse the repository at this point in the history
  • Loading branch information
matanlurey committed Aug 23, 2024
1 parent 204a54a commit 043e9be
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 121 deletions.
1 change: 1 addition & 0 deletions lib/pxl.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'package:pxl/src/blend.dart';
export 'package:pxl/src/buffer.dart';
export 'package:pxl/src/format.dart';
export 'package:pxl/src/geometry.dart';
123 changes: 122 additions & 1 deletion lib/src/blend.dart
Original file line number Diff line number Diff line change
@@ -1 +1,122 @@
abstract interface class BlendMode {}
import 'dart:typed_data';

import 'package:meta/meta.dart';
import 'package:pxl/src/format.dart';
import 'package:pxl/src/internal.dart';

part 'blend/porter_duff.dart';

/// Algorithms to use when painting on the canvas.
///
/// When drawing a shape or image onto a canvas, different algorithms can be
/// used to blend the pixels. A custom [BlendMode] can be used to implement
/// custom blending algorithms, or one of the predefined [BlendMode]s can be
/// used.
///
/// Each algorithm takes two colors as input, the _source_ color, which is the
/// color being drawn, and the _destination_ color, which is the color already
/// on the canvas. The algorithm then returns a new color that is the result of
/// blending the two colors.
///
/// ## SIMD
///
/// Some blend modes can optionally use [SIMD optimizations][] by setting the
/// `pxl.SIMD` Dart compilation environment variable:
///
/// [SIMD optimizations]: https://en.wikipedia.org/wiki/SIMD
///
/// ```sh
/// dart compile -Dpxl.SIMD=true
/// ```
///
/// This will use the [Float32x4] class to perform the blending calculations,
/// which can be faster than using scalar but should be tested for performance
/// and correctness.
///
/// See <https://dart.dev/guides/environment-declarations> for more details.
@immutable
abstract mixin class BlendMode {
/// Destination pixels covered by the source are cleared to 0.
static const BlendMode clear = PorterDuff(
PorterDuff.zero,
PorterDuff.zero,
);

/// Destination pixels are replaced by the source pixels.
static const BlendMode src = PorterDuff(
PorterDuff.one,
PorterDuff.zero,
);

/// Source pixels are replaced by the destination pixels.
static const BlendMode dst = PorterDuff(
PorterDuff.zero,
PorterDuff.one,
);

/// The source color is placed over the destination color.
static const BlendMode srcOver = PorterDuff(
PorterDuff.one,
PorterDuff.oneMinusSrc,
);

/// The destination color is placed over the source color.
static const BlendMode dstOver = PorterDuff(
PorterDuff.oneMinusDst,
PorterDuff.one,
);

/// The source that overlaps the destination replaces the destination.
static const BlendMode srcIn = PorterDuff(
PorterDuff.dst,
PorterDuff.zero,
);

/// The destination that overlaps the source replaces the source.
static const BlendMode dstIn = PorterDuff(
PorterDuff.zero,
PorterDuff.src,
);

/// The source that does not overlap the destination replaces the destination.
static const BlendMode srcOut = PorterDuff(
PorterDuff.oneMinusDst,
PorterDuff.zero,
);

/// The destination that does not overlap the source replaces the source.
static const BlendMode dstOut = PorterDuff(
PorterDuff.zero,
PorterDuff.oneMinusSrc,
);

/// The source that overlaps the destination is blended with the destination.
static const BlendMode srcAtop = PorterDuff(
PorterDuff.dst,
PorterDuff.oneMinusSrc,
);

/// The destination that overlaps the source is blended with the source.
static const BlendMode dstAtop = PorterDuff(
PorterDuff.oneMinusDst,
PorterDuff.src,
);

/// The non-overlapping regions of the source and destination are combined.
static const BlendMode xor = PorterDuff(
PorterDuff.oneMinusDst,
PorterDuff.oneMinusSrc,
);

/// The source and destination regions are added together.
static const BlendMode plus = PorterDuff(
PorterDuff.one,
PorterDuff.one,
);

/// Returns a function that blends two pixels together.
T Function(S src, T dst) getBlend<S, T>(
PixelFormat<S, void> srcFormat,
PixelFormat<T, void> dstFormat,
);
}
95 changes: 95 additions & 0 deletions lib/src/blend/porter_duff.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
part of '../blend.dart';

/// A [BlendMode] that uses [Porter-Duff coefficients][1] to blend colors.
///
/// [1]: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
///
/// A custom [BlendMode] can be created by providing custom coefficients to the
/// [PorterDuff] constructor:
///
/// ```dart
/// // Creates a custom blend mode that always returns zero.
/// final customBlendMode = PorterDuff(PorterDuff.zero, PorterDuff.one);
/// ```
final class PorterDuff with BlendMode {
/// Always returns zero (`0.0`).
static double zero(double srcAlpha, double dstAlpha) => 0;

/// Always returns one (`1.0`).
static double one(double srcAlpha, double dstAlpha) => 1;

/// Returns the source alpha channel (`srcAlpha`).
static double src(double srcAlpha, double dstAlpha) => srcAlpha;

/// Returns the destination alpha channel (`dstAlpha`).
static double dst(double srcAlpha, double dstAlpha) => dstAlpha;

/// Returns one minus the source alpha channel (`1 - srcAlpha`).
static double oneMinusSrc(double srcAlpha, double dstAlpha) => 1 - srcAlpha;

/// Returns one minus the destination alpha channel (`1 - dstAlpha`).
static double oneMinusDst(double srcAlpha, double dstAlpha) => 1 - dstAlpha;

/// Creates a blend mode with [Porter-Duff coefficients][1].
///
/// [1]: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
///
/// The [src] and [dst] functions are used to calculate the source and
/// destination coefficients, respectively. The result of blending the source
/// and destination colors is calculated as:
///
/// ```dart
/// final srcAlpha = src(src.w, dst.w);
/// final dstAlpha = dst(src.w, dst.w);
/// return src * srcAlpha + dst * dstAlpha;
/// ```
const PorterDuff(this._src, this._dst);
final double Function(double, double) _src;
final double Function(double, double) _dst;

@override
T Function(S src, T dst) getBlend<S, T>(
PixelFormat<S, void> srcFormat,
PixelFormat<T, void> dstFormat,
) {
return (src, dst) {
final srcRgba = srcFormat.toFloatRgba(src);
final dstRgba = dstFormat.toFloatRgba(dst);
final result = _blendFloatRgba(srcRgba, dstRgba);
return dstFormat.fromFloatRgba(result);
};
}

Float32x4 _blendFloatRgba(Float32x4 src, Float32x4 dst) {
return useSimd
? _blendFloat32x4SIMD(src, dst)
: _blendFloat32x4Scalar(src, dst);
}

Float32x4 _blendFloat32x4Scalar(Float32x4 src, Float32x4 dst) {
final Float32x4(
x: sr,
y: sg,
z: sb,
w: sa,
) = src;
final Float32x4(
x: dr,
y: dg,
z: db,
w: da,
) = dst;

final r = _src(sa, da) * sr + _dst(sa, da) * dr;
final g = _src(sa, da) * sg + _dst(sa, da) * dg;
final b = _src(sa, da) * sb + _dst(sa, da) * db;
final a = _src(sa, da) * sa + _dst(sa, da) * da;
return Float32x4(r, g, b, a);
}

Float32x4 _blendFloat32x4SIMD(Float32x4 src, Float32x4 dst) {
final srcA = Float32x4.splat(_src(src.w, dst.w));
final dstA = Float32x4.splat(_dst(src.w, dst.w));
return src * srcA + dst * dstA;
}
}
1 change: 1 addition & 0 deletions lib/src/buffer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:typed_data';

import 'package:pxl/src/blend.dart';
import 'package:pxl/src/format.dart';
import 'package:pxl/src/geometry.dart';
import 'package:pxl/src/internal.dart';
Expand Down
Loading

0 comments on commit 043e9be

Please sign in to comment.