css4j / echosvg

SVG implementation in the Java™ Language, fork of Apache Batik, supporting level 4 selectors and colors.
Apache License 2.0
39 stars 2 forks source link

Prevent rasterization of a vector that has opacity #53

Closed iocoker closed 1 year ago

iocoker commented 2 years ago

HI there, thanks for this awesome work. This is a question.

Currently, we tried using echosvg to load SVG through the bridge and then paint it unto a Graphics2D context. However, if an item has opacity less than 1, the system automatically rasterizes it into a very low quality image. It happens for Text, Paths, Rect any svg element with an opacity gets painted as a raster image.

I assume it's treating the opacity as a filter effect, whereas it shouldn't be.

Is there a way to prevent this?

carlosame commented 2 years ago

Thank you for reporting this.

In order to fix this issue, I would appreciate if you could provide a sample SVG (paired with the obtained render) showing the problem so I can reproduce it (I do not easily see it in the opacity tests that EchoSVG has).

You could provide a SVG file of your own, or something based on the files in samples/tests/spec/rendering (the rendered PNGs are in test-references/samples/tests/spec/rendering).

iocoker commented 2 years ago

Thanks, here are the files. sample - opaque sample - opacity

opaque sample.pdf opacity sample.pdf

Sorry the SVGs have a transparent background, so the effect could be seen better in white theme. It's actually a relic from Batik, so not unique to Echosvg, I'm just thinking perhaps there's a way to enable some flags or so to make it retain crisps vector even with opacity. A way out was to use fill-opacity but the same issue plaques images, as they are drawn to a Graphics2D very poorly once they have any opacity or any filter.

Here's a reference to the same issue: https://github.com/rototor/pdfbox-graphics2d/issues/30#issuecomment-799236574

We're using the GraphicsNode.paint method to draw the content unto a Graphics2D canvas and that gets rendered into PDF. It retains the vector state until the opacity is set to anything less than 1.

Thank you

carlosame commented 2 years ago

I tested drawGlyphVectorWorks = true as suggested in the pdfbox thread, but the results are bad. For example the Font1 test produced the following Font1_cmp.png comparison image (reference image is at left, drawGlyphVectorWorks = true at right):

Font1_cmp

And the diff image Font1_diff.png:

Font1_diff

In case you want to experiment yourself, you can find these comparison images under echosvg-test/build/reports/tests/test/images after running the tests with Gradle or Eclipse.

It should be possible to fix this issue (leveraging the fact that, unlike Batik, EchoSVG supports colors with an alpha channel), but it is not so easy as just changing a setting.

iocoker commented 2 years ago

Thanks for checking this out; Indeed it's so much work tracing the issue. I'll give it a try.

carlosame commented 2 years ago

Actually, I'm not sure that I reproduced the problem. I rendered the following SVG, which comes from your sample:

<svg width="243.78" height="153.07" xmlns="http://www.w3.org/2000/svg">
<g>
<text xml:space="preserve" font-family="Arial" font-size="40" font-weight="bold"
 style="stroke:none;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#000;fill-rule:nonzero;opacity:.5;white-space:pre"
 transform="matrix(.558 0 0 .558 121.83 76.484)"><tspan x="-116.338" y="12.566">Sample Test</tspan></text>
</g>
</svg>

and here is the resulting PNG:

textAliasing

which is what I'd expect. Perhaps you are overscaling the image?

I've created another (more complex) test where I checked putting the opacity in several different ways (inherited from another style, as an attribute in the text and g elements,...) and still do not see the issue.

In case I'm missing something, would appreciate more information.

carlosame commented 2 years ago

Let's add that the sample image that you provide (in the "opacity sample" PDF) was apparently rendered without anti-aliasing, and in the normal EchoSVG rendering process, anti-aliasing is enabled (it is SVG's default) unless one disables it explicitly.

More information is clearly required.

iocoker commented 2 years ago

@carlosame, thanks again for looking into this further.

So, here's a sample of our code: Screenshot 2022-08-03 171923 Basically we load an SVG string using echosvg, then get a graphicsNode and pass it a graphics2d canvas (we get that from pdfbox-graphics2d) and paint the graphics unto the g2d. Then from there it's encoded into a PDF via ApachePDFBox.

With opacity = 1, this is what the PDF comes to: Screenshot 2022-08-03 171335

With opacity less than 1, this is what we get: Screenshot 2022-08-03 171259

The first retains vector mode, the one with opacity just turns to raster image. I think it has to do with the compositing, as even it treats opacity the same way it does filters, which is, just turn it into bitmap.

We did try SVGSalamanda, just to be sure it's not pdfbox-graphics2d and came out fine with opacity. But SVGSalamanda isn't as mature or feature complete as echosvg, especially when it comes to fonts handling

So, it's not something that can be deduced outputting to a PNG, even if we did increase the resolution, it's still a raster and not that good for print.

I hope this sheds more light into the issue.

carlosame commented 1 year ago

@iocoker would you mind checking the latest 0.3-SNAPSHOT? It may fix this issue or at least be a mitigation.

Unfortunately I have no repository for snapshots, so the idea would be to run:

./gradlew build publishToMavenLocal

and then using the library from your Maven local repository.

carlosame commented 1 year ago

This issue has been pending a response for more than two months. Lacking a reproducer, it is unclear whether it has been fixed or not, so I'm closing it.

If the problem described in this issue persists, please open a new one with a unit test that reproduces it.