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

Cairo renderer #7

Closed
wants to merge 31 commits into from
Closed

Cairo renderer #7

wants to merge 31 commits into from

Conversation

art-w
Copy link
Contributor

@art-w art-w commented Dec 15, 2014

The main motivation for this backend is the ability to rasterize a Vg image to a custom surface in memory, but it can also be used to produce PNG and PS files (and more redundantly, PDF and SVG.) It adds an optional dependency to the cairo2 bindings for OCaml.

The implementation is based on the existing Vgr_htmlc renderer, as the semantics of the javascript canvas are very close to the cairo surface model, with the following known limitations:

  • Gradients are performed in non-linearized RGB space.
  • Outline cuts are only possible on primitive images (constant, gradients).
  • While Cairo is able to render text, it's intended to be used with Pango (which isn't binded with OCaml at this time). Hence, the text API is severely lacking: Beside not using the glyphs infos provided by Vg, the most striking offense is that the weights are limited to Normal and Bold (arbitrarily chosen at W600.)

The rcairo demo has an additional -format argument to specify the file backend.

Let me know if I overlooked anything!

@dbuenzli
Copy link
Owner

Thanks ! Will have a look at all this when I get some time.

Bold (arbitrarily chosen at W600.)

Could you rather use W700 for this ? This would make it consistent with CSS, see
http://www.w3.org/TR/CSS2/fonts.html#font-boldness

Best,

Daniel

The rcairo demo has an additional -format argument to specify the file backend.
Let me know if I overlooked anything!
You can merge this Pull Request by running
git pull https://github.com/art-w/vg cairo2
Or view, comment on, or merge it at:
#7
Commit Summary
Add a cairo2 renderer to the build system
Add a minimal cairo2 example
Vgr_cairo2: cost semantics
Vgr_cairo2: subset implementation based on Vgr_htmlc
Vgr_cairo2: stroke and dash
Vgr_cairo2: gradients
Vgr_cairo2: glyphs
Add Vgr_cairo2 to the doc build
Vgr_cairo2: multiple images
Vgr_cairo2: document the API
Vgr_cairo2: coding convention
Vgr_cairo2: quadratic curves
Vgr_cairo2: correct sizing of minimal example
Vgr_cairo2: add PNG and PSD targets
Vgr_cairo2: bugfix, gradients and arcs
Vgr_cairo2: bugfix, sRGB
Vgr_cairo2: update minimal example to output PNG
Vgr_cairo2: output db examples to PDF
Rstored: allow renderers to have multiple file formats
Vgr_cairo2: rcairo2 has multiple formats
Add cairo2 to .merlin
Vgr_cairo2: add PS and SVG formats
Vgr_cairo2: update copyright
Rename to Vgr_cairo
Vgr_cairo: update dependencies in README
Vgr_cairo: documentation
Vgr_cairo: resolution
Vgr_cairo: update min_cairo example
Vgr_cairo: documentation, correct resolution
Vgr_cairo: small adjustments

