Skip to content

Commit

Permalink
Yet another rewrite with much cleaner SIMD blends.
Browse files Browse the repository at this point in the history
  • Loading branch information
matanlurey committed Aug 13, 2024
1 parent 680e9e1 commit 5e3e9bf
Show file tree
Hide file tree
Showing 31 changed files with 1,289 additions and 2,923 deletions.
456 changes: 135 additions & 321 deletions lib/src/blend.dart

Large diffs are not rendered by default.

190 changes: 6 additions & 184 deletions lib/src/buffer.dart
Original file line number Diff line number Diff line change
@@ -1,194 +1,16 @@
import 'dart:math' as math;
import 'dart:typed_data';

import 'package:lodim/lodim.dart';
import 'package:meta/meta.dart';
import 'package:pxl/src/blend.dart';
import 'package:pxl/src/color.dart';

part 'buffer/clipped.dart';
part 'buffer/empty.dart';
part 'buffer/mapped.dart';
part 'buffer/pixels.dart';
part 'buffer/rgba.dart';
part 'buffer/scaled.dart';
part 'buffer/vec4.dart';

/// Returns `height` if it is not `null`, otherwise calculates the height.
///
/// [length], [width], and [height] is checked to be non-negative and valid.
int _checkAndInferHeight({
required int length,
required int width,
int? height,
}) {
RangeError.checkNotNegative(width, 'width');
if (height == null) {
height = width != 0 ? length ~/ width : 0;
} else {
RangeError.checkNotNegative(height, 'height');
}
if (length != width * height) {
throw ArgumentError(
'The length of the pixel data must be equal to the product of the '
'width and height.',
);
}
return height;
}

/// A 2-dimensional view of a pixel-buffer like object with a fixed [width] and
/// [height].
///
/// Buffer is analgous to [Iterable] but for 2-dimensional pixel data, and
/// typically exists in order to provide a common interface for reading
/// pixel data from different sources, or providing a common interface for
/// compatibility.
///
/// See [Pixels] for a mutable concrete implementation of this interface.
abstract base mixin class Buffer<T extends Color> {
/// Creates an empty buffer with zero width and height.
///
/// This buffer is immutable and has no pixels.
const factory Buffer.empty() = _EmptyBuffer<T>;

/// The width of the buffer, in pixels.
/// TODO: Implement Buffer.
abstract final class Buffer<T extends Color> {
/// TODO: Replace stub.
int get width;

/// The height of the buffer, in pixels.
/// TODO: Replace stub.
int get height;

/// The total number of pixels in the buffer.
/// TODO: Replace stub.
int get length;

/// Returns the pixel at [index] in the buffer without bound checking.
///
/// ## Safety
///
/// {@template pxl:unsafe}
/// This method is intended for use in performance-sensitive code where the
/// parameters are known to be within bounds, otherwise it may result in
/// undefined behavior, including memory corruption and data loss.
/// {@endtemplate}
///
/// Prefer [operator []] for most operations.
/// TODO: Replace stub.
T uncheckedGet(int index);

/// Returns the pixel at [index] in the buffer.
///
/// The [index] must be in the range `0 ≤ index < length`.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
T operator [](int index) {
RangeError.checkValueInInterval(index, 0, length - 1, 'index');
return uncheckedGet(index);
}

/// Returns the index of the pixel at the given [position] in the buffer.
///
/// ## Safety
///
/// {@macro pxl:unsafe}
///
/// Prefer using [indexAt] for most operations.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
int uncheckedIndexAt(Pos position) {
return position.y * width + position.x;
}

/// Returns the index of the pixel at the given [position] in the buffer.
///
/// The [position] must be within the bounds of the buffer.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@nonVirtual
int indexAt(Pos position) {
if (!toBoundsRect().contains(position)) {
throw RangeError('position is out of bounds: $position');
}
return uncheckedIndexAt(position);
}

/// Returns a view of the buffer clipped to the given [bounds].
///
/// If the buffer is entirely out of bounds, an empty buffer is returned.
///
/// This is guaranteed to be a constant-time operation.
Buffer<T> clip(Rect bounds) => _ClippedBuffer(this, bounds);

/// Returns a view of the buffer scaled by the given factors.
///
/// Both [widthBy] and [heightBy] must be positive integers.
Buffer<T> scale({int widthBy = 1, int heightBy = 1}) {
if (widthBy < 1) {
throw ArgumentError.value(
widthBy,
'widthBy',
'must be greater than 0',
);
}
if (heightBy < 1) {
throw ArgumentError.value(
heightBy,
'heightBy',
'must be greater than 0',
);
}
return _ScaledBuffer(this, widthBy, heightBy);
}

/// Returns a view of the buffer mapped to the given function.
///
/// The [mapper] function is called for each pixel in the buffer.
///
/// The returned buffer has the same dimensions as the original buffer.
Buffer<R> map<R extends Color>(R Function(T color) mapper) {
return _MappedBuffer<T, R>(this, mapper);
}

/// Returns the bounds of the buffer.
///
/// If [clip] is provided, the bounds are clipped to the given rectangle.
///
/// If the buffer is entirely out of bounds, an empty rectangle is returned.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@nonVirtual
Rect toBoundsRect([Rect? clip]) {
final bounds = Rect.fromLTWH(0, 0, width, height);
return clip == null ? bounds : bounds.intersect(clip);
}

/// Converts and returns the buffer as a list of pixels in RGBA format.
///
/// The returned list is a copy of the buffer and can be modified without
/// affecting the original.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('dart2js:index-bounds:trust')
@pragma('vm:unsafe:no-bounds-checks')
Uint32List toRgba8888List() {
final list = Uint32List(length);
for (var i = 0; i < length; i++) {
list[i] = this.uncheckedGet(i).rgba.value;
}
return list;
}

/// Converts and returns the buffer as a list of pixels in Vec4 format.
///
/// The returned list is a copy of the buffer and can be modified without
/// affecting the original.
@pragma('dart2js:tryInline')
@pragma('vm:prefer-inline')
@pragma('dart2js:index-bounds:trust')
@pragma('vm:unsafe:no-bounds-checks')
Float32x4List toVec4List() {
final list = Float32x4List(length);
for (var i = 0; i < length; i++) {
list[i] = this.uncheckedGet(i).vec4.value;
}
return list;
}
}
31 changes: 0 additions & 31 deletions lib/src/buffer/clipped.dart

This file was deleted.

19 changes: 0 additions & 19 deletions lib/src/buffer/empty.dart

This file was deleted.

19 changes: 0 additions & 19 deletions lib/src/buffer/mapped.dart

This file was deleted.

Loading

0 comments on commit 5e3e9bf

Please sign in to comment.