alacarte-maps / alacarte

Renderer for OpenStreetMap tiles.
https://alacarte-maps.github.io/
Other
58 stars 18 forks source link

lost alpha channel on canvas after Dec-2013 #74

Closed chnav closed 8 years ago

chnav commented 8 years ago

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.

florianjacob commented 8 years ago

@TheMarex could you take a look at this?

chnav commented 8 years ago

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.

chnav commented 8 years ago

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.

chnav commented 8 years ago

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.

chnav commented 8 years ago

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.

chnav commented 8 years ago

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.

florianjacob commented 8 years ago

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:

chnav commented 8 years ago

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.

florianjacob commented 8 years ago

@chnav thanks for your work! Fixing it upstream is even better. :+1: Another reason for a cairo version as recent as possible.