From d14203561de1a282b02f0f52c78c3a30e1302f80 Mon Sep 17 00:00:00 2001 From: talaj Date: Wed, 13 Sep 2017 11:14:46 +0200 Subject: [PATCH] color font support improvements (#3758) :+1: --- include/mapnik/text/color_font_renderer.hpp | 74 +++++++ include/mapnik/text/face.hpp | 4 +- include/mapnik/text/harfbuzz_shaper.hpp | 2 +- include/mapnik/text/renderer.hpp | 34 +++- include/mapnik/text/rotation.hpp | 5 + src/build.py | 1 + src/text/color_font_renderer.cpp | 169 ++++++++++++++++ src/text/face.cpp | 18 +- src/text/renderer.cpp | 202 ++++++++++++-------- test/data-visual | 2 +- 10 files changed, 424 insertions(+), 87 deletions(-) create mode 100644 include/mapnik/text/color_font_renderer.hpp create mode 100644 src/text/color_font_renderer.cpp diff --git a/include/mapnik/text/color_font_renderer.hpp b/include/mapnik/text/color_font_renderer.hpp new file mode 100644 index 000000000..3495d1945 --- /dev/null +++ b/include/mapnik/text/color_font_renderer.hpp @@ -0,0 +1,74 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2017 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +#ifndef MAPNIK_COLOR_FONT_RENDERER_HPP +#define MAPNIK_COLOR_FONT_RENDERER_HPP + +#pragma GCC diagnostic push +#include + +#pragma GCC diagnostic push +#include +#include +#pragma GCC diagnostic pop + +// freetype2 +extern "C" +{ +#include +#include FT_FREETYPE_H +#include FT_STROKER_H +} + +#pragma GCC diagnostic pop + +#include +#include +#include +#include + +namespace mapnik +{ + +agg::trans_affine glyph_transform(agg::trans_affine const& tr, + unsigned glyph_height, + int x, int y, + double angle, + box2d const& bbox); + +template +void composite_color_glyph(T & pixmap, + FT_Bitmap const& bitmap, + agg::trans_affine const& tr, + double opacity, + composite_mode_e comp_op); + +struct glyph_t; + +image_rgba8 render_glyph_image(glyph_t const& glyph, + FT_Bitmap const& bitmap, + agg::trans_affine const& tr, + pixel_position & render_pos); + +} + +#endif diff --git a/include/mapnik/text/face.hpp b/include/mapnik/text/face.hpp index 765478b82..bac78a6f4 100644 --- a/include/mapnik/text/face.hpp +++ b/include/mapnik/text/face.hpp @@ -77,8 +77,10 @@ public: ~font_face(); private: + bool init_color_font(); + FT_Face face_; - bool color_font_ = false; + const bool color_font_; }; using face_ptr = std::shared_ptr; diff --git a/include/mapnik/text/harfbuzz_shaper.hpp b/include/mapnik/text/harfbuzz_shaper.hpp index a62071800..41fac9565 100644 --- a/include/mapnik/text/harfbuzz_shaper.hpp +++ b/include/mapnik/text/harfbuzz_shaper.hpp @@ -284,7 +284,7 @@ static void shape_text(text_line & line, double tmp_height = g.height(); if (g.face->is_color()) { - tmp_height = size; + tmp_height = g.ymax(); } if (tmp_height > max_glyph_height) max_glyph_height = tmp_height; width_map[char_index] += g.advance(); diff --git a/include/mapnik/text/renderer.hpp b/include/mapnik/text/renderer.hpp index 6819392ff..9a81de6e4 100644 --- a/include/mapnik/text/renderer.hpp +++ b/include/mapnik/text/renderer.hpp @@ -29,11 +29,16 @@ #include #include #include +#include #pragma GCC diagnostic push #include +#pragma GCC diagnostic push +#include +#include "agg_pixfmt_rgba.h" #include +#pragma GCC diagnostic pop // freetype2 extern "C" @@ -53,12 +58,21 @@ struct glyph_t FT_Glyph image; detail::evaluated_format_properties const& properties; pixel_position pos; + rotation rot; double size; - glyph_t(FT_Glyph image_, detail::evaluated_format_properties const& properties_, pixel_position const& pos_, double size_) + box2d bbox; + glyph_t(FT_Glyph image_, + detail::evaluated_format_properties const& properties_, + pixel_position const& pos_, + rotation const& rot_, + double size_, + box2d const& bbox_) : image(image_), properties(properties_), pos(pos_), - size(size_) {} + rot(rot_), + size(size_), + bbox(bbox_) {} }; class text_renderer : private util::noncopyable @@ -124,7 +138,12 @@ public: void render(glyph_positions const& positions); private: pixmap_type & pixmap_; - void render_halo(FT_Bitmap_ *bitmap, unsigned rgba, int x, int y, + + template + void render_halo(unsigned char *buffer, + unsigned width, + unsigned height, + unsigned rgba, int x, int y, double halo_radius, double opacity, composite_mode_e comp_op); }; @@ -140,7 +159,14 @@ public: void render(glyph_positions const& positions, value_integer feature_id); private: pixmap_type & pixmap_; - void render_halo_id(FT_Bitmap_ *bitmap, mapnik::value_integer feature_id, int x, int y, int halo_radius); + + template + void render_halo_id(unsigned char *buffer, + unsigned width, + unsigned height, + mapnik::value_integer feature_id, + int x, int y, + int halo_radius); }; } diff --git a/include/mapnik/text/rotation.hpp b/include/mapnik/text/rotation.hpp index 294eed05a..dcd47dd55 100644 --- a/include/mapnik/text/rotation.hpp +++ b/include/mapnik/text/rotation.hpp @@ -17,6 +17,11 @@ struct rotation double cos; rotation operator~() const { return rotation(sin, -cos); } rotation operator!() const { return rotation(-sin, cos); } + + double angle() const + { + return std::atan2(sin, cos); + } }; } diff --git a/src/build.py b/src/build.py index 00c9134a4..13c5a9b5d 100644 --- a/src/build.py +++ b/src/build.py @@ -241,6 +241,7 @@ source = Split( text/placement_finder.cpp text/properties_util.cpp text/renderer.cpp + text/color_font_renderer.cpp text/symbolizer_helpers.cpp text/text_properties.cpp text/font_feature_settings.cpp diff --git a/src/text/color_font_renderer.cpp b/src/text/color_font_renderer.cpp new file mode 100644 index 000000000..b7e447b7b --- /dev/null +++ b/src/text/color_font_renderer.cpp @@ -0,0 +1,169 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2017 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ + +// mapnik +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#include +#include "agg_rendering_buffer.h" +#include "agg_color_rgba.h" +#include "agg_scanline_u.h" +#include "agg_image_filters.h" +#include "agg_trans_bilinear.h" +#include "agg_span_allocator.h" +#include "agg_image_accessors.h" +#include "agg_span_image_filter_rgba.h" +#include "agg_renderer_base.h" +#include "agg_renderer_scanline.h" +#include "agg_pixfmt_rgba.h" +#pragma GCC diagnostic pop + +namespace mapnik +{ + +template +void composite_image(Pixmap & pixmap, + ImageAccessor & img_accessor, + double width, + double height, + agg::trans_affine const& tr, + double opacity, + composite_mode_e comp_op) +{ + double p[8]; + p[0] = 0; p[1] = 0; + p[2] = width; p[3] = 0; + p[4] = width; p[5] = height; + p[6] = 0; p[7] = height; + + tr.transform(&p[0], &p[1]); + tr.transform(&p[2], &p[3]); + tr.transform(&p[4], &p[5]); + tr.transform(&p[6], &p[7]); + + rasterizer ras; + ras.move_to_d(p[0], p[1]); + ras.line_to_d(p[2], p[3]); + ras.line_to_d(p[4], p[5]); + ras.line_to_d(p[6], p[7]); + + using color_type = agg::rgba8; + using order_type = agg::order_rgba; + using blender_type = agg::comp_op_adaptor_rgba_pre; + using pixfmt_comp_type = agg::pixfmt_custom_blend_rgba; + using renderer_base = agg::renderer_base; + + agg::scanline_u8 sl; + agg::rendering_buffer buf(pixmap.bytes(), + pixmap.width(), + pixmap.height(), + pixmap.row_size()); + pixfmt_comp_type pixf(buf); + pixf.comp_op(static_cast(comp_op)); + renderer_base renb(pixf); + + agg::span_allocator sa; + agg::image_filter_bilinear filter_kernel; + agg::image_filter_lut filter(filter_kernel, false); + + using interpolator_type = agg::span_interpolator_linear; + using span_gen_type = agg::span_image_resample_rgba_affine; + using renderer_type = agg::renderer_scanline_aa_alpha, + span_gen_type>; + agg::trans_affine final_tr(p, 0, 0, width, height); + final_tr.tx = std::floor(final_tr.tx + .5); + final_tr.ty = std::floor(final_tr.ty + .5); + interpolator_type interpolator(final_tr); + span_gen_type sg(img_accessor, interpolator, filter); + renderer_type rp(renb,sa, sg, static_cast(opacity * 255)); + agg::render_scanlines(ras, sl, rp); +} + +agg::trans_affine glyph_transform(agg::trans_affine const& tr, + unsigned glyph_height, + int x, int y, + double angle, + box2d const& bbox) +{ + agg::trans_affine t; + double scale = bbox.height() / glyph_height; + t *= agg::trans_affine_translation(0, -bbox.maxy() / scale); // set to baseline + t *= tr; + t *= agg::trans_affine_rotation(angle); + t *= agg::trans_affine_scaling(scale); + t *= agg::trans_affine_translation(x, y); + return t; +} + +template +void composite_color_glyph(T & pixmap, + FT_Bitmap const& bitmap, + agg::trans_affine const& tr, + double opacity, + composite_mode_e comp_op) +{ + using glyph_pixfmt_type = agg::pixfmt_bgra32_pre; + using img_accessor_type = agg::image_accessor_clone; + unsigned width = bitmap.width; + unsigned height = bitmap.rows; + agg::rendering_buffer glyph_buf(bitmap.buffer, width, height, width * glyph_pixfmt_type::pix_width); + glyph_pixfmt_type glyph_pixf(glyph_buf); + img_accessor_type img_accessor(glyph_pixf); + + composite_image( + pixmap, img_accessor, width, height, tr, + opacity, comp_op); +} + +template +void composite_color_glyph(image_rgba8 & pixmap, + FT_Bitmap const& bitmap, + agg::trans_affine const& tr, + double opacity, + composite_mode_e comp_op); + +image_rgba8 render_glyph_image(glyph_t const& glyph, + FT_Bitmap const& bitmap, + agg::trans_affine const& tr, + pixel_position & render_pos) +{ + agg::trans_affine transform(glyph_transform(tr, bitmap.rows, 0, 0, -glyph.rot.angle(), glyph.bbox)); + box2d bitmap_bbox(0, 0, bitmap.width, bitmap.rows); + bitmap_bbox *= transform; + image_rgba8 glyph_image(bitmap_bbox.width(), bitmap_bbox.height()); + transform *= agg::trans_affine_translation(-bitmap_bbox.minx(), -bitmap_bbox.miny()); + render_pos = render_pos + + pixel_position(glyph.pos.x, -glyph.pos.y) + + pixel_position(std::round(bitmap_bbox.minx()), std::round(bitmap_bbox.miny())); + composite_color_glyph(glyph_image, bitmap, transform, 1, dst_over); + return glyph_image; +} + +} // namespace mapnik diff --git a/src/text/face.cpp b/src/text/face.cpp index e470f758c..4da295b19 100644 --- a/src/text/face.cpp +++ b/src/text/face.cpp @@ -38,12 +38,17 @@ namespace mapnik { font_face::font_face(FT_Face face) - : face_(face) + : face_(face), + color_font_(init_color_font()) +{ +} + +bool font_face::init_color_font() { static const uint32_t tag = FT_MAKE_TAG('C', 'B', 'D', 'T'); unsigned long length = 0; FT_Load_Sfnt_Table(face_, tag, 0, nullptr, &length); - if (length) color_font_ = true; + return length > 0; } bool font_face::set_character_sizes(double size) @@ -85,6 +90,15 @@ bool font_face::glyph_dimensions(glyph_info & glyph) const glyph.unscaled_ymax = glyph_bbox.yMax; glyph.unscaled_advance = face_->glyph->advance.x; glyph.unscaled_line_height = face_->size->metrics.height; + + if (color_font_) + { + double scale_multiplier = 2048.0 / (face_->size->metrics.height); + glyph.unscaled_ymin *= scale_multiplier; + glyph.unscaled_ymax *= scale_multiplier; + glyph.unscaled_advance *= scale_multiplier; + } + return true; } diff --git a/src/text/renderer.cpp b/src/text/renderer.cpp index 692190b8e..2d8ad331e 100644 --- a/src/text/renderer.cpp +++ b/src/text/renderer.cpp @@ -30,6 +30,22 @@ #include #include #include +#include + +#pragma GCC diagnostic push +#include +#include "agg_rendering_buffer.h" +#include "agg_pixfmt_rgba.h" +#include "agg_color_rgba.h" +#include "agg_scanline_u.h" +#include "agg_image_filters.h" +#include "agg_trans_bilinear.h" +#include "agg_span_allocator.h" +#include "agg_image_accessors.h" +#include "agg_span_image_filter_rgba.h" +#include "agg_renderer_base.h" +#include "agg_renderer_scanline.h" +#pragma GCC diagnostic pop namespace mapnik { @@ -112,7 +128,8 @@ void text_renderer::prepare_glyphs(glyph_positions const& positions) FT_Glyph image; error = FT_Get_Glyph(face->glyph, &image); if (error) continue; - glyphs_.emplace_back(image, *glyph.format, pos, size); + box2d bbox(0, glyph_pos.glyph.ymin(), glyph_pos.glyph.advance(), glyph_pos.glyph.ymax()); + glyphs_.emplace_back(image, *glyph.format, pos, glyph_pos.rot, size, bbox); } } @@ -135,32 +152,6 @@ void composite_bitmap(T & pixmap, FT_Bitmap *bitmap, unsigned rgba, int x, int y } } -template -void composite_color_bitmap(T & pixmap, FT_Bitmap *bitmap, int x, int y, double size, double opacity, composite_mode_e comp_op) -{ - image_rgba8 image(bitmap->width, bitmap->rows); - - for (unsigned i = 0, p = 0; i < bitmap->width; ++i, p += 4) - { - for (unsigned j = 0, q = 0; j < bitmap->rows; ++j, ++q) - { - std::uint8_t b = bitmap->buffer[q * bitmap->width * 4 + p]; - std::uint8_t g = bitmap->buffer[q * bitmap->width * 4 + p + 1]; - std::uint8_t r = bitmap->buffer[q * bitmap->width * 4 + p + 2]; - std::uint8_t a = bitmap->buffer[q * bitmap->width * 4 + p + 3]; - unsigned c = static_cast((a << 24) | (b << 16) | (g << 8) | (r)); - image(i, j) = c; - } - } - double scale = size/image.height(); - int scaled_width = bitmap->width * scale; - int scaled_height = bitmap->rows * scale; - image_rgba8 scaled_image(scaled_width, scaled_height); - scale_image_agg(scaled_image, image , SCALING_BILINEAR , scale, scale, 0.0, 0.0, 1.0, 0); - set_premultiplied_alpha(scaled_image, true); - composite(pixmap, scaled_image, comp_op, opacity, x, y); -} - template agg_text_renderer::agg_text_renderer (pixmap_type & pixmap, halo_rasterizer_e rasterizer, @@ -229,28 +220,58 @@ void agg_text_renderer::render(glyph_positions const& pos) if (!error) { FT_BitmapGlyph bit = reinterpret_cast(g); - composite_bitmap(pixmap_, - &bit->bitmap, - halo_fill, - bit->left, - height - bit->top, - halo_opacity, - halo_comp_op_); + if (bit->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) + { + continue; + } + else + { + composite_bitmap(pixmap_, + &bit->bitmap, + halo_fill, + bit->left, + height - bit->top, + halo_opacity, + halo_comp_op_); + } } } else { error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); - if (!error) + if (error) { - FT_BitmapGlyph bit = reinterpret_cast(g); - render_halo(&bit->bitmap, - halo_fill, - bit->left, - height - bit->top, - halo_radius, - halo_opacity, - halo_comp_op_); + continue; + } + FT_BitmapGlyph bit = reinterpret_cast(g); + if (bit->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) + { + pixel_position render_pos(base_point); + image_rgba8 glyph_image(render_glyph_image(glyph, + bit->bitmap, + transform_, + render_pos)); + const constexpr std::size_t pixel_size = sizeof(image_rgba8::pixel_type); + render_halo(glyph_image.bytes(), + glyph_image.width(), + glyph_image.height(), + halo_fill, + render_pos.x, render_pos.y, + halo_radius, + halo_opacity, + halo_comp_op_); + } + else + { + render_halo<1>(bit->bitmap.buffer, + bit->bitmap.width, + bit->bitmap.rows, + halo_fill, + bit->left, + height - bit->top, + halo_radius, + halo_opacity, + halo_comp_op_); } } } @@ -267,22 +288,27 @@ void agg_text_renderer::render(glyph_positions const& pos) error = 0; if ( glyph.image->format != FT_GLYPH_FORMAT_BITMAP ) { - error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1); } if (error == 0) { FT_BitmapGlyph bit = reinterpret_cast(glyph.image); int pixel_mode = bit->bitmap.pixel_mode; - if (pixel_mode == 7) + if (pixel_mode == FT_PIXEL_MODE_BGRA) { - int x = (start.x >> 6) + glyph.pos.x; - int y = height - (start.y >> 6) + glyph.pos.y; - composite_color_bitmap(pixmap_, - &bit->bitmap, - x,y, glyph.size, - text_opacity, - comp_op_); + int x = base_point.x + glyph.pos.x; + int y = base_point.y - glyph.pos.y; + agg::trans_affine transform( + glyph_transform(transform_, + bit->bitmap.rows, + x, y, + -glyph.rot.angle(), + glyph.bbox)); + composite_color_glyph(pixmap_, + bit->bitmap, + transform, + text_opacity, + comp_op_); } else { @@ -300,7 +326,6 @@ void agg_text_renderer::render(glyph_positions const& pos) } - template void grid_text_renderer::render(glyph_positions const& pos, value_integer feature_id) { @@ -329,11 +354,31 @@ void grid_text_renderer::render(glyph_positions const& pos, value_integer fea if (!error) { FT_BitmapGlyph bit = reinterpret_cast(glyph.image); - render_halo_id(&bit->bitmap, - feature_id, - bit->left, - height - bit->top, - static_cast(halo_radius)); + if (bit->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) + { + pixel_position render_pos(base_point); + image_rgba8 glyph_image(render_glyph_image(glyph, + bit->bitmap, + transform_, + render_pos)); + const constexpr std::size_t pixel_size = sizeof(image_rgba8::pixel_type); + render_halo_id(glyph_image.bytes(), + glyph_image.width(), + glyph_image.height(), + feature_id, + render_pos.x, render_pos.y, + static_cast(halo_radius)); + } + else + { + render_halo_id<1>(bit->bitmap.buffer, + bit->bitmap.width, + bit->bitmap.rows, + feature_id, + bit->left, + height - bit->top, + static_cast(halo_radius)); + } } FT_Done_Glyph(glyph.image); } @@ -341,16 +386,17 @@ void grid_text_renderer::render(glyph_positions const& pos, value_integer fea template -void agg_text_renderer::render_halo(FT_Bitmap *bitmap, - unsigned rgba, - int x1, - int y1, - double halo_radius, - double opacity, - composite_mode_e comp_op) +template +void agg_text_renderer::render_halo(unsigned char *buffer, + unsigned width, + unsigned height, + unsigned rgba, + int x1, + int y1, + double halo_radius, + double opacity, + composite_mode_e comp_op) { - int width = bitmap->width; - int height = bitmap->rows; int x, y; if (halo_radius < 1.0) { @@ -358,7 +404,7 @@ void agg_text_renderer::render_halo(FT_Bitmap *bitmap, { for (y=0; y < height; y++) { - int gray = bitmap->buffer[y*bitmap->width+x]; + int gray = buffer[(y * width + x) * PixelWidth + PixelWidth - 1]; if (gray) { mapnik::composite_pixel(pixmap_, comp_op, x+x1-1, y+y1-1, rgba, gray*halo_radius*halo_radius, opacity); @@ -382,7 +428,7 @@ void agg_text_renderer::render_halo(FT_Bitmap *bitmap, { for (y=0; y < height; y++) { - int gray = bitmap->buffer[y*bitmap->width+x]; + int gray = buffer[(y * width + x) * PixelWidth + PixelWidth - 1]; if (gray) { for (int n=-halo_radius; n <=halo_radius; ++n) @@ -395,21 +441,21 @@ void agg_text_renderer::render_halo(FT_Bitmap *bitmap, } template -void grid_text_renderer::render_halo_id( - FT_Bitmap *bitmap, - mapnik::value_integer feature_id, - int x1, - int y1, - int halo_radius) +template +void grid_text_renderer::render_halo_id(unsigned char *buffer, + unsigned width, + unsigned height, + mapnik::value_integer feature_id, + int x1, + int y1, + int halo_radius) { - int width = bitmap->width; - int height = bitmap->rows; int x, y; for (x=0; x < width; x++) { for (y=0; y < height; y++) { - int gray = bitmap->buffer[y*bitmap->width+x]; + int gray = buffer[(y * width + x) * PixelWidth + PixelWidth - 1]; if (gray) { for (int n=-halo_radius; n <=halo_radius; ++n) diff --git a/test/data-visual b/test/data-visual index df578e343..114faca33 160000 --- a/test/data-visual +++ b/test/data-visual @@ -1 +1 @@ -Subproject commit df578e3436681bb9bc582c7ac55a4205e98334f4 +Subproject commit 114faca337e6dd1a0b31f30c285f8f4c29915e09