diff --git a/lib/src/buffer/compare.dart b/lib/src/buffer/compare.dart index 8f4fa5c..874d657 100644 --- a/lib/src/buffer/compare.dart +++ b/lib/src/buffer/compare.dart @@ -1,6 +1,8 @@ part of '../buffer.dart'; /// The result of a pixel comparison test. +/// +/// {@category Output and Comparison} final class ComparisonResult { /// Compares two pixel buffers [a] and [b] and returns the result. factory ComparisonResult._compare( diff --git a/lib/src/buffer/pixels.dart b/lib/src/buffer/pixels.dart index 6e7c3e9..adec631 100644 --- a/lib/src/buffer/pixels.dart +++ b/lib/src/buffer/pixels.dart @@ -311,8 +311,8 @@ abstract final class Pixels with Buffer { return; } source = Rect.fromLTWH( - source.top, source.left, + source.top, clipped.width, clipped.height, ); @@ -457,8 +457,8 @@ abstract final class Pixels with Buffer { return; } source = Rect.fromLTWH( - source.top, source.left, + source.top, clipped.width, clipped.height, ); @@ -508,8 +508,8 @@ abstract final class Pixels with Buffer { return; } source = Rect.fromLTWH( - sRect.top, sRect.left, + sRect.top, tRect.width, tRect.height, ); @@ -557,6 +557,7 @@ abstract final class Pixels with Buffer { dstIdx++; } dstIdx += width - source.width; + srcIdx += from.width - source.width; } } } diff --git a/lib/src/codec.dart b/lib/src/codec.dart index 31f18df..66a0c89 100644 --- a/lib/src/codec.dart +++ b/lib/src/codec.dart @@ -1,2 +1,3 @@ export 'codec/netpbm.dart'; +export 'codec/packedbm.dart'; export 'codec/unpng.dart'; diff --git a/lib/src/codec/netpbm.dart b/lib/src/codec/netpbm.dart index 83f2284..e37d509 100644 --- a/lib/src/codec/netpbm.dart +++ b/lib/src/codec/netpbm.dart @@ -10,6 +10,114 @@ import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:pxl/pxl.dart'; +/// A codec that encodes and decodes pixel data as a portable pixmap (Netpbm) +/// image format. +/// +/// {@category Output and Comparison} +const netpbmAsciiCodec = NetpbmAsciiCodec._(); + +/// A codec that encodes and decodes pixel data as a portable pixmap (Netpbm) +/// image format. +/// +/// {@category Output and Comparison} +const netpbmBinaryCodec = NetpbmBinaryCodec._(); + +/// A codec that encodes and decodes pixel data as a portable pixmap (Netpbm) +/// image format. +/// +/// A singleton instance of this class is available as [netpbmAsciiCodec]. +/// +/// {@category Output and Comparison} +final class NetpbmAsciiCodec extends Codec, String> { + const NetpbmAsciiCodec._(); + + @override + NetpbmAsciiEncoder get encoder => netpbmAsciiEncoder; + + /// Encodes pixel data as an ASCII Netpbm image. + /// + /// The [comments] are added to the image. + /// + /// If the [format] is omitted, defaults to: + /// - [NetpbmFormat.graymap] for grayscale pixel data. + /// - [NetpbmFormat.pixmap] for RGB pixel data. + /// - [NetpbmFormat.bitmap] otherwise. + @override + String encode( + Buffer input, { + Iterable comments = const [], + NetpbmFormat? format, + }) { + return netpbmAsciiEncoder.convert( + input, + comments: comments, + format: format, + ); + } + + @override + NetpbmAsciiDecoder get decoder => netpbmAsciiDecoder; + + /// Decodes an ASCII Netpbm image to pixel data. + /// + /// If the input is invalid, a [FormatException] is thrown. + /// + /// The [format] is used to convert the pixel data to the desired format; + /// if omitted defaults to [abgr8888]. + @override + Buffer decode(String input, {PixelFormat? format}) { + return netpbmAsciiDecoder.convert(input, format: format); + } +} + +/// A codec that encodes and decodes pixel data as a portable pixmap (Netpbm) +/// image format. +/// +/// A singleton instance of this class is available as [netpbmBinaryCodec]. +/// +/// {@category Output and Comparison} +final class NetpbmBinaryCodec extends Codec, Uint8List> { + const NetpbmBinaryCodec._(); + + @override + NetpbmBinaryEncoder get encoder => netpbmBinaryEncoder; + + /// Encodes pixel data as a binary Netpbm image. + /// + /// The [comments] are added to the image. + /// + /// If the [format] is omitted, defaults to: + /// - [NetpbmFormat.graymap] for grayscale pixel data. + /// - [NetpbmFormat.pixmap] for RGB pixel data. + /// - [NetpbmFormat.bitmap] otherwise. + @override + Uint8List encode( + Buffer input, { + Iterable comments = const [], + NetpbmFormat? format, + }) { + return netpbmBinaryEncoder.convert( + input, + comments: comments, + format: format, + ); + } + + @override + NetpbmBinaryDecoder get decoder => netpbmBinaryDecoder; + + /// Decodes a binary Netpbm image to pixel data. + /// + /// If the input is invalid, a [FormatException] is thrown. + /// + /// The [format] is used to convert the pixel data to the desired format; + /// if omitted defaults to [abgr8888]. + @override + Buffer decode(Uint8List input, {PixelFormat? format}) { + return netpbmBinaryDecoder.convert(input, format: format); + } +} + /// Encodes pixel data as a portable pixmap (Netpbm) image format. /// /// {@macro pxl.netpbm_encoder.format} @@ -84,9 +192,19 @@ abstract final class NetpbmEncoder extends Converter, T> { /// {@endtemplate} final NetpbmFormat? format; + /// Converts the pixel data to a Netpbm image. + /// + /// The [comments] are added to the image. + /// + /// If the [format] is omitted, and [NetpbmEncoder.format] is not set, the + /// format is inferred from the pixel data. @override - T convert(Buffer input, {Iterable comments = const []}) { - final format = _getOrInferFormat(input); + T convert( + Buffer input, { + Iterable comments = const [], + NetpbmFormat? format, + }) { + format ??= _getOrInferFormat(input); final header = NetpbmHeader( width: input.width, height: input.height, @@ -391,13 +509,21 @@ abstract final class NetpbmDecoder extends Converter> { @protected List _data(T input, int offset, {required bool bitmap}); + /// Converts the input to integer-based pixel data. + /// + /// If the input is invalid, a [FormatException] is thrown. + /// + /// The [format] is used to convert the pixel data to the desired format; + /// if omitted, the decoder's [NetpbmDecoder.format] is used, which defaults + /// to [abgr8888]. @override @nonVirtual - Buffer convert(T input) { + Buffer convert(T input, {PixelFormat? format}) { final (header, error, offset) = _parseHeader(input); if (header == null) { throw FormatException(error, input); } + final fmt = format ?? this.format; final data = _data( input, offset, @@ -407,12 +533,12 @@ abstract final class NetpbmDecoder extends Converter> { switch (header.format) { case NetpbmFormat.bitmap: pixels = data.map((value) { - return value == 0x0 ? format.zero : format.max; + return value == 0x0 ? fmt.zero : fmt.max; }); case NetpbmFormat.graymap: pixels = data.map((value) { final gray = gray8.create(gray: value); - return format.convert(gray, from: gray8); + return fmt.convert(gray, from: gray8); }); case NetpbmFormat.pixmap: if (data.length % 3 != 0) { @@ -423,11 +549,11 @@ abstract final class NetpbmDecoder extends Converter> { final g = data[i * 3 + 1]; final b = data[i * 3 + 2]; final rgb = rgb888.create(red: r, green: g, blue: b); - return format.convert(rgb, from: rgb888); + return fmt.convert(rgb, from: rgb888); }); } - final buffer = IntPixels(header.width, header.height, format: format); + final buffer = IntPixels(header.width, header.height, format: fmt); buffer.data.setAll(0, pixels); return buffer; } @@ -519,6 +645,8 @@ final class NetpbmBinaryDecoder extends NetpbmDecoder { } /// Parsed header information from a Netpbm image. +/// +/// {@category Output and Comparison} @immutable final class NetpbmHeader { /// Creates a new Netpbm header with the given values. diff --git a/lib/src/codec/packedbm.dart b/lib/src/codec/packedbm.dart new file mode 100644 index 0000000..671d777 --- /dev/null +++ b/lib/src/codec/packedbm.dart @@ -0,0 +1,133 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:pxl/pxl.dart'; + +/// Codec for encoding and decoding packed bitmaps. +/// +/// A packed bitmap is a compact binary representation of a bitmap, where each +/// pixel is encoded as a single bit. This can be useful for storing large +/// bitmaps in a compact format, at the cost of some overhead to pack and +/// unpack the data. +const packedBitmapCodec = PackedBitmapCodec._(); + +/// Codec for encoding and decoding packed bitmaps. +final class PackedBitmapCodec extends Codec, List> { + const PackedBitmapCodec._(); + + @override + Converter, Uint32List> get encoder => packedBitmapEncoder; + + @override + Converter, Buffer> get decoder => packedBitmapDecoder; + + @override + Buffer decode( + List encoded, { + PixelFormat format = abgr8888, + }) { + return packedBitmapDecoder.convert(encoded, format: format); + } +} + +/// Encodes a bitmap to a compact (packed) binary format. +/// +/// See [PackedBitmapEncoder] for details. +const packedBitmapEncoder = PackedBitmapEncoder._(); + +/// Encodes a bitmap to a compact (packed) binary format. +/// +/// For a given buffer, [PixelFormat.zero] is encoded as `0`, and any other +/// value is encoded as `1`. 32 values are packed into a single byte, with the +/// least significant bit representing the first value. The first two 32-bit +/// words of the output are the width and height of the bitmap, respectively. +/// +/// For example, a 128x128 bitmap, which normally would require 2048 bytes, +/// can be encoded into 256 bytes (128 * 128 / 32), or a 8x reduction in size, +/// at the cost of some overhead to pack and unpack the data. +/// +/// A singleton instance of this class is available as [packedBitmapEncoder]. +final class PackedBitmapEncoder extends Converter, Uint32List> { + const PackedBitmapEncoder._(); + + @override + Uint32List convert(Buffer input) { + final width = input.width; + final height = input.height; + final data = input.data; + + // Convert every 32 pixels into 32-bits. + // That is, a 128x128 bitmap is represented as 4x4 32-bit words. + final output = Uint32List(2 + (data.length ~/ 32)); + output[0] = width; + output[1] = height; + + var word = 0; + var bit = 0; + var offset = 2; + for (final pixel in input.data) { + if (pixel != input.format.zero) { + word |= 1 << bit; + } + bit++; + if (bit == 32) { + output[offset] = word; + word = 0; + bit = 0; + offset++; + } + } + + return output; + } +} + +/// Decodes a bitmap from a compact (packed) binary format. +/// +/// See [PackedBitmapDecoder] for details. +const packedBitmapDecoder = PackedBitmapDecoder._(); + +/// Decodes a bitmap from a compact (packed) binary format. +/// +/// A `0` bit represents [PixelFormat.zero], and a `1` bit is [PixelFormat.max]; +/// 32 values are packed into a single byte, with the least significant bit +/// representing the first value. The first two 32-bit words of the input are +/// the width and height of the bitmap, respectively. +final class PackedBitmapDecoder extends Converter, Buffer> { + const PackedBitmapDecoder._(); + + @override + Buffer convert( + List input, { + PixelFormat format = abgr8888, + }) { + final Uint32List view; + if (input is TypedDataList) { + view = input.buffer.asUint32List(); + } else { + view = Uint32List.fromList(input); + } + final width = view[0]; + final height = view[1]; + final output = IntPixels(width, height, format: format); + + // Reverse of the above encoding. + var word = 0; + var bit = 0; + var offset = 2; + for (var i = 0; i < output.data.length; i++) { + if (bit == 0) { + word = view[offset]; + offset++; + } + output.data[i] = (word & 1) == 0 ? format.zero : format.max; + word >>= 1; + bit++; + if (bit == 32) { + bit = 0; + } + } + + return output; + } +} diff --git a/lib/src/format/grayscale.dart b/lib/src/format/grayscale.dart index 8b79ebf..bcdfa9c 100644 --- a/lib/src/format/grayscale.dart +++ b/lib/src/format/grayscale.dart @@ -1,6 +1,8 @@ part of '../format.dart'; /// A mixin for pixel formats that represent _graysacle_ pixels. +/// +/// {@category Pixel Formats} base mixin Grayscale implements PixelFormat { /// Creates a new pixel with the given channel values. /// diff --git a/pubspec.yaml b/pubspec.yaml index c48ab70..0be7e9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: dev_dependencies: checks: ^0.3.0 + image: ^4.2.0 oath: ^0.2.1 path: ^1.9.0 test: ^1.25.8 diff --git a/test/codec/netpbm_ascii_test.dart b/test/codec/netpbm_ascii_test.dart index c330f7b..fbb85ab 100644 --- a/test/codec/netpbm_ascii_test.dart +++ b/test/codec/netpbm_ascii_test.dart @@ -60,9 +60,10 @@ void main() { pixels.set(Pos(1, 0), abgr8888.white); pixels.set(Pos(2, 0), abgr8888.white); - final encoded = const NetpbmAsciiEncoder( + final encoded = netpbmAsciiEncoder.convert( + pixels, format: NetpbmFormat.bitmap, - ).convert(pixels); + ); check(encoded).equals( [ 'P1', @@ -102,7 +103,7 @@ void main() { ); // Try decoding the encoded string. - final decoded = NetpbmAsciiDecoder(format: monochrome).convert(encoded); + final decoded = netpbmAsciiDecoder.convert(encoded, format: monochrome); check(decoded.compare(pixels).difference).equals(0.0); }); @@ -113,9 +114,10 @@ void main() { pixels.set(Pos(1, 0), gray8.create(gray: 128)); pixels.set(Pos(2, 0), gray8.create(gray: 255)); - final encoded = const NetpbmAsciiEncoder( + final encoded = netpbmAsciiEncoder.convert( + pixels, format: NetpbmFormat.graymap, - ).convert(pixels); + ); check(encoded).equals( [ @@ -127,7 +129,7 @@ void main() { ); // Try decoding the encoded string. - final decoded = NetpbmAsciiDecoder(format: gray8).convert(encoded); + final decoded = netpbmAsciiDecoder.convert(encoded, format: gray8); check(decoded.format).equals(pixels.format); check(decoded.compare(pixels).difference).equals(0.0); }); diff --git a/test/codec/netpbm_binary_test.dart b/test/codec/netpbm_binary_test.dart index 371c05f..98b0f9f 100644 --- a/test/codec/netpbm_binary_test.dart +++ b/test/codec/netpbm_binary_test.dart @@ -69,9 +69,10 @@ void main() { pixels.set(Pos(1, 0), abgr8888.white); pixels.set(Pos(2, 0), abgr8888.white); - final encoded = const NetpbmBinaryEncoder( + final encoded = netpbmBinaryEncoder.convert( + pixels, format: NetpbmFormat.bitmap, - ).convert(pixels); + ); check(encoded).deepEquals( Uint8List.fromList([ ...utf8.encode('P4\n3 2\n'), @@ -109,7 +110,7 @@ void main() { ); // Try decoding the encoded string. - final decoded = NetpbmBinaryDecoder(format: monochrome).convert(encoded); + final decoded = netpbmBinaryDecoder.convert(encoded, format: monochrome); check(decoded.compare(pixels).difference).equals(0.0); }); @@ -132,7 +133,7 @@ void main() { ); // Try decoding the encoded string. - final decoded = NetpbmBinaryDecoder(format: gray8).convert(encoded); + final decoded = netpbmBinaryDecoder.convert(encoded, format: gray8); check(decoded.format).equals(pixels.format); check(decoded.compare(pixels).difference).equals(0.0); }); diff --git a/test/codec/packedbm_test.dart b/test/codec/packedbm_test.dart new file mode 100644 index 0000000..c039c3d --- /dev/null +++ b/test/codec/packedbm_test.dart @@ -0,0 +1,23 @@ +import 'package:pxl/pxl.dart'; + +import '../src/prelude.dart'; + +void main() { + test('encodes a 128x128 image compactly', () { + final input = IntPixels(128, 128); + + // Fill every other pixel with white. + for (var i = 0; i < input.data.length; i += 2) { + input.data[i] = abgr8888.white; + } + + final encoded = packedBitmapCodec.encoder.convert(input); + check(encoded).deepEquals([ + 128, 128, // Width and height. + ...Iterable.generate(128 * 128 ~/ 32, (i) => 0x55555555), + ]); + + final decoded = packedBitmapCodec.decoder.convert(encoded); + check(decoded.compare(input).difference).equals(0); + }); +} diff --git a/test/golden/blit_with_embedded_font.png b/test/golden/blit_with_embedded_font.png new file mode 100644 index 0000000..0a426bf Binary files /dev/null and b/test/golden/blit_with_embedded_font.png differ diff --git a/test/golden/copyFrom_with_embedded_font.png b/test/golden/copyFrom_with_embedded_font.png new file mode 100644 index 0000000..77fe4b8 Binary files /dev/null and b/test/golden/copyFrom_with_embedded_font.png differ diff --git a/test/golden/terminal8x8_font.png b/test/golden/terminal8x8_font.png new file mode 100644 index 0000000..49466cc Binary files /dev/null and b/test/golden/terminal8x8_font.png differ diff --git a/test/codec/unpng_golden_test.dart b/test/golden_test.dart similarity index 53% rename from test/codec/unpng_golden_test.dart rename to test/golden_test.dart index 2749b6c..8bffb4b 100644 --- a/test/codec/unpng_golden_test.dart +++ b/test/golden_test.dart @@ -5,7 +5,9 @@ import 'dart:io' as io; import 'dart:typed_data'; import 'package:path/path.dart' as p; import 'package:pxl/pxl.dart'; -import '../src/prelude.dart'; + +import 'src/embed.dart'; +import 'src/prelude.dart'; final _updateGoldens = () { return switch (io.Platform.environment['UPDATE_GOLDENS']?.toUpperCase()) { @@ -78,4 +80,83 @@ void main() { uncompressedPngEncoder.convert(image), ); }); + + test('embeded font', () { + final image = terminal8x8Font; + checkOrUpdateGolden( + 'terminal8x8_font', + uncompressedPngEncoder.convert(image), + ); + }); + + test('copyFrom with embedded font', () { + // 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, 8); + output.copyFrom( + terminal8x8Font, + source: Rect.fromWH(8, 8, offset: $P), + target: Pos(00, 00), + ); + + output.copyFrom( + terminal8x8Font, + source: Rect.fromWH(8, 8, offset: $X), + target: Pos(08, 00), + ); + + output.copyFrom( + terminal8x8Font, + source: Rect.fromWH(8, 8, offset: $L), + target: Pos(16, 00), + ); + + checkOrUpdateGolden( + 'copyFrom_with_embedded_font', + uncompressedPngEncoder.convert(output), + ); + }); + + test('blit with embedded font', () { + // 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, 8)..fill(abgr8888.magenta); + final input = IntPixels.from( + terminal8x8Font.map((i) { + // Make every white pixel semi-transparent blue; + return i == 0 ? i : abgr8888.create(blue: 0xFF, alpha: 0x80); + }), + ); + + output.blit( + input, + target: Pos(00, 00), + source: Rect.fromWH(8, 8, offset: $P), + ); + + output.blit( + input, + target: Pos(08, 00), + source: Rect.fromWH(8, 8, offset: $X), + ); + + output.blit( + input, + target: Pos(16, 00), + source: Rect.fromWH(8, 8, offset: $L), + ); + + checkOrUpdateGolden( + 'blit_with_embedded_font', + uncompressedPngEncoder.convert(output), + ); + }); } diff --git a/test/src/embed.dart b/test/src/embed.dart new file mode 100644 index 0000000..fca5990 --- /dev/null +++ b/test/src/embed.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; + +import 'package:pxl/pxl.dart'; + +/// A terminal font with 8x8 glyphs in code page 437 tile order. +/// +/// The font is encoded as a packed bitmap, where each pixel is encoded as a +/// single bit, which makes the size of the font relatively small, particularly +/// as it is (further) base64 encoded. +final terminal8x8Font = packedBitmapCodec.decoder.convert( + base64Decode(_packedBase648x8Font).buffer.asUint32List(), +); + +/// A [packedBitmapEncoder]'d 8x8 bitmap font. +/// +/// See `/tool/embed.dart` for the original font image. +const _packedBase648x8Font = + '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'; diff --git a/tool/embed.dart b/tool/embed.dart new file mode 100755 index 0000000..8c9151e --- /dev/null +++ b/tool/embed.dart @@ -0,0 +1,33 @@ +#!/usr/bin/env dart + +import 'dart:convert'; +import 'dart:io' as io; + +import 'package:image/image.dart'; +import 'package:path/path.dart' as p; +import 'package:pxl/pxl.dart'; + +void main() { + final file = io.File(p.join('tool', 'terminal8x8.png')); + final bytes = file.readAsBytesSync(); + final image = decodePng(bytes)!; + + // Create a buffer to write the image to. + final buffer = IntPixels(image.width, image.height); + + // Write the image to the buffer. + for (var y = 0; y < image.height; y++) { + for (var x = 0; x < image.width; x++) { + final color = image.getPixel(x, y); + if (color.r < 20 && color.g < 20 && color.b < 20) { + buffer.set(Pos(x, y), abgr8888.zero); + } else { + buffer.set(Pos(x, y), abgr8888.white); + } + } + } + + final output = packedBitmapEncoder.convert(buffer); + final base64 = base64Encode(output.buffer.asUint8List()); + io.stdout.write(base64); +} diff --git a/tool/terminal8x8.png b/tool/terminal8x8.png new file mode 100644 index 0000000..4e40554 Binary files /dev/null and b/tool/terminal8x8.png differ