File Changes
M .merlin (https://github.com/dbuenzli/vg/pull/7/files#diff-0) (2)
M README.md (https://github.com/dbuenzli/vg/pull/7/files#diff-1) (14)
M _tags (https://github.com/dbuenzli/vg/pull/7/files#diff-2) (9)
M build (https://github.com/dbuenzli/vg/pull/7/files#diff-3) (2)
M doc/api.odocl (https://github.com/dbuenzli/vg/pull/7/files#diff-4) (1)
M doc/dev-api.odocl (https://github.com/dbuenzli/vg/pull/7/files#diff-5) (3)
M opam (https://github.com/dbuenzli/vg/pull/7/files#diff-6) (3)
M pkg/META (https://github.com/dbuenzli/vg/pull/7/files#diff-7) (11)
M pkg/build.ml (https://github.com/dbuenzli/vg/pull/7/files#diff-8) (3)
A src/vgr_cairo.ml (https://github.com/dbuenzli/vg/pull/7/files#diff-9) (439)
A src/vgr_cairo.mli (https://github.com/dbuenzli/vg/pull/7/files#diff-10) (117)
A src/vgr_cairo.mllib (https://github.com/dbuenzli/vg/pull/7/files#diff-11) (1)
A test/min_cairo.ml (https://github.com/dbuenzli/vg/pull/7/files#diff-12) (36)
A test/rcairo.ml (https://github.com/dbuenzli/vg/pull/7/files#diff-13) (58)
M test/rstored.ml (https://github.com/dbuenzli/vg/pull/7/files#diff-14) (17)
M test/tests.itarget (https://github.com/dbuenzli/vg/pull/7/files#diff-15) (4)

Patch Links:
https://github.com/dbuenzli/vg/pull/7.patch
https://github.com/dbuenzli/vg/pull/7.diff


Reply to this email directly or view it on GitHub (#7).

{- [resolution], specifies the rendering resolution in samples per
meters. The PDF, PS and SVG formats are measured in points by Cairo,
while the PNG format is in pixels. If unspecified, the default
conversion to points is used.}}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is hard to understand. Is resolution that [resolution] argument expressed in samples per meters or not ? (P.S. do not clarify yourself I'm currently merging in another branch).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides resolution only makes sense for raster formats.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree that this comment is undecipherable and that the resolution argument should only be specified for the PNG backend. The second part tries to warn that the resolution for PNG isn't correct with the default value since we don't have access to the DPI. Also, since the resolution effectively acts as a scaling factor, I was trying to say "it scales from Vg units to Cairo units, but those differs depending on the backend, so you can just forget the physical units if you want a PNG with a specific size in pixels at the end".

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't get it but I have the impression that this API is wrong. It's not that you need to access the DPI, you should be able to set the DPI for the output image. Is there any way to set the physical size of the render target in cairo ? I see that the generated PNGs have no pHYs chunks.

For the other backends wouldn't make it sense to simply ignore the argument ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think so. Cairo defines the units used for each of its backend, but doesn't care about the physical size otherwise (so pixel surfaces don't have a "printed real life size" metadata..)

The other backends effectively do not need to respect the resolution since the conversion from meters to points doesn't need to be configured; it seemed simpler to just use the default value and let users tweak the ratio it if they desire. I think Png of resolution might be better than silently ignoring the argument if you care about enforcing the physical ratio.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seemed simpler to just use the default value and let users tweak the ratio it if they desire.

I'd rather not mutiply the API points were ratios can be tweaked and I don't like the fact that the name of the argument doesn't reflect the semantics, so I'm going to ignore it for the other backends and simply use the point to meter conversion internally for those.

According to what I read for PNG this still represents the number of samples per meters right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it copies the handling of the resolution argument for the js canvas.

@dbuenzli
Copy link
Owner

Are the glyph examples rendering correctly on your side ? Here it seems that none do. E.g. rendering pie-ambiguity to PNG gives:

pie-ambiguity

{- [size], Surfaces created with [Cairo.Surface] have a valid size, while
file based surfaces have a size of zero by default: If the size of the
surface can not be determined, the optional argument [size] is used
instead. [Invalid_argument] is raised if the size is invalid.}}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the renderable size instead ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't resize a cairo surface, so I followed the semantics of the js canvas target : fill the surface and ignore the physical ratio. The size optional argument is a bugfix for cairo api, because even though cairo won't tell us its value, the user still specified a size when he created it. It didn't seem right to default to something else.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't resize a cairo surface, so I followed the semantics of the js canvas target : fill the surface and ignore the physical ratio.

Yes this is ok.

The size optional argument is a bugfix for cairo api, because even though cairo won't tell us its value, the user still specified a size when he created it. It didn't seem right to default to something else.

Ok I didn't get that. I thought you could specify the size after the fact. Now what I don't like with this is that it can be error prone to switch from a memory based to file based surfaces. Should we maybe always force the user (re)specify the surface size. Or is there maybe a way to fix that so that the file based surfaces report their size correctly ?

More precisely could tell me how these zero-based sized surfaces get created ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, sure. The cairo bindings provides five modules for creating surfaces: the generic Surface, and the file-based PDF, PS, SVG and PNG. The first one, Surface, creates and manages the surfaces in memory and is the motivation for the function target_surface since it provides a lot of non-file formats (and you could just use the simpler target if you wanted to dump the result in a file.) The function to create a new surface has the type format -> width:int -> height:int -> surface. While there are no resize capability, such a surface reports the correct size when asked (so the ?size argument is ignored.)

The four remaining modules allow you to create a file-based surface. For unknown reasons, this surfaces report a size of (0,0) when asked (so the ?size argument is used.) The PDF and PS surfaces do support resizing (but I think it would be confusing to arbitrarily resize for them.)

I expect people to use target_surface with surfaces created by the module Surface, typically for rendering textures that are then displayed directly to the screen with opengl or whatever. Very exceptionally, someone might want to use target_surface with one of the file-based module so that he can configure lowlevel stuff... but then we need to know the image size.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PDF and PS surfaces do support resizing (but I think it would be confusing to arbitrarily resize for them.)

Shouldn't we rather resize according to the renderable's size ? That's what for example vg's built-in PDF renderer does.

The four remaining modules allow you to create a file-based surface. For unknown reasons, this surfaces report a size of (0,0) when asked (so the ?size argument is used.)

If this is a bug I'm somewhat reluctant to adapt the API for the bug...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a bug I'm somewhat reluctant to adapt the API for the bug...

So this is not a bug per se. The fact is that each kind of surface has its own functions and you can't use functions for a surface of a given kind with the other.

After a long discussion with @Chris00 we came to the conclusion that this is not a good interface and that it is preferable to operate directly on contexts rather than surfaces (e.g. this will allow to use Cairo_gtk which is not possible at the moment).

So we forget about the size argument for the target. The semantics is then that given a renderable (size, view, i) we setup a transform so that view rectangle is mapped on the rectangle Box2.v P2.o size in the coordinate system as setup in the given context when we do the render operation. This means that if you create your surface with size, get the context, create a target with it and your renderable is (size, view, i) it will do the expected, that is map the view rectangle on the whole surface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, yes, this sounds good. Thanks for clearing it up with Chris00!

@art-w
Copy link
Contributor Author

art-w commented Aug 11, 2015

Uh, yes, they do. I'm not sure what's going on. Is it the correct font, or does cairo defaults to something else ?

pie-ambiguity

@dbuenzli
Copy link
Owner

Uh, yes, they do.

Ok cool. I was suspecting that.

I'm not sure what's going on. Is it the correct font, or does cairo defaults to something else ?

I'm using cairo on on osx. That may explain some things. I'll try to have a closer look at it.

@art-w
Copy link
Contributor Author

art-w commented Aug 11, 2015

I don't have a clue, but I only tested in on linux since I don't have access to anything else. I'll let you know if I think of some other explanation. You can also send me the rendering by mail if the bug isn't always "the texts are 50x larger than they should", it could help. Otherwise, the outputs I have are identical to the pdf backend (except for the gradients which are subtly not computed in the correct colorspace, but that's all.)

@dbuenzli
Copy link
Owner

"the texts are 50x larger than they should",

Yes that's the symptom. I don't think the problem is with your code. It seems rather to be a bug in cairo. See graphite-project/graphite-web#1191 (comment).

let c = V2.((q + 2. * p0) / 3.) in
let c' = V2.((pt + 2. * q) / 3.) in
P2.(Cairo.curve_to s.ctx (x c) (y c) (x c') (y c') (x pt) (y pt));
loop pt segs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the following animated gif shows quadratic curves (reference image from the canvas backend) seem too be too flat. I suspect the degree elevation of quadratic curves is wrong.
diff

@dbuenzli
Copy link
Owner

Am I correct in thinking that these two Cairo.save are useless since they are immediatly poped by this Cairo.restore ?

Also it seems to me that this Cairo.save is never Cairo.restored.

@Chris00
Copy link

Chris00 commented Aug 13, 2015

It is indeed a bit strange the save the pristine state. After all save and restore are more intended to be used to allow some construction not to affect the current state of the image (e.g. you want to draw a path but do not want the current point to be modified).

@dbuenzli
Copy link
Owner

Btw. @Chris00 I was trying to support multiple page output and it seems I get the following assertion failure:

 Assertion failed: (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&surface->ref_count)), function cairo_surface_destroy, file cairo-surface.c, line 930.
/bin/bash: line 1: 86018 Abort trap: 6           ./rcairo.native -pack bla -format ps

Any idea ?

@dbuenzli
Copy link
Owner

Any idea ?

Never mind can no longer reproduce...

@dbuenzli
Copy link
Owner

Ok so your PR has been squashed and merged as bbcabd4 I changed a bit the API and made stored renderer for pdf and ps support multiple page output. Thanks for bootstrapping this and sorry again for the long delay. Release coming soon.

@dbuenzli dbuenzli closed this Aug 14, 2015
@art-w
Copy link
Contributor Author

art-w commented Aug 14, 2015

Great! Thanks for taking the time to make so many fixes, I'm very happy with all the improvements you made. Have a nice day!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants