diff --git a/lib/src/buffer.dart b/lib/src/buffer.dart index 0b9ca73..4d909ef 100644 --- a/lib/src/buffer.dart +++ b/lib/src/buffer.dart @@ -12,6 +12,7 @@ part 'buffer/map.dart'; part 'buffer/pixels_float.dart'; part 'buffer/pixels_int.dart'; part 'buffer/pixels.dart'; +part 'buffer/scaled.dart'; /// A 2-dimensional _view_ of pixel data [T] in memory. /// @@ -171,6 +172,30 @@ abstract base mixin class Buffer { return _ClippedBuffer(this, result); } + /// Returns a lazy buffer that scales the buffer by the given factor. + /// + /// The returned buffer will have the same dimensions as the original buffer + /// multiplied by the scale factor. The scale factor must be greater than 0. + /// + /// ## Example + /// + /// ```dart + /// final buffer = IntPixels(2, 2, data: Uint32List.fromList([ + /// abgr8888.red, abgr8888.green, + /// abgr8888.blue, abgr8888.magenta, + /// ])); + /// + /// final scaled = buffer.mapScaled(2); + /// print(scaled.data); // [0xFFFF0000, 0xFFFF0000, 0xFF00FF00, 0xFF00FF00, 0xFF0000FF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF00FF] + /// ``` + Buffer mapScaled(int scale) { + RangeError.checkNotNegative(scale, 'scale'); + if (scale == 1) { + return this; + } + return _ScaledBuffer(this, scale); + } + /// Returns a lazy iterable of pixels in the buffer from [start] to [end]. /// /// The returned iterable will contain all pixels in the buffer that are diff --git a/lib/src/buffer/scaled.dart b/lib/src/buffer/scaled.dart new file mode 100644 index 0000000..192d4c5 --- /dev/null +++ b/lib/src/buffer/scaled.dart @@ -0,0 +1,26 @@ +part of '../buffer.dart'; + +final class _ScaledBuffer extends _Buffer { + const _ScaledBuffer(super._source, this._scale); + final int _scale; + + @override + Iterable get data { + return Iterable.generate(length, (i) { + final pos = Pos(i % width, i ~/ width); + return getUnsafe(pos); + }); + } + + @override + T getUnsafe(Pos pos) { + final sourcePos = pos ~/ _scale; + return _source.getUnsafe(sourcePos); + } + + @override + int get width => _source.width * _scale; + + @override + int get height => _source.height * _scale; +} diff --git a/test/buffer_test.dart b/test/buffer_test.dart index 3b23103..ed60bac 100644 --- a/test/buffer_test.dart +++ b/test/buffer_test.dart @@ -116,6 +116,40 @@ void main() { ).throws(); }); + test('mapScaled', () { + final buffer = IntPixels( + 2, + 2, + data: Uint32List.fromList([ + abgr8888.red, + abgr8888.green, + abgr8888.blue, + abgr8888.cyan, + ]), + ); + final scaled = buffer.mapScaled(2); + check(scaled.width).equals(4); + check(scaled.height).equals(4); + check(scaled.data).deepEquals([ + abgr8888.red, + abgr8888.red, + abgr8888.green, + abgr8888.green, + abgr8888.red, + abgr8888.red, + abgr8888.green, + abgr8888.green, + abgr8888.blue, + abgr8888.blue, + abgr8888.cyan, + abgr8888.cyan, + abgr8888.blue, + abgr8888.blue, + abgr8888.cyan, + abgr8888.cyan, + ]); + }); + group('compare', () { test('different width returns difference = 1.0', () { final a = IntPixels(2, 2); diff --git a/test/golden/blit_with_embedded_font_and_scaling.png b/test/golden/blit_with_embedded_font_and_scaling.png new file mode 100644 index 0000000..bf92bfd Binary files /dev/null and b/test/golden/blit_with_embedded_font_and_scaling.png differ diff --git a/test/golden_test.dart b/test/golden_test.dart index 8bffb4b..ace0d30 100644 --- a/test/golden_test.dart +++ b/test/golden_test.dart @@ -159,4 +159,38 @@ void main() { uncompressedPngEncoder.convert(output), ); }); + + test('blit with embedded font and scaling', () { + // We are going to write the word "PXL" in the terminal8x8Font. + // The font is stored in code page 437 tile order. + final $P = Pos(00, 5) * 8; + final $X = Pos(08, 5) * 8; + final $L = Pos(12, 4) * 8; + + final output = IntPixels(24 * 4, 8 * 4)..fill(abgr8888.black); + final scaled = terminal8x8Font.mapScaled(4); + + output.blit( + scaled, + target: Pos(00, 00), + source: Rect.fromWH(8 * 4, 8 * 4, offset: $P * 4), + ); + + output.blit( + scaled, + target: Pos(08 * 4, 00), + source: Rect.fromWH(8 * 4, 8 * 4, offset: $X * 4), + ); + + output.blit( + scaled, + target: Pos(16 * 4, 00), + source: Rect.fromWH(8 * 4, 8 * 4, offset: $L * 4), + ); + + checkOrUpdateGolden( + 'blit_with_embedded_font_and_scaling', + uncompressedPngEncoder.convert(output), + ); + }); }