RazrFalcon / tiny-skia

A tiny Skia subset ported to Rust
BSD 3-Clause "New" or "Revised" License
1.05k stars 67 forks source link

Shader on stroke - Too "eager" clamping #107

Closed Dherse closed 9 months ago

Dherse commented 9 months ago

Hello,

I am currently working on implementing gradients in typst in pr https://github.com/typst/typst/pull/2312, and I have hit a roadblock that might be an issue with tiny-skia, and if it is not I am open to any suggestion. Due to some of ours needs (mostly related to color space interpolation, additional options, etc.) we are not using the built-in linear and radial gradients in tiny-skia and instead relying on shaders with a pre-rendered and cached pixmap at the same resolution as the output, which leads to the reasonably simple:

sk_paint.shader = sk::Pattern::new(
     pixmap.as_ref().unwrap().as_ref().as_ref(),
     sk::SpreadMode::Pad,
     sk::FilterQuality::Nearest,
     1.0,
     fill_transform
         .pre_scale(1.0 / state.pixel_per_pt, 1.0 / state.pixel_per_pt),
);

Where fill_transform is sk::Transform::identity most of the time, we have a specific feature we call relative-placement, but that's irrelevant for this issue. We also use Nearest since we're rendering in native resolution so there is no need to sample.

The problem arises when drawing the stroke of a shape (the fill works perfectly), there are two issues that seem to occur:

image (the red box is the limit between the inner-fill of the shape, and the stroke of the outline of the shape)

You can see from it that it starts clamping too early (at about the halfway point) which I don't think should happen. It also seems that regardless of the transform I apply to the stroke, it remains the same (I have mostly tried translating it to the top left and scaling it down slightly). This issue is actually common to all of our gradients, they all behave the same way.

For reference, here is the pixmap used when creating the stroke's shader, as exported with pixmap.save_png:

image

For reference, here is what it should look like (works in both SVG and PDF mode):

image

RazrFalcon commented 9 months ago

Em... I'm not really sure what you're trying to do. Can you provide a complete, stand-alone example that I can run? Then I would try to port it to Skia to make check if this is a tiny-skia bug or not.

Dherse commented 9 months ago

I'll try my best, but I am far from being a (tiny-)skia expert

RazrFalcon commented 9 months ago

Me to 😄 I have no idea how it works.

Dherse commented 9 months ago

@RazrFalcon Sorry for not being clear enough, here is a small repo with (the smallest I could) reproducible example: https://github.com/Dherse/tiny-skia-bug

I generated the fill and the stroke PNGs from typst by exporting the pixmap generated there (so that I could skip the logic of generating them here).

Hope this helps,

Thanks a lot 😄

RazrFalcon commented 9 months ago

I genuinely have no idea what you're trying to achieve here. You have two bitmaps of different size and you're trying to overlap them somehow? So they will blend together perfectly? But instead of blending to bitmaps you're using fill and stroke? On top of that you have multiple transforms.

I don't see a bug in tiny-skia for now. It does exactly what you want.

If you use the same image (stroke.png) for both fill and stroke. With identical transform for both. Then you will get a perfect overlap. But you expect images with different sizes to overlap, which would not happen.

Dherse commented 9 months ago

What I'm trying to do, is apply a shader to both the stroke and the fill such that they line up, in this case I chose them to be the same for testing, but in practice they could be anything (different gradients, different colors, etc.). And I have a single transform for both, with the only exception that my Patterns have a transform for scaling them to the right size

Even if I use the same image for both and the same transform, the shader in the stroke is still clipped. Essentially, I would like the stroke to continue the pattern of the fill (for this specific example).

image

Or am I misgenerating the stroke.png or fill.png and that is the issue?

Thanks

RazrFalcon commented 9 months ago

Just shift the pattern by half the stroke width: sk::Transform::identity().pre_translate(-7.5, -7.5).pre_scale(0.5, 0.5)

Dherse commented 9 months ago

Hmm, strange that I could net get it to work, anyway, thanks for the help ;)

RazrFalcon commented 9 months ago

You're welcome.