color font support improvements (#3758)

👍
This commit is contained in:
talaj 2017-09-13 11:14:46 +02:00 committed by Artem Pavlenko
parent c7c10fed46
commit d14203561d
10 changed files with 424 additions and 87 deletions

View file

@ -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 <mapnik/warning_ignore.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include <agg_trans_affine.h>
#pragma GCC diagnostic pop
// freetype2
extern "C"
{
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
}
#pragma GCC diagnostic pop
#include <mapnik/text/glyph_info.hpp>
#include <mapnik/image_any.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/geometry/box2d.hpp>
namespace mapnik
{
agg::trans_affine glyph_transform(agg::trans_affine const& tr,
unsigned glyph_height,
int x, int y,
double angle,
box2d<double> const& bbox);
template <typename T>
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

View file

@ -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<font_face>;

View file

@ -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();

View file

@ -29,11 +29,16 @@
#include <mapnik/symbolizer_enumerations.hpp>
#include <mapnik/util/noncopyable.hpp>
#include <mapnik/pixel_position.hpp>
#include <mapnik/text/color_font_renderer.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_pixfmt_rgba.h"
#include <agg_trans_affine.h>
#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<double> bbox;
glyph_t(FT_Glyph image_,
detail::evaluated_format_properties const& properties_,
pixel_position const& pos_,
rotation const& rot_,
double size_,
box2d<double> 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 <std::size_t PixelWidth>
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 <std::size_t PixelWidth>
void render_halo_id(unsigned char *buffer,
unsigned width,
unsigned height,
mapnik::value_integer feature_id,
int x, int y,
int halo_radius);
};
}

View file

@ -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);
}
};
}

View file

@ -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

View file

@ -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 <mapnik/text/color_font_renderer.hpp>
#include <mapnik/text/renderer.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/image_scaling.hpp>
#include <mapnik/text/face.hpp>
#include <mapnik/agg_rasterizer.hpp>
#include <mapnik/image_util.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#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 <typename Pixmap, typename ImageAccessor>
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<color_type, order_type>;
using pixfmt_comp_type = agg::pixfmt_custom_blend_rgba<blender_type, agg::rendering_buffer>;
using renderer_base = agg::renderer_base<pixfmt_comp_type>;
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<agg::comp_op_e>(comp_op));
renderer_base renb(pixf);
agg::span_allocator<color_type> sa;
agg::image_filter_bilinear filter_kernel;
agg::image_filter_lut filter(filter_kernel, false);
using interpolator_type = agg::span_interpolator_linear<agg::trans_affine>;
using span_gen_type = agg::span_image_resample_rgba_affine<ImageAccessor>;
using renderer_type = agg::renderer_scanline_aa_alpha<renderer_base,
agg::span_allocator<agg::rgba8>,
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<unsigned>(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<double> 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 <typename T>
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<glyph_pixfmt_type>;
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<T, img_accessor_type>(
pixmap, img_accessor, width, height, tr,
opacity, comp_op);
}
template
void composite_color_glyph<image_rgba8>(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<double> 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

View file

@ -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;
}

View file

@ -30,6 +30,22 @@
#include <mapnik/text/face.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/image_any.hpp>
#include <mapnik/agg_rasterizer.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#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<double> 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 <typename T>
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<unsigned>((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 <typename T>
agg_text_renderer<T>::agg_text_renderer (pixmap_type & pixmap,
halo_rasterizer_e rasterizer,
@ -229,6 +220,12 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g);
if (bit->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA)
{
continue;
}
else
{
composite_bitmap(pixmap_,
&bit->bitmap,
halo_fill,
@ -238,13 +235,37 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
halo_comp_op_);
}
}
}
else
{
error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1);
if (!error)
if (error)
{
continue;
}
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g);
render_halo(&bit->bitmap,
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<pixel_size>(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,
@ -267,20 +288,25 @@ void agg_text_renderer<T>::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<FT_BitmapGlyph>(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,
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_);
}
@ -300,7 +326,6 @@ void agg_text_renderer<T>::render(glyph_positions const& pos)
}
template <typename T>
void grid_text_renderer<T>::render(glyph_positions const& pos, value_integer feature_id)
{
@ -329,19 +354,42 @@ void grid_text_renderer<T>::render(glyph_positions const& pos, value_integer fea
if (!error)
{
FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image);
render_halo_id(&bit->bitmap,
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<pixel_size>(glyph_image.bytes(),
glyph_image.width(),
glyph_image.height(),
feature_id,
render_pos.x, render_pos.y,
static_cast<int>(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<int>(halo_radius));
}
}
FT_Done_Glyph(glyph.image);
}
}
template <typename T>
void agg_text_renderer<T>::render_halo(FT_Bitmap *bitmap,
template <std::size_t PixelWidth>
void agg_text_renderer<T>::render_halo(unsigned char *buffer,
unsigned width,
unsigned height,
unsigned rgba,
int x1,
int y1,
@ -349,8 +397,6 @@ void agg_text_renderer<T>::render_halo(FT_Bitmap *bitmap,
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<T>::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<T>::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<T>::render_halo(FT_Bitmap *bitmap,
}
template <typename T>
void grid_text_renderer<T>::render_halo_id(
FT_Bitmap *bitmap,
template <std::size_t PixelWidth>
void grid_text_renderer<T>::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)

@ -1 +1 @@
Subproject commit df578e3436681bb9bc582c7ac55a4205e98334f4
Subproject commit 114faca337e6dd1a0b31f30c285f8f4c29915e09