linebender / linebender/tiny-skia

Support stride in `Pixmap`/`PixmapMut`?

Open
#168 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
1.6k
Forks
99
Avg merge
2h 52m
Merged PRs (30d)
1

Description

Hey! I'm working on [improving Softbuffer](https://github.com/rust-windowing/softbuffer/milestone/2), a library for creating a buffer that can be rendered on the CPU and presented to a window. I believe that `tiny-skia` and `vello_cpu` are good complements for it, and I'll be recommending them in our documentation in https://github.com/rust-windowing/softbuffer/pull/325.

For us to be zero-copy on Android and macOS, we need to expose stride, see https://github.com/rust-windowing/softbuffer/pull/315. This is unfortunate though, because it means that integration with `tiny-skia` when `stride != width` requires rendering into a separate buffer first.

Would it be possible (and desirable) for `tiny-skia` to support passing a `byte stride != width * 4` in their creation methods?

---

Note that this might not be _that_ useful yet, since a lot of platforms still only support BGRA pixel formats, so users of `tiny-skia` would still have to copy in any case then. I plan to improve that, such that in the future an integration between these two libraries would look like:
```rust
// Set surface as transparent and try to use an RGBA pixel format.
surface.set_alpha_mode(AlphaMode::Premultiplied);
let needs_rgba_conversion = surface.supported_pixel_formats().contains(PixelFormat::Rgba8) {
surface.set_alpha_mode(PixelFormat::Rgba8);
false
} else {
// Ideally supported on most platforms, this would only be a fallback in edge cases.
// (e.g. Wayland compositors aren't guaranteed to support RGBA, but most do).
surface.set_pixel_format(PixelFormat::Bgra8);
true
};

// Get next buffer.
let buffer = surface.next_buffer().unwrap();

// Create `PixmapMut<'_>` from `Buffer<'_>`.
let width = buffer.width();
let height = buffer.height();
let byte_stride = buffer.byte_stride();
let data = buffer.data();
let pixmap = PixmapMut::from_bytes(data, byte_stride, width, height);

draw(pixmap); // User draws here.

if needs_rgba_conversion {
// Swap red and blue channel to convert RGBA to BGRA.
for row in buffer.rows() {
for [r, _g, b, _a] in row.as_chunks::<4>() {
std::mem::swap(r, b);
}
}
}

// Present the image.
buffer.present();
```

That would fully avoid allocating unnecessary buffers, and would only add an extra processing step (swapping the r and b channels, which could probably be vectorized quite a bit) if absolutely necessary. (If you wanted to avoid that too, you'd have to make the order of `PremultipliedColorU8` platform-dependent. This is what Softbuffer is gonna do in its default mode, but it is probably gonna be too annoying for `tiny-skia`).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the Pixmap and PixmapMut creation methods, especially from_bytes, and trace how width, height, and pixel rows are represented. Determine the API and invariants needed to accept a byte stride different from width * 4 while preserving the existing pixel format. Done means callers can construct Pixmap or PixmapMut over strided data without an intermediate buffer.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.