Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uncompressed PNG, more formats, update docs. #1

Merged
merged 6 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pxl

A cross-platform pixel buffer and foundation for pixel-based graphics.
A tiny cross-platform pixel buffer and foundation for pixel-based graphics.

## Usage

Expand All @@ -10,7 +10,13 @@ import 'package:pxl/pxl.dart';

## Features

TODO: Document what the package does, include screenshots, etc.
![Example](https://github.com/user-attachments/assets/5d8a97c5-d9d8-4c60-852c-bfd043f1634b)

- Create and manipulate in-memory integer or floating-point pixel buffers.
- Define and convert between pixel formats.
- Palette-based indexed pixel formats.
- Buffer-to-buffer blitting with automatic format conversion and blend modes.
- Region-based pixel manipulation, replacement, and copying.

## Contributing

Expand All @@ -32,8 +38,9 @@ To preview `dartdoc` output locally, run:
./chore dartodc
```

### Resources
### Inspiration and Sources

- [`MTLPixelFormat`](https://developer.apple.com/documentation/metal/mtlpixelformat)
- [`@thi.ng/pixel`](https://github.com/thi-ng/umbrella/tree/main/packages/pixel)
- [`embedded-graphics`](https://crates.io/crates/embedded-graphics)
- <https://www.da.vidbuchanan.co.uk/blog/hello-png.html>
18 changes: 18 additions & 0 deletions dartdoc_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Format: https://github.com/dart-lang/dartdoc#dartdoc_optionsyaml.

dartdoc:
categories:
"Buffers":
markdown: doc/buffers.md
"Pixel Formats":
markdown: doc/formats.md
"Blending":
markdown: doc/blending.md
"Output and Comparison":
markdown: doc/output.md

categoryOrder:
- "Buffers"
- "Pixel Formats"
- "Blending"
- "Output and Comparison"
47 changes: 47 additions & 0 deletions doc/blending.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
The [BlendMode] interface generates a function that blends two pixel values
together:

```dart
// Combine non-overlapping regions of colors.
final xor = BlendMode.xor.getBlend(abgr8888, abgr8888);
print(xor(0x00000000, 0xFFFFFFFF)); // 0xFFFFFFFF
```

[BlendMode]: ../pxl/BlendMode-class.html

Default blend modes are provided using the [PorterDuff] constructor:

```dart
const BlendMode srcIn = PorterDuff(
PorterDuff.dst,
PorterDuff.zero,
);
```

Blend Mode | Description
---------- | -----------
[clear][] | Sets pixels to `zero`.
[src][] | Copies the source pixel.
[dst][] | Copies the destination pixel.
[srcIn][] | The source that overlaps the destination replaces the destination.
[dstIn][] | The destination that overlaps the source replaces the source.
[srcOut][] | The source that does not overlap the destination replaces the destination.
[dstOut][] | The destination that does not overlap the source replaces the source.
[srcAtop][]| The source that overlaps the destination is blended with the destination.
[dstAtop][]| The destination that overlaps the source is blended with the source.
[xor][] | The source and destination are combined where they do not overlap.
[plus][] | The source and destination are added together.

[PorterDuff]: ../pxl/PorterDuff-class.html

[clear]: ../pxl/BlendMode/clear-constant.html
[src]: ../pxl/BlendMode/src-constant.html
[dst]: ../pxl/BlendMode/dst-constant.html
[srcIn]: ../pxl/BlendMode/srcIn-constant.html
[dstIn]: ../pxl/BlendMode/dstIn-constant.html
[srcOut]: ../pxl/BlendMode/srcOut-constant.html
[dstOut]: ../pxl/BlendMode/dstOut-constant.html
[srcAtop]: ../pxl/BlendMode/srcAtop-constant.html
[dstAtop]: ../pxl/BlendMode/dstAtop-constant.html
[xor]: ../pxl/BlendMode/xor-constant.html
[plus]: ../pxl/BlendMode/plus-constant.html
97 changes: 97 additions & 0 deletions doc/buffers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
2-dimensional views of pixel data are provided by the [Buffer][] type, which
provides a read-only view, with similar functionality to an `Iterable`: it can
be extended or mixed-in as a base class provided the necessary methods are
implemented.

```dart
final class MyBuffer extends Buffer<int> {
@override
PixelFormat<int, void> get format => myFormat;

@override
int get width => 640;

@override
int get height => 480;

/* ... rest of the class implementation ... */
}
```

[Buffer]: ../pxl/Buffer-class.html

In most cases, a concrete [Pixels][] instance will be used to represent pixel
data, which is a buffer that can be read from _and written to_ and guarantees
fast access to linearly stored pixel data. For example, the [IntPixels][] class
stores pixel data as a list of integers, and [FloatPixels][] stores pixel data
as a 32x4 matrix of floating-point values.

[Pixels]: ../pxl/Pixels-class.html
[IntPixels]: ../pxl/IntPixels-class.html
[FloatPixels]: ../pxl/FloatPixels-class.html

```dart
// Creating a 320x240 pixel buffer with the default `abgr8888` format.
final pixels = new IntPixels(320, 240);

// Setting the pixel at (10, 20) to red.
pixels.set(Pos(10, 20), abgr8888.red);
```

## Writing data

The `set` method is not the only way to write data to a buffer:

- [`clear`][]: Clears a region to the `zero` value.
- [`fill`][]: Fills a region with a pixel value.
- [`copyFrom`][]: Copies pixel data from another buffer.

[`clear`]: ../pxl/Pixels/clear.html
[`fill`]: ../pxl/Pixels/fill.html
[`copyFrom`]: ../pxl/Pixels/copyFrom.html

For example, to fill the entire buffer with red:

```dart
pixels.fill(abgr8888.red);
```

Pixels also support _alpha blending_; see [blending](./Blending-topic.html)
for more information.

## Reading data

Reading data from a buffer is done using the `get` method:

```dart
final pixel = pixels.get(Pos(10, 20));
```

The `get` method is not the only way to read data from a buffer:

- [`getRange`][]: Reads a range of pixels lazily.
- [`getRect`][]: Reads a rectangular region of pixels lazily.

[`getRange`]: ../pxl/Buffer/getRange.html
[`getRect`]: ../pxl/Buffer/getRect.html

## Converting data

Many operations in Pxl can automatically convert between different pixel formats
as needed.

To lazily convert buffers, use the `mapConvert` method:

```dart
final abgr8888Pixels = IntPixels(320, 240);
final rgba8888Buffer = abgr8888Pixels.mapConvert(rgba8888);
```

To copy the actual data, use [IntPixels.from][] or [FloatPixels.from][]:

```dart
final rgba8888Pixels = IntPixels.from(abgr8888Pixels);
```

[IntPixels.from]: ../pxl/IntPixels/IntPixels.from.html
[FloatPixels.from]: ../pxl/FloatPixels/FloatPixels.from.html
82 changes: 82 additions & 0 deletions doc/formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
A [PixelFormat][] is a description of how pixel data is stored in memory. It
includes a set of standard properties and rules for how to interpret the data,
such as how many bytes are used to store each pixel, canonical (`zero` or `max`)
values, and conversion rules to and from other formats.

Pxl ships with a number of built-in pixel formats, and can be extended with
additional formats:

```dart
const myFormat = const MyFormat._();

// Pixel data type Channel data type
// v v
final class MyFormat extends PixelFormat<int, int> {
const MyFormat._();

@override
int get bytesPerPixel => 4;

@override
int get zero => 0;

// ... rest of the class implementation
}
```

[PixelFormat]: ../pxl/PixelFormat-class.html

## Integer pixel formats

All integer formats use the ABGR 32-bit format as a common intermediate for
conversions; channels that are larger or smaller than 8 bits are scaled to fit
within the 8-bit range. For example a 4-bit channel value of `0x0F` would be
scaled to `0xFF` when converting to 8-bit.

Name | Bits per pixel | Description
------------ | -------------- | ------------------------------------------------
[abgr8888][] | 32 | 4 channels @ 8 bits each
[argb8888][] | 32 | 4 channels @ 8 bits each
[rgba8888][] | 32 | 4 channels @ 8 bits each

[abgr8888]: ../pxl/abgr8888-constant.html
[argb8888]: ../pxl/argb8888-constant.html
[rgba8888]: ../pxl/rgba8888-constant.html

## Floating-point pixel formats

All floating-point formats use the RGBA 128-bit format as a common intermediate
for conversions; channels that are larger or smaller than 32 bits are scaled to
fit within the 32-bit range. When converting to packed integer formats, data is
normalized to the range `[0.0, 1.0]` and then scaled to fit within the integer
range.

Name | Bits per pixel | Description
------------- | -------------- | -----------------------------------------------
[floatRgba][] | 128 | Red, Green, Blue, Alpha

[floatRgba]: ../pxl/floatRgba-constant.html

## Indexed pixel formats

[IndexedFormat][]s use a palette to map pixel values to colors.

The palette is stored as a separate array of colors, and the pixel data is
stored as indices into the palette.

```dart
// Example of a 1-bit indexed format.
final mono1 = IndexedPixelFormat.bits8(
const [0xFF000000, 0xFFFFFFFF],
format: abgr8888,
);
```

Name | Bits per pixel | Description
------------- | -------------- | -----------------------------------------------
[system8][] | 8 | 8 colors.
[system256][] | 8 | 256 colors (216 RGB + 40 grayscale).

[IndexedFormat]: ../pxl/IndexedFormat-class.html
[system8]: ../pxl/system8.html
[system256]: ../pxl/system256.html
54 changes: 54 additions & 0 deletions doc/output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Pxl is a headless pixel manipulation library, and provides limited support for
rendering, expecting the user to provide their own rendering solution (any
`Canvas`-like API will do, or something that supports pixel buffers in one of
the many formats Pxl provides).

To aid with testing and debugging, Pxl provides a few useful utilities.

## Comparisons

Two buffers can be compared for differences using the `compare` method:

```dart
final diff = pixels1.compare(pixels2);
print(diff);
```

The `compare` method returns a `Comparison` object, which can be used to
summarize, iterate, or visualize the differences between two buffers. Pxl itself
uses this method in its test suite to compare expected and actual results for
pixel operations.

```dart
final diff = pixels1.compare(pixels2);
if (diff.difference < 0.01) {
print('The buffers are nearly identical.');
} else {
print('The buffers differ by ${diff.difference}.');
}
```

## Codecs

Converting pixel buffers to popular formats is best done with a full-featured
library like `image` or `dart:ui`, but Pxl provides a few simple codecs for
converting pixel buffers to pixel-based formats like [PFM][], [PBM][], and can
even output as an uncompressed PNG with [uncompressedPngEncoder][]:

```dart
import 'dart:io';

import 'package:pxl/pxl.dart';

void main() {
final image = IntPixels(8, 8);
image.fill(abgr8888.red);

final bytes = uncompressedPngEncoder.convert(image);
File('example.png').writeAsBytesSync(bytes);
}
```

[uncompressedPngEncoder]: ../pxl/uncompressedPngEncoder-constant.html
[PFM]: http://netpbm.sourceforge.net/doc/pfm.html
[PBM]: http://netpbm.sourceforge.net/doc/pbm.html
Binary file added example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions example/example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env dart

import 'dart:io';
import 'dart:math' as math;

import 'package:pxl/pxl.dart';

void main() {
const imageWidth = 256;
const imageHeight = 256;
final image = IntPixels(imageWidth, imageHeight);

// Fill background with green
image.fill(abgr8888.green);

// Draw a gradient sky
for (var y = 0; y < imageHeight / 2; y++) {
final blue = (y / (imageHeight / 2) * 255).toInt();
image.fill(
abgr8888.create(blue: blue),
target: Rect.fromLTWH(0, y, imageWidth, 1),
);
}

// Draw a pixelated yellow sun
final sunCenter = Pos.floor(imageWidth / 2, imageHeight / 4);
const sunRadius = 32;
for (var y = sunCenter.y - sunRadius; y <= sunCenter.y + sunRadius; y++) {
for (var x = sunCenter.x - sunRadius; x <= sunCenter.x + sunRadius; x++) {
final distance = sunCenter.distanceTo(Pos(x, y));
if (distance <= sunRadius) {
final intensity = 255 - (distance / sunRadius * 255).toInt();
image.set(
Pos(x, y),
abgr8888.create(red: 255 - intensity, green: 255 - intensity),
);
}
}
}

// Draw some pixelated clouds
final rng = math.Random();
for (var i = 0; i < 32; i++) {
final cloudX = rng.nextInt(imageWidth - 10);
final cloudY = rng.nextInt(imageHeight ~/ 2 - 5);
final cloudWidth = rng.nextInt(10) + 5;
final cloudHeight = rng.nextInt(5) + 3;

for (var y = cloudY; y < cloudY + cloudHeight; y++) {
for (var x = cloudX; x < cloudX + cloudWidth; x++) {
if (rng.nextDouble() < 0.7) {
// Some randomness for cloud shape
image.set(Pos(x, y), abgr8888.white);
}
}
}
}

// Save the image
final bytes = uncompressedPngEncoder.convert(image);
File('example.png').writeAsBytesSync(bytes);
}
Loading
Loading