From e86758084cb1b4d03894afa611ff1b0456ab2447 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Fri, 28 Jul 2017 16:28:15 +0200 Subject: [PATCH] gems: painter for drawing sub-pixel accurate lines --- repos/gems/include/polygon_gfx/line_painter.h | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 repos/gems/include/polygon_gfx/line_painter.h diff --git a/repos/gems/include/polygon_gfx/line_painter.h b/repos/gems/include/polygon_gfx/line_painter.h new file mode 100644 index 000000000..7e6ce7ee5 --- /dev/null +++ b/repos/gems/include/polygon_gfx/line_painter.h @@ -0,0 +1,184 @@ +/* + * \brief Functor for drawing lines on a surface + * \author Norman Feske + * \date 2017-07-27 + */ + +/* + * Copyright (C) 2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__POLYGON_GFX__LINE_PAINTER_H_ +#define _INCLUDE__POLYGON_GFX__LINE_PAINTER_H_ + +#include +#include + + +struct Line_painter +{ + typedef Genode::Surface_base::Rect Rect; + typedef Genode::Surface_base::Point Point; + + /** + * Fixpoint number with 8 fractional bits + */ + class Fixpoint + { + public: + + enum { FRAC_BITS = 8, FRAC_MASK = (1 << FRAC_BITS) - 1 }; + + long const value; + + private: + + Fixpoint(long v) : value(v) { } + + public: + + static Fixpoint from_int(long v) { return Fixpoint(v << FRAC_BITS); } + static Fixpoint from_raw(long v) { return Fixpoint(v); } + + long integer() const { return value >> FRAC_BITS; } + long fractional() const { return value & FRAC_MASK; } + }; + + /** + * Look-up table used for the non-linear application of alpha values + */ + struct Lut + { + unsigned char value[255]; + + Lut() + { + auto fill_segment = [&] (long x1, long y1, long x2, long y2) + { + for (long i = x1; i < x2; i++) value[i] = y1; + }; + + int const v = 210; + bezier(0, 0, 255 - v, v, 255, 255, fill_segment, 6); + } + }; + + /* + * Use a single 'Lut' object across multiple instances of 'Line_painter'. + */ + static Lut const &_init_lut() { static Lut lut; return lut; } + + Lut const &_lut = _init_lut(); + + template + static void _mix_pixel(PT &dst, PT src, int alpha) + { + dst = PT::mix(dst, src, alpha); + } + + template + void _transfer_pixel(PT *dst, unsigned dst_w, PT pixel, int alpha, + Fixpoint x, Fixpoint y) const + { + dst += y.integer()*dst_w + x.integer(); + + /* + * The values of 'u' and 'v' correspond to the subpixel position + * of the coordinate (x, y) within its surrounding four pixels. + * They are used to weight the transfer of the color value to + * those pixels. + */ + unsigned long const u = x.fractional(), inv_u = 255 - u, + v = y.fractional(), inv_v = 255 - v; + + _mix_pixel(dst[0], pixel, _lut.value[(alpha * inv_u * inv_v) >> 16]); + _mix_pixel(dst[1], pixel, _lut.value[(alpha * u * inv_v) >> 16]); + _mix_pixel(dst[dst_w], pixel, _lut.value[(alpha * inv_u * v) >> 16]); + _mix_pixel(dst[dst_w + 1], pixel, _lut.value[(alpha * u * v) >> 16]); + } + + /** + * Draw line with sub-pixel accuracy + * + * The line is drawn only if both points reside within the clipping area of + * the surface. + * + * Internally, the coordinates are interpolated as fixpoint values with a + * fractional part of 16 bits. Therefore, the integer part of the + * coordinate arguments must not exceed 16 bits (on 32-bit platforms where + * 'long' has 32 bits). + * + * This function does not call 'surface.flush_pixels()' as it is meant + * to draw curve segments at a fine granularity. In this case, only one + * call of 'flush_pixels' per curve is preferred. + */ + template + void paint(Genode::Surface &surface, + Fixpoint x1, Fixpoint y1, Fixpoint x2, Fixpoint y2, + Genode::Color color) const + { + using namespace Genode; + + /* + * Reduce clipping area by one pixel as the antialiased line touches an + * area of 2x2 for each pixel. + */ + Rect const clip(surface.clip().p1(), surface.clip().p2() + Point(-1, -1)); + + /* both points must reside within clipping area */ + if (!surface.clip().contains(Point(x1.integer(), y1.integer())) || + !surface.clip().contains(Point(x2.integer(), y2.integer()))) + return; + + long const dx_f = x2.value - x1.value, + dy_f = y2.value - y1.value; + + long const num_steps = max(abs(dx_f) + 127, abs(dy_f) + 127) >> 8; + + if (num_steps == 0) + return; + + /* + * For the interpolation of the coordinates, we use a 16 bits for the + * fractional part (8 bit of the 'Fixpoint' value + 8 bit additional + * precision. + */ + long const x_ascent = (dx_f << 8)/num_steps, + y_ascent = (dy_f << 8)/num_steps; + + PT const pixel(color.r, color.g, color.b); + int const alpha = color.a; + + PT * const dst = surface.addr(); + unsigned const dst_w = surface.size().w(); + + long x = x1.value << 8, y = y1.value << 8; + + for (long i = 0; i < num_steps; i++) { + + _transfer_pixel(dst, dst_w, pixel, alpha, + Fixpoint::from_raw(x >> 8), + Fixpoint::from_raw(y >> 8)); + + x += x_ascent; + y += y_ascent; + } + } + + template + void paint(Genode::Surface &surface, Point p1, Point p2, + Genode::Color color) const + { + paint(surface, + Fixpoint::from_int(p1.x()), Fixpoint::from_int(p1.y()), + Fixpoint::from_int(p2.x()), Fixpoint::from_int(p2.y()), + color); + + surface.flush_pixels(Rect(p1, p2)); + } +}; + +#endif /* _INCLUDE__POLYGON_GFX__LINE_PAINTER_H_ */