Closed chnav closed 8 years ago
@TheMarex could you take a look at this?
Correction about render behavior, my bad.
I've played with brighter canvas colors and found (visually ) Alpha channel is recognized i.e. canvas { fill-color: #80000080; } and canvas { fill-color: #800000; } are rendered different, BUT somewhere at very low level an extra black opaque canvas added. Maybe it is some default layer setting {0.0, 0.0, 0.0, 1.0} or so.
Ok finally got it working
diff --git a/src/server/renderer/render_canvas.cpp b/src/server/renderer/render_canvas.cpp
--- a/src/server/renderer/render_canvas.cpp
+++ b/src/server/renderer/render_canvas.cpp
@@ -135,6 +135,7 @@ PNGRenderCanvas::PNGRenderCanvas(unsigned int layerWidth,
cairo_set_font_options(layers[LAYER_LABELS].cr, fontOpts);
cairo_select_font_face(layers[LAYER_LABELS].cr, DEFAULT_FONT,
CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ this->clear(); // cairo FIX: making sure destination slice image will have Alpha output on PNG
}
The problem lays deep in cairo, it can be bug; not sure it is reproduced on platforms other than MSVC.
Tech details
Original [render_canvas.cpp]: PNGRenderCanvas() constructor creates surface and for some reasons [renderer.cpp]: Renderer::sliceTile() is called with empty image before any actual TMS-request (possibly as a part of pre-rendering, not sure about that).
When trace in MSVS 2015, at first call of Renderer::sliceTile() I see metatile and slice are white opaque, it's 32-bit image with pixels values RGBA(255,255,255,255).
The [cairo_png.c]: _cairo_image_analyze_transparency() is called during [cairo-png.c]: write_png(), while image isn't initialized yet; its image->transparency equal to CAIRO_IMAGE_UNKNOWN, thus code begin to detect the actual content. It scans through every pixel's alpha, and after it see all of them 255 it decides the image is fully opaque, then assigns image->transparency=CAIRO_IMAGE_IS_OPAQUE. The latter value is stored deep in slice data and not changed anymore. Any PNG export after that have to pre-multiply RGB values by corresponding alpha and export PNG to RGB24 format, no alpha.
Still having sporadic problems with blocked alpha channel, when TMS request happens before pre-rendering complete.
(added) Found RENDER_LOCK definition in config.hpp and implementation in render.cpp; will test with lock enabled.
Have answer from the cairo mailing list, "it is not a bug it is a feature" ((
In short, they trying to optimize PNG. Even user intentionally set CAIRO_FORMAT_ARGB32 for the surface, on first writing they try to optimize it; if all alphas set to 255 they call it opaque and write 24-bit png. The problem is they now lock the png-format and all consequent writings for that surface will also be RGB24.
CONCLUSION: when working with mix of data both opaque and transparent, slice image cannot be re-used; it must be newly created every time we want to write png.
Code examples
------- code 1 begin -------------- cairo_surface_t surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256); cairo_t cr = cairo_create (surface);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); cairo_paint(cr); cairo_surface_write_to_png(surface, "cairo_test1a.png"); // ... cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5); cairo_paint(cr); cairo_surface_write_to_png(surface, "cairo_test1b.png"); ------- code 1 end --------------
Both files supposed to be 32 bit PNG with alpha but above will produce 24 bit png without alpha.
------- code 2 begin -------------- cairo_surface_t surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256, 256); cairo_t cr = cairo_create (surface);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.9); cairo_paint(cr); cairo_surface_write_to_png(surface, "cairo_test2a.png"); // ... cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5); cairo_paint(cr); cairo_surface_write_to_png(surface, "cairo_test2b.png"); ------- code 2 end --------------
Now both files are 32 bit PNG alpha. The only code difference is we have opaque or transparent image before 1st writing.
Thanks alot for figuring this out and shining light to what's happening there with Cairo! :+1:
As you seem to have fully understood the problems here and know how to solve them, do you think you could create a Pull Request fixing this yourself? And maybe linking the relevant documentation / mailing list post so others know why you can't reuse the slice image under that circumstance?
It would be very appreciated. :smile:
Hi Florian, I made some bugfix workaround but soon got a Cairo patch from mailing list. It was surprisingly fast ))
https://lists.cairographics.org/archives/cairo/2016-June/027435.html
I run few tests, looks like Cairo patch solved the problem and no alaCarte modification is required, we can keep and re-use slice surface.
@chnav thanks for your work! Fixing it upstream is even better. :+1: Another reason for a cairo version as recent as possible.
I have been using Win32 version of alaCarte compiled by OSM user:freexec on 2013-12 http://forum.openstreetmap.org/viewtopic.php?pid=387410#p387410
Couple days ago I cloned repository and compiled Win32 version using MSVS 2015. All works fine except alpha channel of canvas is not processed anymore.
canvas { fill-color: #20000080; }
is rendered with opacity 100% and gives same result as
canvas { fill-color: #200000; }
Comparing modern version against Dec-2013 I think something lost while transfer from cairomm to direct cairo use. I'm currently looking into renderer.cpp and paintBackground() but cannot find what's wrong.