From 179fa413b047bede6e32109e2ce82437c5fb8d34 Mon Sep 17 00:00:00 2001 From: MenTaLguY Date: Mon, 16 Jan 2006 02:36:01 +0000 Subject: moving trunk for module inkscape (bzr r1) --- src/display/.cvsignore | 7 + src/display/Makefile_insert | 66 ++ src/display/bezier-utils-test.cpp | 334 ++++++ src/display/bezier-utils-work.txt | 34 + src/display/bezier-utils.cpp | 983 ++++++++++++++++ src/display/bezier-utils.h | 49 + src/display/canvas-arena.cpp | 451 ++++++++ src/display/canvas-arena.h | 58 + src/display/canvas-bpath.cpp | 485 ++++++++ src/display/canvas-bpath.h | 99 ++ src/display/canvas-grid.cpp | 308 +++++ src/display/canvas-grid.h | 49 + src/display/curve.cpp | 1289 +++++++++++++++++++++ src/display/curve.h | 133 +++ src/display/display-forward.h | 46 + src/display/gnome-canvas-acetate.cpp | 100 ++ src/display/gnome-canvas-acetate.h | 41 + src/display/guideline.cpp | 198 ++++ src/display/guideline.h | 55 + src/display/makefile.in | 17 + src/display/nr-arena-forward.h | 51 + src/display/nr-arena-glyphs.cpp | 679 +++++++++++ src/display/nr-arena-glyphs.h | 109 ++ src/display/nr-arena-group.cpp | 256 +++++ src/display/nr-arena-group.h | 46 + src/display/nr-arena-image.cpp | 258 +++++ src/display/nr-arena-image.h | 51 + src/display/nr-arena-item.cpp | 1155 +++++++++++++++++++ src/display/nr-arena-item.h | 188 +++ src/display/nr-arena-shape.cpp | 1298 +++++++++++++++++++++ src/display/nr-arena-shape.h | 213 ++++ src/display/nr-arena.cpp | 131 +++ src/display/nr-arena.h | 57 + src/display/nr-gradient-gpl.cpp | 306 +++++ src/display/nr-gradient-gpl.h | 41 + src/display/nr-plain-stuff-gdk.cpp | 46 + src/display/nr-plain-stuff-gdk.h | 32 + src/display/nr-plain-stuff.cpp | 94 ++ src/display/nr-plain-stuff.h | 33 + src/display/sodipodi-ctrl.cpp | 494 ++++++++ src/display/sodipodi-ctrl.h | 65 ++ src/display/sodipodi-ctrlrect.cpp | 330 ++++++ src/display/sodipodi-ctrlrect.h | 70 ++ src/display/sp-canvas-util.cpp | 307 +++++ src/display/sp-canvas-util.h | 61 + src/display/sp-canvas.cpp | 2074 ++++++++++++++++++++++++++++++++++ src/display/sp-canvas.h | 186 +++ src/display/sp-ctrlline.cpp | 217 ++++ src/display/sp-ctrlline.h | 45 + src/display/sp-ctrlquadr.cpp | 210 ++++ src/display/sp-ctrlquadr.h | 43 + src/display/testnr.cpp | 24 + 52 files changed, 13972 insertions(+) create mode 100644 src/display/.cvsignore create mode 100644 src/display/Makefile_insert create mode 100644 src/display/bezier-utils-test.cpp create mode 100644 src/display/bezier-utils-work.txt create mode 100644 src/display/bezier-utils.cpp create mode 100644 src/display/bezier-utils.h create mode 100644 src/display/canvas-arena.cpp create mode 100644 src/display/canvas-arena.h create mode 100644 src/display/canvas-bpath.cpp create mode 100644 src/display/canvas-bpath.h create mode 100644 src/display/canvas-grid.cpp create mode 100644 src/display/canvas-grid.h create mode 100644 src/display/curve.cpp create mode 100644 src/display/curve.h create mode 100644 src/display/display-forward.h create mode 100644 src/display/gnome-canvas-acetate.cpp create mode 100644 src/display/gnome-canvas-acetate.h create mode 100644 src/display/guideline.cpp create mode 100644 src/display/guideline.h create mode 100644 src/display/makefile.in create mode 100644 src/display/nr-arena-forward.h create mode 100644 src/display/nr-arena-glyphs.cpp create mode 100644 src/display/nr-arena-glyphs.h create mode 100644 src/display/nr-arena-group.cpp create mode 100644 src/display/nr-arena-group.h create mode 100644 src/display/nr-arena-image.cpp create mode 100644 src/display/nr-arena-image.h create mode 100644 src/display/nr-arena-item.cpp create mode 100644 src/display/nr-arena-item.h create mode 100644 src/display/nr-arena-shape.cpp create mode 100644 src/display/nr-arena-shape.h create mode 100644 src/display/nr-arena.cpp create mode 100644 src/display/nr-arena.h create mode 100644 src/display/nr-gradient-gpl.cpp create mode 100644 src/display/nr-gradient-gpl.h create mode 100644 src/display/nr-plain-stuff-gdk.cpp create mode 100644 src/display/nr-plain-stuff-gdk.h create mode 100644 src/display/nr-plain-stuff.cpp create mode 100644 src/display/nr-plain-stuff.h create mode 100644 src/display/sodipodi-ctrl.cpp create mode 100644 src/display/sodipodi-ctrl.h create mode 100644 src/display/sodipodi-ctrlrect.cpp create mode 100644 src/display/sodipodi-ctrlrect.h create mode 100644 src/display/sp-canvas-util.cpp create mode 100644 src/display/sp-canvas-util.h create mode 100644 src/display/sp-canvas.cpp create mode 100644 src/display/sp-canvas.h create mode 100644 src/display/sp-ctrlline.cpp create mode 100644 src/display/sp-ctrlline.h create mode 100644 src/display/sp-ctrlquadr.cpp create mode 100644 src/display/sp-ctrlquadr.h create mode 100644 src/display/testnr.cpp (limited to 'src/display') diff --git a/src/display/.cvsignore b/src/display/.cvsignore new file mode 100644 index 000000000..49cf2d7a9 --- /dev/null +++ b/src/display/.cvsignore @@ -0,0 +1,7 @@ +.deps +.dirstamp +.libs +Makefile +Makefile.in +makefile +*-test diff --git a/src/display/Makefile_insert b/src/display/Makefile_insert new file mode 100644 index 000000000..de6ec85c2 --- /dev/null +++ b/src/display/Makefile_insert @@ -0,0 +1,66 @@ +## Makefile.am fragment sourced by src/Makefile.am. +# +# Sodipodi GnomeCanvas objects +# Author: Lauris Kaplinski +# +# Here are major objects, used for displaying things +# + +display/all: display/libspdisplay.a + +display/clean: + rm -f display/libspdisplay.a $(display_libspdisplay_a_OBJECTS) + +display/canvas-arena.$(OBJEXT): helper/sp-marshal.h +display/sp-canvas.$(OBJEXT): helper/sp-marshal.h + +display_libspdisplay_a_SOURCES = \ + display/nr-arena-forward.h \ + display/nr-arena.cpp \ + display/nr-arena.h \ + display/nr-arena-item.cpp \ + display/nr-arena-item.h \ + display/nr-arena-group.cpp \ + display/nr-arena-group.h \ + display/nr-arena-image.cpp \ + display/nr-arena-image.h \ + display/nr-arena-shape.cpp \ + display/nr-arena-shape.h \ + display/nr-arena-glyphs.cpp \ + display/nr-arena-glyphs.h \ + display/canvas-arena.cpp \ + display/canvas-arena.h \ + display/bezier-utils.cpp \ + display/bezier-utils.h \ + display/canvas-bpath.cpp \ + display/canvas-bpath.h \ + display/canvas-grid.cpp \ + display/canvas-grid.h \ + display/curve.cpp \ + display/curve.h \ + display/display-forward.h \ + display/gnome-canvas-acetate.cpp \ + display/gnome-canvas-acetate.h \ + display/guideline.cpp \ + display/guideline.h \ + display/nr-gradient-gpl.cpp \ + display/nr-gradient-gpl.h \ + display/nr-plain-stuff-gdk.cpp \ + display/nr-plain-stuff-gdk.h \ + display/nr-plain-stuff.cpp \ + display/nr-plain-stuff.h \ + display/sodipodi-ctrl.cpp \ + display/sodipodi-ctrl.h \ + display/sodipodi-ctrlrect.cpp \ + display/sodipodi-ctrlrect.h \ + display/sp-canvas-util.cpp \ + display/sp-canvas-util.h \ + display/sp-canvas.cpp \ + display/sp-canvas.h \ + display/sp-ctrlline.cpp \ + display/sp-ctrlline.h \ + display/sp-ctrlquadr.cpp \ + display/sp-ctrlquadr.h + +display_bezier_utils_test_SOURCES = display/bezier-utils-test.cpp +display_bezier_utils_test_LDADD = libnr/libnr.a -lglib-2.0 diff --git a/src/display/bezier-utils-test.cpp b/src/display/bezier-utils-test.cpp new file mode 100644 index 000000000..d42672113 --- /dev/null +++ b/src/display/bezier-utils-test.cpp @@ -0,0 +1,334 @@ +#include "../utest/utest.h" +#include +#include /* NR_DF_TEST_CLOSE */ + +/* mental disclaims all responsibility for this evil idea for testing + static functions. The main disadvantages are that we retain the + #define's and `using' directives of the included file. */ +#include "bezier-utils.cpp" + +using NR::Point; + +static bool range_approx_equal(double const a[], double const b[], unsigned len); + +/* (Returns false if NaN encountered.) */ +template +static bool range_equal(T const a[], T const b[], unsigned len) { + for (unsigned i = 0; i < len; ++i) { + if ( a[i] != b[i] ) { + return false; + } + } + return true; +} + +inline bool point_approx_equal(NR::Point const &a, NR::Point const &b, double const eps) +{ + using NR::X; using NR::Y; + return ( NR_DF_TEST_CLOSE(a[X], b[X], eps) && + NR_DF_TEST_CLOSE(a[Y], b[Y], eps) ); +} + +static inline double square(double const x) { + return x * x; +} + +/** Determine whether the found control points are the same as previously found on some developer's + machine. Doesn't call utest__fail, just writes a message to stdout for diagnostic purposes: + the most important test is that the root-mean-square of errors in the estimation are low rather + than that the control points found are the same. +**/ +static void compare_ctlpts(Point const est_b[], Point const exp_est_b[]) +{ + unsigned diff_mask = 0; + for (unsigned i = 0; i < 4; ++i) { + for (unsigned d = 0; d < 2; ++d) { + if ( fabs( est_b[i][d] - exp_est_b[i][d] ) > 1.1e-5 ) { + diff_mask |= 1 << ( i * 2 + d ); + } + } + } + if ( diff_mask != 0 ) { + printf("Warning: got different control points from previously-coded (diffs=0x%x).\n", + diff_mask); + printf(" Previous:"); + for (unsigned i = 0; i < 4; ++i) { + printf(" (%g, %g)", exp_est_b[i][0], exp_est_b[i][1]); // localizing ok + } + putchar('\n'); + printf(" Found: "); + for (unsigned i = 0; i < 4; ++i) { + printf(" (%g, %g)", est_b[i][0], est_b[i][1]); // localizing ok + } + putchar('\n'); + } +} + +static void compare_rms(Point const est_b[], double const t[], Point const d[], unsigned const n, + double const exp_rms_error) +{ + double sum_errsq = 0.0; + for (unsigned i = 0; i < n; ++i) { + Point const fit_pt = bezier_pt(3, est_b, t[i]); + Point const diff = fit_pt - d[i]; + sum_errsq += dot(diff, diff); + } + double const rms_error = sqrt( sum_errsq / n ); + UTEST_ASSERT( rms_error <= exp_rms_error + 1.1e-6 ); + if ( rms_error < exp_rms_error - 1.1e-6 ) { + /* The fitter code appears to have improved [or the floating point calculations differ + on this machine from the machine where exp_rms_error was calculated]. */ + printf("N.B. rms_error regression requirement can be decreased: have rms_error=%g.\n", rms_error); // localizing ok + } +} + +int main(int argc, char *argv[]) { + utest_start("bezier-utils.cpp"); + + UTEST_TEST("copy_without_nans_or_adjacent_duplicates") { + NR::Point const src[] = { + Point(2., 3.), + Point(2., 3.), + Point(0., 0.), + Point(2., 3.), + Point(2., 3.), + Point(1., 9.), + Point(1., 9.) + }; + Point const exp_dest[] = { + Point(2., 3.), + Point(0., 0.), + Point(2., 3.), + Point(1., 9.) + }; + g_assert( G_N_ELEMENTS(src) == 7 ); + Point dest[7]; + struct tst { + unsigned src_ix0; + unsigned src_len; + unsigned exp_dest_ix0; + unsigned exp_dest_len; + } const test_data[] = { + /* src start ix, src len, exp_dest start ix, exp dest len */ + {0, 0, 0, 0}, + {2, 1, 1, 1}, + {0, 1, 0, 1}, + {0, 2, 0, 1}, + {0, 3, 0, 2}, + {1, 3, 0, 3}, + {0, 5, 0, 3}, + {0, 6, 0, 4}, + {0, 7, 0, 4} + }; + for (unsigned i = 0 ; i < G_N_ELEMENTS(test_data) ; ++i) { + tst const &t = test_data[i]; + UTEST_ASSERT( t.exp_dest_len + == copy_without_nans_or_adjacent_duplicates(src + t.src_ix0, + t.src_len, + dest) ); + UTEST_ASSERT(range_equal(dest, + exp_dest + t.exp_dest_ix0, + t.exp_dest_len)); + } + } + + UTEST_TEST("bezier_pt(1)") { + Point const a[] = {Point(2.0, 4.0), + Point(1.0, 8.0)}; + UTEST_ASSERT( bezier_pt(1, a, 0.0) == a[0] ); + UTEST_ASSERT( bezier_pt(1, a, 1.0) == a[1] ); + UTEST_ASSERT( bezier_pt(1, a, 0.5) == Point(1.5, 6.0) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + UTEST_ASSERT( bezier_pt(1, a, ti) == si * a[0] + ti * a[1] ); + } + } + + UTEST_TEST("bezier_pt(2)") { + Point const b[] = {Point(1.0, 2.0), + Point(8.0, 4.0), + Point(3.0, 1.0)}; + UTEST_ASSERT( bezier_pt(2, b, 0.0) == b[0] ); + UTEST_ASSERT( bezier_pt(2, b, 1.0) == b[2] ); + UTEST_ASSERT( bezier_pt(2, b, 0.5) == Point(5.0, 2.75) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + Point const exp_pt( si*si * b[0] + 2*si*ti * b[1] + ti*ti * b[2] ); + Point const pt(bezier_pt(2, b, ti)); + UTEST_ASSERT(point_approx_equal(pt, exp_pt, 1e-11)); + } + } + + Point const c[] = {Point(1.0, 2.0), + Point(8.0, 4.0), + Point(3.0, 1.0), + Point(-2.0, -4.0)}; + UTEST_TEST("bezier_pt(3)") { + UTEST_ASSERT( bezier_pt(3, c, 0.0) == c[0] ); + UTEST_ASSERT( bezier_pt(3, c, 1.0) == c[3] ); + UTEST_ASSERT( bezier_pt(3, c, 0.5) == Point(4.0, 13.0/8.0) ); + double const t[] = {0.5, 0.25, 0.3, 0.6}; + for (unsigned i = 0; i < G_N_ELEMENTS(t); ++i) { + double const ti = t[i], si = 1.0 - ti; + UTEST_ASSERT( LInfty( bezier_pt(3, c, ti) + - ( si*si*si * c[0] + + 3*si*si*ti * c[1] + + 3*si*ti*ti * c[2] + + ti*ti*ti * c[3] ) ) + < 1e-4 ); + } + } + + struct Err_tst { + Point pt; + double u; + double err; + } const err_tst[] = { + {c[0], 0.0, 0.0}, + {Point(4.0, 13.0/8.0), 0.5, 0.0}, + {Point(4.0, 2.0), 0.5, 9.0/64.0}, + {Point(3.0, 2.0), 0.5, 1.0 + 9.0/64.0}, + {Point(6.0, 2.0), 0.5, 4.0 + 9.0/64.0}, + {c[3], 1.0, 0.0}, + }; + + UTEST_TEST("compute_max_error_ratio") { + Point d[G_N_ELEMENTS(err_tst)]; + double u[G_N_ELEMENTS(err_tst)]; + for (unsigned i = 0; i < G_N_ELEMENTS(err_tst); ++i) { + Err_tst const &t = err_tst[i]; + d[i] = t.pt; + u[i] = t.u; + } + g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(d) ); + unsigned max_ix = ~0u; + double const err_ratio = compute_max_error_ratio(d, u, G_N_ELEMENTS(d), c, 1.0, &max_ix); + UTEST_ASSERT( fabs( sqrt(err_tst[4].err) - err_ratio ) < 1e-12 ); + UTEST_ASSERT( max_ix == 4 ); + } + + UTEST_TEST("chord_length_parameterize") { + /* n == 2 */ + { + Point const d[] = {Point(2.9415, -5.8149), + Point(23.021, 4.9814)}; + double u[G_N_ELEMENTS(d)]; + double const exp_u[] = {0.0, 1.0}; + g_assert( G_N_ELEMENTS(u) == G_N_ELEMENTS(exp_u) ); + chord_length_parameterize(d, u, G_N_ELEMENTS(d)); + UTEST_ASSERT(range_equal(u, exp_u, G_N_ELEMENTS(exp_u))); + } + + /* Straight line. */ + { + double const exp_u[] = {0.0, 0.1829, 0.2105, 0.2105, 0.619, 0.815, 0.999, 1.0}; + unsigned const n = G_N_ELEMENTS(exp_u); + Point d[n]; + double u[n]; + Point const a(-23.985, 4.915), b(4.9127, 5.203); + for (unsigned i = 0; i < n; ++i) { + double bi = exp_u[i], ai = 1.0 - bi; + d[i] = ai * a + bi * b; + } + chord_length_parameterize(d, u, n); + UTEST_ASSERT(range_approx_equal(u, exp_u, n)); + } + } + + /* Feed it some points that can be fit exactly with a single bezier segment, and see how + well it manages. */ + Point const src_b[4] = {Point(5., -3.), + Point(8., 0.), + Point(4., 2.), + Point(3., 3.)}; + double const t[] = {0.0, .001, .03, .05, .09, .13, .18, .25, .29, .33, .39, .44, + .51, .57, .62, .69, .75, .81, .91, .93, .97, .98, .999, 1.0}; + unsigned const n = G_N_ELEMENTS(t); + Point d[n]; + for (unsigned i = 0; i < n; ++i) { + d[i] = bezier_pt(3, src_b, t[i]); + } + Point const tHat1(unit_vector( src_b[1] - src_b[0] )); + Point const tHat2(unit_vector( src_b[2] - src_b[3] )); + + UTEST_TEST("generate_bezier") { + Point est_b[4]; + generate_bezier(est_b, d, t, n, tHat1, tHat2, 1.0); + + compare_ctlpts(est_b, src_b); + + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, 1e-8); + } + + UTEST_TEST("sp_bezier_fit_cubic_full") { + Point est_b[4]; + int splitpoints[2]; + gint const succ = sp_bezier_fit_cubic_full(est_b, splitpoints, d, n, tHat1, tHat2, square(1.2), 1); + UTEST_ASSERT( succ == 1 ); + + Point const exp_est_b[4] = { + Point(5.000000, -3.000000), + Point(7.5753, -0.4247), + Point(4.77533, 1.22467), + Point(3, 3) + }; + compare_ctlpts(est_b, exp_est_b); + + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, .307911); + } + + UTEST_TEST("sp_bezier_fit_cubic") { + Point est_b[4]; + gint const succ = sp_bezier_fit_cubic(est_b, d, n, square(1.2)); + UTEST_ASSERT( succ == 1 ); + + Point const exp_est_b[4] = { + Point(5.000000, -3.000000), + Point(7.57134, -0.423509), + Point(4.77929, 1.22426), + Point(3, 3) + }; + compare_ctlpts(est_b, exp_est_b); + +#if 1 /* A change has been made to right_tangent. I believe that usually this change + will result in better fitting, but it won't do as well for this example where + we happen to be feeding a t=0.999 point to the fitter. */ + printf("TODO: Update this test case for revised right_tangent implementation.\n"); + /* In particular, have a test case to show whether the new implementation + really is likely to be better on average. */ +#else + /* We're being unfair here in using our t[] rather than best t[] for est_b: we + may over-estimate RMS of errors. */ + compare_rms(est_b, t, d, n, .307983); +#endif + } + + return !utest_end(); +} + +/* (Returns false if NaN encountered.) */ +static bool range_approx_equal(double const a[], double const b[], unsigned const len) { + for (unsigned i = 0; i < len; ++i) { + if (!( fabs( a[i] - b[i] ) < 1e-4 )) { + return false; + } + } + return true; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/bezier-utils-work.txt b/src/display/bezier-utils-work.txt new file mode 100644 index 000000000..8604c1207 --- /dev/null +++ b/src/display/bezier-utils-work.txt @@ -0,0 +1,34 @@ +min .5 * sum_i lensq(bez_pt(b, u[i]) - d[i]) + +lensq(d)=dot(d, d) = d.x * d.x + d.y * d.y + +sum_i (f(i) + g(i)) = sum_i f(i) + sum_i g(i), so +we can separate into x,y parts. Since they are the same, we write `z' in the below +to mean either x or y. + +.5 * sum_i (bez_pt(b, u[i]) - d[i]).z^2 + += .5 * sum_i (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B2(u[i]) * b[2] + + B3(u[i]) * b[3] + - d[i] ).z^2 + += H. + +Suppose that b[0,1,3] are fixed (with b[1] perhaps being calculated +from a prior call to existing generate_bezier). + +d H / d b[2].z = sum_i B2(u[i]) * (bez_pt(b, u[i]) - d[i]).z + +Solve for dH/db[2].z==0: + +-sum_i B2(u[i]) B2(u[i]) * b[2].z = sum_i B2(u[i]) * (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B3(u[i]) * b[3] + - d[i] ).z +b[2].z = ((sum_i B2(u[i]) * (B0(u[i]) * b[0] + + B1(u[i]) * b[1] + + B3(u[i]) * b[3] + - d[i] ).z) + / -sum_i (B2(u[i]))^2) diff --git a/src/display/bezier-utils.cpp b/src/display/bezier-utils.cpp new file mode 100644 index 000000000..8316c86cc --- /dev/null +++ b/src/display/bezier-utils.cpp @@ -0,0 +1,983 @@ +#define __SP_BEZIER_UTILS_C__ + +/** \file + * Bezier interpolation for inkscape drawing code. + */ +/* + * Original code published in: + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski + * Peter Moulder + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2003,2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define SP_HUGE 1e5 +#define noBEZIER_DEBUG + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_IEEEFP_H +# include +#endif + +#include +#include +#include "bezier-utils.h" +#include + +#include "isnan.h" + + +typedef NR::Point BezierCurve[]; + +/* Forward declarations */ +static void generate_bezier(NR::Point b[], NR::Point const d[], gdouble const u[], unsigned len, + NR::Point const &tHat1, NR::Point const &tHat2, double tolerance_sq); +static void estimate_lengths(NR::Point bezier[], + NR::Point const data[], gdouble const u[], unsigned len, + NR::Point const &tHat1, NR::Point const &tHat2); +static void estimate_bi(NR::Point b[4], unsigned ei, + NR::Point const data[], double const u[], unsigned len); +static void reparameterize(NR::Point const d[], unsigned len, double u[], BezierCurve const bezCurve); +static gdouble NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble u); +static NR::Point bezier_pt(unsigned degree, NR::Point const V[], gdouble t); +static NR::Point sp_darray_center_tangent(NR::Point const d[], unsigned center, unsigned length); +static NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const len); +static unsigned copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[]); +static void chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned len); +static double compute_max_error_ratio(NR::Point const d[], double const u[], unsigned len, + BezierCurve const bezCurve, double tolerance, + unsigned *splitPoint); +static double compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve, + double const tolerance); + + +static NR::Point const unconstrained_tangent(0, 0); + + +/* + * B0, B1, B2, B3 : Bezier multipliers + */ + +#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B2(u) ( 3 * u * u * ( 1.0 - u ) ) +#define B3(u) ( u * u * u ) + +#ifdef BEZIER_DEBUG +# define DOUBLE_ASSERT(x) g_assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) ) +# define BEZIER_ASSERT(b) do { \ + DOUBLE_ASSERT((b)[0][NR::X]); DOUBLE_ASSERT((b)[0][NR::Y]); \ + DOUBLE_ASSERT((b)[1][NR::X]); DOUBLE_ASSERT((b)[1][NR::Y]); \ + DOUBLE_ASSERT((b)[2][NR::X]); DOUBLE_ASSERT((b)[2][NR::Y]); \ + DOUBLE_ASSERT((b)[3][NR::X]); DOUBLE_ASSERT((b)[3][NR::Y]); \ + } while(0) +#else +# define DOUBLE_ASSERT(x) do { } while(0) +# define BEZIER_ASSERT(b) do { } while(0) +#endif + + +/** + * Fit a single-segment Bezier curve to a set of digitized points. + * + * \return Number of segments generated, or -1 on error. + */ +gint +sp_bezier_fit_cubic(NR::Point *bezier, NR::Point const *data, gint len, gdouble error) +{ + return sp_bezier_fit_cubic_r(bezier, data, len, error, 1); +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, with + * possible weedout of identical points and NaNs. + * + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + * + * \return Number of segments generated, or -1 on error. + */ +gint +sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint const len, gdouble const error, unsigned const max_beziers) +{ + g_return_val_if_fail(bezier != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + g_return_val_if_fail(max_beziers < (1ul << (31 - 2 - 1 - 3)), -1); + + NR::Point *uniqued_data = g_new(NR::Point, len); + unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data); + + if ( uniqued_len < 2 ) { + g_free(uniqued_data); + return 0; + } + + /* Call fit-cubic function with recursion. */ + gint const ret = sp_bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len, + unconstrained_tangent, unconstrained_tangent, + error, max_beziers); + g_free(uniqued_data); + return ret; +} + +/** + * Copy points from src to dest, filter out points containing NaN and + * adjacent points with equal x and y. + * \return length of dest + */ +static unsigned +copy_without_nans_or_adjacent_duplicates(NR::Point const src[], unsigned src_len, NR::Point dest[]) +{ + unsigned si = 0; + for (;;) { + if ( si == src_len ) { + return 0; + } + if (!isNaN(src[si][NR::X]) && + !isNaN(src[si][NR::Y])) { + dest[0] = NR::Point(src[si]); + ++si; + break; + } + } + unsigned di = 0; + for (; si < src_len; ++si) { + NR::Point const src_pt = NR::Point(src[si]); + if ( src_pt != dest[di] + && !isNaN(src_pt[NR::X]) + && !isNaN(src_pt[NR::Y])) { + dest[++di] = src_pt; + } + } + unsigned dest_len = di + 1; + g_assert( dest_len <= src_len ); + return dest_len; +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, without + * possible weedout of identical points and NaNs. + * + * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + */ +gint +sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[], + NR::Point const data[], gint const len, + NR::Point const &tHat1, NR::Point const &tHat2, + double const error, unsigned const max_beziers) +{ + int const maxIterations = 4; /* Max times to try iterating */ + + g_return_val_if_fail(bezier != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + g_return_val_if_fail(max_beziers >= 1, -1); + g_return_val_if_fail(error >= 0.0, -1); + + if ( len < 2 ) return 0; + + if ( len == 2 ) { + /* We have 2 points, which can be fitted trivially. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + double const dist = ( L2( data[len - 1] + - data[0] ) + / 3.0 ); + if (isNaN(dist)) { + /* Numerical problem, fall back to straight line segment. */ + bezier[1] = bezier[0]; + bezier[2] = bezier[3]; + } else { + bezier[1] = ( is_zero(tHat1) + ? ( 2 * bezier[0] + bezier[3] ) / 3. + : bezier[0] + dist * tHat1 ); + bezier[2] = ( is_zero(tHat2) + ? ( bezier[0] + 2 * bezier[3] ) / 3. + : bezier[3] + dist * tHat2 ); + } + BEZIER_ASSERT(bezier); + return 1; + } + + /* Parameterize points, and attempt to fit curve */ + unsigned splitPoint; /* Point to split point set at. */ + bool is_corner; + { + double *u = g_new(double, len); + chord_length_parameterize(data, u, len); + if ( u[len - 1] == 0.0 ) { + /* Zero-length path: every point in data[] is the same. + * + * (Clients aren't allowed to pass such data; handling the case is defensive + * programming.) + */ + g_free(u); + return 0; + } + + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + + /* Find max deviation of points to fitted curve. */ + double const tolerance = sqrt(error + 1e-9); + double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + g_free(u); + return 1; + } + + /* If error not too large, then try some reparameterization and iteration. */ + if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) { + for (int i = 0; i < maxIterations; i++) { + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + g_free(u); + return 1; + } + } + } + g_free(u); + is_corner = (maxErrorRatio < 0); + } + + if (is_corner) { + g_assert(splitPoint < unsigned(len)); + if (splitPoint == 0) { + if (is_zero(tHat1)) { + /* Got spike even with unconstrained initial tangent. */ + ++splitPoint; + } else { + return sp_bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2, + error, max_beziers); + } + } else if (splitPoint == unsigned(len - 1)) { + if (is_zero(tHat2)) { + /* Got spike even with unconstrained final tangent. */ + --splitPoint; + } else { + return sp_bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent, + error, max_beziers); + } + } + } + + if ( 1 < max_beziers ) { + /* + * Fitting failed -- split at max error point and fit recursively + */ + unsigned const rec_max_beziers1 = max_beziers - 1; + + NR::Point recTHat2, recTHat1; + if (is_corner) { + g_return_val_if_fail(0 < splitPoint && splitPoint < unsigned(len - 1), -1); + recTHat1 = recTHat2 = unconstrained_tangent; + } else { + /* Unit tangent vector at splitPoint. */ + recTHat2 = sp_darray_center_tangent(data, splitPoint, len); + recTHat1 = -recTHat2; + } + gint const nsegs1 = sp_bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1, + tHat1, recTHat2, error, rec_max_beziers1); + if ( nsegs1 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[1]: recursive call failed\n"); +#endif + return -1; + } + g_assert( nsegs1 != 0 ); + if (split_points != NULL) { + split_points[nsegs1 - 1] = splitPoint; + } + unsigned const rec_max_beziers2 = max_beziers - nsegs1; + gint const nsegs2 = sp_bezier_fit_cubic_full(bezier + nsegs1*4, + ( split_points == NULL + ? NULL + : split_points + nsegs1 ), + data + splitPoint, len - splitPoint, + recTHat1, tHat2, error, rec_max_beziers2); + if ( nsegs2 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[2]: recursive call failed\n"); +#endif + return -1; + } + +#ifdef BEZIER_DEBUG + g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n", + nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers); +#endif + return nsegs1 + nsegs2; + } else { + return -1; + } +} + + +/** + * Fill in \a bezier[] based on the given data and tangent requirements, using + * a least-squares fit. + * + * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. + * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, + * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. + * + * \param tolerance_sq Used only for an initial guess as to tangent directions + * when \a tHat1 or \a tHat2 is zero. + */ +static void +generate_bezier(NR::Point bezier[], + NR::Point const data[], gdouble const u[], unsigned const len, + NR::Point const &tHat1, NR::Point const &tHat2, + double const tolerance_sq) +{ + bool const est1 = is_zero(tHat1); + bool const est2 = is_zero(tHat2); + NR::Point est_tHat1( est1 + ? sp_darray_left_tangent(data, len, tolerance_sq) + : tHat1 ); + NR::Point est_tHat2( est2 + ? sp_darray_right_tangent(data, len, tolerance_sq) + : tHat2 ); + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + /* We find that sp_darray_right_tangent tends to produce better results + for our current freehand tool than full estimation. */ + if (est1) { + estimate_bi(bezier, 1, data, u, len); + if (bezier[1] != bezier[0]) { + est_tHat1 = unit_vector(bezier[1] - bezier[0]); + } + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + } +} + + +static void +estimate_lengths(NR::Point bezier[], + NR::Point const data[], gdouble const uPrime[], unsigned const len, + NR::Point const &tHat1, NR::Point const &tHat2) +{ + double C[2][2]; /* Matrix C. */ + double X[2]; /* Matrix X. */ + + /* Create the C and X matrices. */ + C[0][0] = 0.0; + C[0][1] = 0.0; + C[1][0] = 0.0; + C[1][1] = 0.0; + X[0] = 0.0; + X[1] = 0.0; + + /* First and last control points of the Bezier curve are positioned exactly at the first and + last data points. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + + for (unsigned i = 0; i < len; i++) { + /* Bezier control point coefficients. */ + double const b0 = B0(uPrime[i]); + double const b1 = B1(uPrime[i]); + double const b2 = B2(uPrime[i]); + double const b3 = B3(uPrime[i]); + + /* rhs for eqn */ + NR::Point const a1 = b1 * tHat1; + NR::Point const a2 = b2 * tHat2; + + C[0][0] += dot(a1, a1); + C[0][1] += dot(a1, a2); + C[1][0] = C[0][1]; + C[1][1] += dot(a2, a2); + + /* Additional offset to the data point from the predicted point if we were to set bezier[1] + to bezier[0] and bezier[2] to bezier[3]. */ + NR::Point const shortfall + = ( data[i] + - ( ( b0 + b1 ) * bezier[0] ) + - ( ( b2 + b3 ) * bezier[3] ) ); + X[0] += dot(a1, shortfall); + X[1] += dot(a2, shortfall); + } + + /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. + Now solve for alpha. */ + double alpha_l, alpha_r; + + /* Compute the determinants of C and X. */ + double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + if ( det_C0_C1 != 0 ) { + /* Apparently Kramer's rule. */ + double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; + double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C0_X / det_C0_C1; + } else { + /* The matrix is under-determined. Try requiring alpha_l == alpha_r. + * + * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same + * variable in the equations. We can do this by adding the columns of C to form a single + * column, to be multiplied by alpha to give the column vector X. + * + * We try each row in turn. + */ + double const c0 = C[0][0] + C[0][1]; + if (c0 != 0) { + alpha_l = alpha_r = X[0] / c0; + } else { + double const c1 = C[1][0] + C[1][1]; + if (c1 != 0) { + alpha_l = alpha_r = X[1] / c1; + } else { + /* Let the below code handle this. */ + alpha_l = alpha_r = 0.; + } + } + } + + /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get + coincident control points that lead to divide by zero in any subsequent + NewtonRaphsonRootFind() call.) */ + /// \todo Check whether this special-casing is necessary now that + /// NewtonRaphsonRootFind handles non-positive denominator. + if ( alpha_l < 1.0e-6 || + alpha_r < 1.0e-6 ) + { + alpha_l = alpha_r = ( L2( data[len - 1] + - data[0] ) + / 3.0 ); + } + + /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and + right, respectively. */ + bezier[1] = alpha_l * tHat1 + bezier[0]; + bezier[2] = alpha_r * tHat2 + bezier[3]; + + return; +} + +static double lensq(NR::Point const p) { + return dot(p, p); +} + +static void +estimate_bi(NR::Point bezier[4], unsigned const ei, + NR::Point const data[], double const u[], unsigned const len) +{ + g_return_if_fail(1 <= ei && ei <= 2); + unsigned const oi = 3 - ei; + double num[2] = {0., 0.}; + double den = 0.; + for (unsigned i = 0; i < len; ++i) { + double const ui = u[i]; + double const b[4] = { + B0(ui), + B1(ui), + B2(ui), + B3(ui) + }; + + for (unsigned d = 0; d < 2; ++d) { + num[d] += b[ei] * (b[0] * bezier[0][d] + + b[oi] * bezier[oi][d] + + b[3] * bezier[3][d] + + - data[i][d]); + } + den -= b[ei] * b[ei]; + } + + if (den != 0.) { + for (unsigned d = 0; d < 2; ++d) { + bezier[ei][d] = num[d] / den; + } + } else { + bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.; + } +} + +/** + * Given set of points and their parameterization, try to find a better assignment of parameter + * values for the points. + * + * \param d Array of digitized points. + * \param u Current parameter values. + * \param bezCurve Current fitted curve. + * \param len Number of values in both d and u arrays. + * Also the size of the array that is allocated for return. + */ +static void +reparameterize(NR::Point const d[], + unsigned const len, + double u[], + BezierCurve const bezCurve) +{ + g_assert( 2 <= len ); + + unsigned const last = len - 1; + g_assert( bezCurve[0] == d[0] ); + g_assert( bezCurve[3] == d[last] ); + g_assert( u[0] == 0.0 ); + g_assert( u[last] == 1.0 ); + /* Otherwise, consider including 0 and last in the below loop. */ + + for (unsigned i = 1; i < last; i++) { + u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]); + } +} + +/** + * Use Newton-Raphson iteration to find better root. + * + * \param Q Current fitted curve + * \param P Digitized point + * \param u Parameter value for "P" + * + * \return Improved u + */ +static gdouble +NewtonRaphsonRootFind(BezierCurve const Q, NR::Point const &P, gdouble const u) +{ + g_assert( 0.0 <= u ); + g_assert( u <= 1.0 ); + + /* Generate control vertices for Q'. */ + NR::Point Q1[3]; + for (unsigned i = 0; i < 3; i++) { + Q1[i] = 3.0 * ( Q[i+1] - Q[i] ); + } + + /* Generate control vertices for Q''. */ + NR::Point Q2[2]; + for (unsigned i = 0; i < 2; i++) { + Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] ); + } + + /* Compute Q(u), Q'(u) and Q''(u). */ + NR::Point const Q_u = bezier_pt(3, Q, u); + NR::Point const Q1_u = bezier_pt(2, Q1, u); + NR::Point const Q2_u = bezier_pt(1, Q2, u); + + /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the + distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the + distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum + distance from P to Q(u)). */ + NR::Point const diff = Q_u - P; + double numerator = dot(diff, Q1_u); + double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u); + + double improved_u; + if ( denominator > 0. ) { + /* One iteration of Newton-Raphson: + improved_u = u - f(u)/f'(u) */ + improved_u = u - ( numerator / denominator ); + } else { + /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather + than local minimum), so we move an arbitrary amount in the right direction. */ + if ( numerator > 0. ) { + improved_u = u * .98 - .01; + } else if ( numerator < 0. ) { + /* Deliberately asymmetrical, to reduce the chance of cycling. */ + improved_u = .031 + u * .98; + } else { + improved_u = u; + } + } + + if (!isFinite(improved_u)) { + improved_u = u; + } else if ( improved_u < 0.0 ) { + improved_u = 0.0; + } else if ( improved_u > 1.0 ) { + improved_u = 1.0; + } + + /* Ensure that improved_u isn't actually worse. */ + { + double const diff_lensq = lensq(diff); + for (double proportion = .125; ; proportion += .125) { + if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) { + if ( proportion > 1.0 ) { + //g_warning("found proportion %g", proportion); + improved_u = u; + break; + } + improved_u = ( ( 1 - proportion ) * improved_u + + proportion * u ); + } else { + break; + } + } + } + + DOUBLE_ASSERT(improved_u); + return improved_u; +} + +/** + * Evaluate a Bezier curve at parameter value \a t. + * + * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. + * \param V The control points for the Bezier curve. Must have (\a degree+1) + * elements. + * \param t The "parameter" value, specifying whereabouts along the curve to + * evaluate. Typically in the range [0.0, 1.0]. + * + * Let s = 1 - t. + * BezierII(1, V) gives (s, t) * V, i.e. t of the way + * from V[0] to V[1]. + * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V. + * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V. + * + * The derivative of BezierII(i, V) with respect to t + * is i * BezierII(i-1, V'), where for all j, V'[j] = + * V[j + 1] - V[j]. + */ +static NR::Point +bezier_pt(unsigned const degree, NR::Point const V[], gdouble const t) +{ + /** Pascal's triangle. */ + static int const pascal[4][4] = {{1}, + {1, 1}, + {1, 2, 1}, + {1, 3, 3, 1}}; + g_assert( degree < G_N_ELEMENTS(pascal) ); + gdouble const s = 1.0 - t; + + /* Calculate powers of t and s. */ + double spow[4]; + double tpow[4]; + spow[0] = 1.0; spow[1] = s; + tpow[0] = 1.0; tpow[1] = t; + for (unsigned i = 1; i < degree; ++i) { + spow[i + 1] = spow[i] * s; + tpow[i + 1] = tpow[i] * t; + } + + NR::Point ret = spow[degree] * V[0]; + for (unsigned i = 1; i <= degree; ++i) { + ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i]; + } + return ret; +} + +/* + * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent : + * Approximate unit tangents at endpoints and "center" of digitized curve + */ + +/** + * Estimate the (forward) tangent at point d[first + 0.5]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * \pre (2 \<= len) and (d[0] != d[1]). + **/ +NR::Point +sp_darray_left_tangent(NR::Point const d[], unsigned const len) +{ + g_assert( len >= 2 ); + g_assert( d[0] != d[1] ); + return unit_vector( d[1] - d[0] ); +} + +/** + * Estimates the (backward) tangent at d[last - 0.5]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +static NR::Point +sp_darray_right_tangent(NR::Point const d[], unsigned const len) +{ + g_assert( 2 <= len ); + unsigned const last = len - 1; + unsigned const prev = last - 1; + g_assert( d[last] != d[prev] ); + return unit_vector( d[prev] - d[last] ); +} + +/** + * Estimate the (forward) tangent at point d[0]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * + * \pre 2 \<= len. + * \pre d[0] != d[1]. + * \pre all[p in d] in_svg_plane(p). + * \post is_unit_vector(ret). + **/ +NR::Point +sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq) +{ + g_assert( 2 <= len ); + g_assert( 0 <= tolerance_sq ); + for (unsigned i = 1;;) { + NR::Point const pi(d[i]); + NR::Point const t(pi - d[0]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + ++i; + if (i == len) { + return ( distsq == 0 + ? sp_darray_left_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[last]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +NR::Point +sp_darray_right_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq) +{ + g_assert( 2 <= len ); + g_assert( 0 <= tolerance_sq ); + unsigned const last = len - 1; + for (unsigned i = last - 1;; i--) { + NR::Point const pi(d[i]); + NR::Point const t(pi - d[last]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + if (i == 0) { + return ( distsq == 0 + ? sp_darray_right_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[center], by averaging the two + * segments connected to d[center] (and then normalizing the result). + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre (0 \< center \< len - 1) and d is uniqued (at least in + * the immediate vicinity of \a center). + */ +static NR::Point +sp_darray_center_tangent(NR::Point const d[], + unsigned const center, + unsigned const len) +{ + g_assert( center != 0 ); + g_assert( center < len - 1 ); + + NR::Point ret; + if ( d[center + 1] == d[center - 1] ) { + /* Rotate 90 degrees in an arbitrary direction. */ + NR::Point const diff = d[center] - d[center - 1]; + ret = NR::rot90(diff); + } else { + ret = d[center - 1] - d[center + 1]; + } + ret.normalize(); + return ret; +} + + +/** + * Assign parameter values to digitized points using relative distances between points. + * + * \pre Parameter array u must have space for \a len items. + */ +static void +chord_length_parameterize(NR::Point const d[], gdouble u[], unsigned const len) +{ + g_return_if_fail( 2 <= len ); + + /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ + u[0] = 0.0; + for (unsigned i = 1; i < len; i++) { + double const dist = L2( d[i] - d[i-1] ); + u[i] = u[i-1] + dist; + } + + /* Then scale to [0.0 .. 1.0]. */ + gdouble tot_len = u[len - 1]; + g_return_if_fail( tot_len != 0 ); + if (isFinite(tot_len)) { + for (unsigned i = 1; i < len; ++i) { + u[i] /= tot_len; + } + } else { + /* We could do better, but this probably never happens anyway. */ + for (unsigned i = 1; i < len; ++i) { + u[i] = i / (gdouble) ( len - 1 ); + } + } + + /** \todo + * It's been reported that u[len - 1] can differ from 1.0 on some + * systems (amd64), despite it having been calculated as x / x where x + * is isFinite and non-zero. + */ + if (u[len - 1] != 1) { + double const diff = u[len - 1] - 1; + if (fabs(diff) > 1e-13) { + g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1", + u[len - 1], diff); + } + u[len - 1] = 1; + } + +#ifdef BEZIER_DEBUG + g_assert( u[0] == 0.0 && u[len - 1] == 1.0 ); + for (unsigned i = 1; i < len; i++) { + g_assert( u[i] >= u[i-1] ); + } +#endif +} + + + + +/** + * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum + * error is non-zero) set \a *splitPoint to the corresponding index. + * + * \pre 2 \<= len. + * \pre u[0] == 0. + * \pre u[len - 1] == 1.0. + * \post ((ret == 0.0) + * || ((*splitPoint \< len - 1) + * \&\& (*splitPoint != 0 || ret \< 0.0))). + */ +static gdouble +compute_max_error_ratio(NR::Point const d[], double const u[], unsigned const len, + BezierCurve const bezCurve, double const tolerance, + unsigned *const splitPoint) +{ + g_assert( 2 <= len ); + unsigned const last = len - 1; + g_assert( bezCurve[0] == d[0] ); + g_assert( bezCurve[3] == d[last] ); + g_assert( u[0] == 0.0 ); + g_assert( u[last] == 1.0 ); + /* I.e. assert that the error for the first & last points is zero. + * Otherwise we should include those points in the below loop. + * The assertion is also necessary to ensure 0 < splitPoint < last. + */ + + double maxDistsq = 0.0; /* Maximum error */ + double max_hook_ratio = 0.0; + unsigned snap_end = 0; + NR::Point prev = bezCurve[0]; + for (unsigned i = 1; i <= last; i++) { + NR::Point const curr = bezier_pt(3, bezCurve, u[i]); + double const distsq = lensq( curr - d[i] ); + if ( distsq > maxDistsq ) { + maxDistsq = distsq; + *splitPoint = i; + } + double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); + if (max_hook_ratio < hook_ratio) { + max_hook_ratio = hook_ratio; + snap_end = i; + } + prev = curr; + } + + double const dist_ratio = sqrt(maxDistsq) / tolerance; + double ret; + if (max_hook_ratio <= dist_ratio) { + ret = dist_ratio; + } else { + g_assert(0 < snap_end); + ret = -max_hook_ratio; + *splitPoint = snap_end - 1; + } + g_assert( ret == 0.0 + || ( ( *splitPoint < last ) + && ( *splitPoint != 0 || ret < 0. ) ) ); + return ret; +} + +/** + * Whereas compute_max_error_ratio() checks for itself that each data point + * is near some point on the curve, this function checks that each point on + * the curve is near some data point (or near some point on the polyline + * defined by the data points, or something like that: we allow for a + * "reasonable curviness" from such a polyline). "Reasonable curviness" + * means we draw a circle centred at the midpoint of a..b, of radius + * proportional to the length |a - b|, and require that each point on the + * segment of bezCurve between the parameters of a and b be within that circle. + * If any point P on the bezCurve segment is outside of that allowable + * region (circle), then we return some metric that increases with the + * distance from P to the circle. + * + * Given that this is a fairly arbitrary criterion for finding appropriate + * places for sharp corners, we test only one point on bezCurve, namely + * the point on bezCurve with parameter halfway between our estimated + * parameters for a and b. (Alternatives are taking the farthest of a + * few parameters between those of a and b, or even using a variant of + * NewtonRaphsonFindRoot() for finding the maximum rather than minimum + * distance.) + */ +static double +compute_hook(NR::Point const &a, NR::Point const &b, double const u, BezierCurve const bezCurve, + double const tolerance) +{ + NR::Point const P = bezier_pt(3, bezCurve, u); + NR::Point const diff = .5 * (a + b) - P; + double const dist = NR::L2(diff); + if (dist < tolerance) { + return 0; + } + double const allowed = NR::L2(b - a) + tolerance; + return dist / allowed; + /** \todo + * effic: Hooks are very rare. We could start by comparing + * distsq, only resorting to the more expensive L2 in cases of + * uncertainty. + */ +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/bezier-utils.h b/src/display/bezier-utils.h new file mode 100644 index 000000000..f281ab220 --- /dev/null +++ b/src/display/bezier-utils.h @@ -0,0 +1,49 @@ +#ifndef __SP_BEZIER_UTILS_H__ +#define __SP_BEZIER_UTILS_H__ + +/* + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * from "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include + +/* Bezier approximation utils */ + +gint sp_bezier_fit_cubic(NR::Point bezier[], NR::Point const data[], gint len, gdouble error); + +gint sp_bezier_fit_cubic_r(NR::Point bezier[], NR::Point const data[], gint len, gdouble error, + unsigned max_beziers); + +gint sp_bezier_fit_cubic_full(NR::Point bezier[], int split_points[], NR::Point const data[], gint len, + NR::Point const &tHat1, NR::Point const &tHat2, + gdouble error, unsigned max_beziers); + +NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len); +NR::Point sp_darray_left_tangent(NR::Point const d[], unsigned const len, double const tolerance_sq); +NR::Point sp_darray_right_tangent(NR::Point const d[], unsigned const length, double const tolerance_sq); + + +#endif /* __SP_BEZIER_UTILS_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/canvas-arena.cpp b/src/display/canvas-arena.cpp new file mode 100644 index 000000000..1d77de158 --- /dev/null +++ b/src/display/canvas-arena.cpp @@ -0,0 +1,451 @@ +#define __SP_CANVAS_ARENA_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +enum { + ARENA_EVENT, + LAST_SIGNAL +}; + +static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass); +static void sp_canvas_arena_init(SPCanvasArena *group); +static void sp_canvas_arena_destroy(GtkObject *object); + +static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf); +static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); +static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event); + +static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event); + +static void sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data); +static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data); + +NRArenaEventVector carenaev = { + {NULL}, + sp_canvas_arena_request_update, + sp_canvas_arena_request_render +}; + +static SPCanvasItemClass *parent_class; +static guint signals[LAST_SIGNAL] = {0}; + +GtkType +sp_canvas_arena_get_type (void) +{ + static GtkType type = 0; + if (!type) { + GtkTypeInfo info = { + "SPCanvasArena", + sizeof (SPCanvasArena), + sizeof (SPCanvasArenaClass), + (GtkClassInitFunc) sp_canvas_arena_class_init, + (GtkObjectInitFunc) sp_canvas_arena_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_canvas_arena_class_init (SPCanvasArenaClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + signals[ARENA_EVENT] = gtk_signal_new ("arena_event", + GTK_RUN_LAST, + GTK_CLASS_TYPE(object_class), + GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event), + sp_marshal_INT__POINTER_POINTER, + GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER); + + object_class->destroy = sp_canvas_arena_destroy; + + item_class->update = sp_canvas_arena_update; + item_class->render = sp_canvas_arena_render; + item_class->point = sp_canvas_arena_point; + item_class->event = sp_canvas_arena_event; +} + +static void +sp_canvas_arena_init (SPCanvasArena *arena) +{ + arena->sticky = FALSE; + + arena->arena = NRArena::create(); + arena->root = NRArenaGroup::create(arena->arena); + nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE); + +#ifdef arena_item_tile_cache + arena->root->skipCaching=true; +#endif + + arena->active = NULL; + + nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena); +} + +static void +sp_canvas_arena_destroy (GtkObject *object) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (object); + + if (arena->active) { + nr_object_unref ((NRObject *) arena->active); + arena->active = NULL; + } + + if (arena->root) { + nr_arena_item_unref (arena->root); + arena->root = NULL; + } + + if (arena->arena) { + nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena); + + nr_object_unref ((NRObject *) arena->arena); + arena->arena = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + if (((SPCanvasItemClass *) parent_class)->update) + (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags); + + arena->gc.transform = affine; + + guint reset; + reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE; + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset); + + item->x1 = arena->root->bbox.x0 - 1; + item->y1 = arena->root->bbox.y0 - 1; + item->x2 = arena->root->bbox.x1 + 1; + item->y2 = arena->root->bbox.y1 + 1; + + if (arena->cursor) { + /* Mess with enter/leave notifiers */ + NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (new_arena != arena->active) { + GdkEventCrossing ec; + ec.window = GTK_WIDGET (item->canvas)->window; + ec.send_event = TRUE; + ec.subwindow = ec.window; + ec.time = GDK_CURRENT_TIME; + ec.x = arena->c[NR::X]; + ec.y = arena->c[NR::Y]; + /* fixme: */ + if (arena->active) { + ec.type = GDK_LEAVE_NOTIFY; + sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + /* fixme: This is not optimal - better track ::destroy (Lauris) */ + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = new_arena; + if (arena->active) nr_object_ref ((NRObject *) arena->active); + if (arena->active) { + ec.type = GDK_ENTER_NOTIFY; + sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + } + } +} + +#ifdef arena_item_tile_cache +extern void age_cache(void); +#endif + +static void +sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + gint bw, bh, sw, sh; + gint x, y; + + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, + NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER, + NR_ARENA_ITEM_STATE_NONE); + + sp_canvas_prepare_buffer(buf); + +#ifdef arena_item_tile_cache + age_cache(); +#endif + bw = buf->rect.x1 - buf->rect.x0; + bh = buf->rect.y1 - buf->rect.y0; + if ((bw < 1) || (bh < 1)) return; + + if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients + /* 256K is the cached buffer and we need 4 channels */ + if (bw * bh < 65536) { // 256K/4 + /* We can go with single buffer */ + sw = bw; + sh = bh; + } else if (bw <= 4096) { + /* Go with row buffer */ + sw = bw; + sh = 65536 / bw; + } else if (bh <= 4096) { + /* Go with column buffer */ + sw = 65536 / bh; + sh = bh; + } else { + sw = 256; + sh = 256; + } + } else { // paths only, so 1M works faster + /* 1M is the cached buffer and we need 4 channels */ + if (bw * bh < 262144) { // 1M/4 + /* We can go with single buffer */ + sw = bw; + sh = bh; + } else if (bw <= 8192) { + /* Go with row buffer */ + sw = bw; + sh = 262144 / bw; + } else if (bh <= 8192) { + /* Go with column buffer */ + sw = 262144 / bh; + sh = bh; + } else { + sw = 512; + sh = 512; + } + } + +/* fixme: RGB transformed bitmap blit is not implemented (Lauris) */ +/* And even if it would be, unless it uses MMX there is little reason to go RGB */ +#define STRICT_RGBA + + for (y = buf->rect.y0; y < buf->rect.y1; y += sh) { + for (x = buf->rect.x0; x < buf->rect.x1; x += sw) { + NRRectL area; +#ifdef STRICT_RGBA + NRPixBlock pb; +#endif + NRPixBlock cb; + + area.x0 = x; + area.y0 = y; + area.x1 = MIN (x + sw, buf->rect.x1); + area.y1 = MIN (y + sh, buf->rect.y1); + +#ifdef STRICT_RGBA + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE); + /* fixme: */ + pb.empty = FALSE; +#endif + + nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1, + buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0), + buf->buf_rowstride, + FALSE, FALSE); + +#ifdef STRICT_RGBA + nr_arena_item_invoke_render (arena->root, &area, &pb, 0); + nr_blit_pixblock_pixblock (&cb, &pb); + nr_pixblock_release (&pb); +#else + nr_arena_item_invoke_render (arena->root, &area, &cb, 0); +#endif + + nr_pixblock_release (&cb); + } + } +} + +static double +sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, + NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK, + NR_ARENA_ITEM_STATE_NONE); + + NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky); + + arena->picked = picked; + + if (picked) { + *actual_item = item; + return 0.0; + } + + return 1e18; +} + +static gint +sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event) +{ + NRArenaItem *new_arena; + /* fixme: This sucks, we have to handle enter/leave notifiers */ + + SPCanvasArena *arena = SP_CANVAS_ARENA (item); + + gint ret = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + if (!arena->cursor) { + if (arena->active) { + //g_warning ("Cursor entered to arena with already active item"); + nr_object_unref ((NRObject *) arena->active); + } + arena->cursor = TRUE; + + /* TODO ... event -> arena transform? */ + arena->c = NR::Point(event->crossing.x, event->crossing.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE); + arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (arena->active) nr_object_ref ((NRObject *) arena->active); + ret = sp_canvas_arena_send_event (arena, event); + } + break; + case GDK_LEAVE_NOTIFY: + if (arena->cursor) { + ret = sp_canvas_arena_send_event (arena, event); + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = NULL; + arena->cursor = FALSE; + } + break; + case GDK_MOTION_NOTIFY: + /* TODO ... event -> arena transform? */ + arena->c = NR::Point(event->motion.x, event->motion.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE); + new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky); + if (new_arena != arena->active) { + GdkEventCrossing ec; + ec.window = event->motion.window; + ec.send_event = event->motion.send_event; + ec.subwindow = event->motion.window; + ec.time = event->motion.time; + ec.x = event->motion.x; + ec.y = event->motion.y; + /* fixme: */ + if (arena->active) { + ec.type = GDK_LEAVE_NOTIFY; + ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + if (arena->active) nr_object_unref ((NRObject *) arena->active); + arena->active = new_arena; + if (arena->active) nr_object_ref ((NRObject *) arena->active); + if (arena->active) { + ec.type = GDK_ENTER_NOTIFY; + ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec); + } + } + ret = sp_canvas_arena_send_event (arena, event); + break; + default: + /* Just send event */ + ret = sp_canvas_arena_send_event (arena, event); + break; + } + + return ret; +} + +static gint +sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event) +{ + gint ret = FALSE; + + /* Send event to arena */ + gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret); + + return ret; +} + +static void +sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data) +{ + sp_canvas_item_request_update (SP_CANVAS_ITEM (data)); +} + +static void +sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data) +{ + sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1); +} + +void +sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta) +{ + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: repick? */ + ca->delta = delta; +} + +void +sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky) +{ + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: repick? */ + ca->sticky = sticky; +} + +void +sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb) +{ + NRRectL area; + + g_return_if_fail (ca != NULL); + g_return_if_fail (SP_IS_CANVAS_ARENA (ca)); + + /* fixme: */ + pb->empty = FALSE; + + area.x0 = pb->area.x0; + area.y0 = pb->area.y0; + area.x1 = pb->area.x1; + area.y1 = pb->area.y1; + + nr_arena_item_invoke_render (ca->root, &area, pb, 0); +} + diff --git a/src/display/canvas-arena.h b/src/display/canvas-arena.h new file mode 100644 index 000000000..805bee60a --- /dev/null +++ b/src/display/canvas-arena.h @@ -0,0 +1,58 @@ +#ifndef __SP_CANVAS_ARENA_H__ +#define __SP_CANVAS_ARENA_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +struct SPCanvasArena; +struct SPCanvasArenaClass; + +#define SP_TYPE_CANVAS_ARENA (sp_canvas_arena_get_type ()) +#define SP_CANVAS_ARENA(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_ARENA, SPCanvasArena)) +#define SP_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_ARENA, SPCanvasArenaClass)) +#define SP_IS_CANVAS_ARENA(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_ARENA)) +#define SP_IS_CANVAS_ARENA_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_ARENA)) + +#include "../display/sp-canvas.h" +#include "nr-arena-item.h" + +struct SPCanvasArena { + SPCanvasItem item; + + guint cursor : 1; + guint sticky : 1; + NR::Point c; // what is this? + + NRArena *arena; + NRArenaItem *root; + NRGC gc; + + NRArenaItem *active; + /* fixme: */ + NRArenaItem *picked; + gdouble delta; +}; + +struct SPCanvasArenaClass { + SPCanvasItemClass parent_class; + + gint (* arena_event) (SPCanvasArena *carena, NRArenaItem *item, GdkEvent *event); +}; + +GtkType sp_canvas_arena_get_type (void); + +void sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta); +void sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky); + +void sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb); + +#endif diff --git a/src/display/canvas-bpath.cpp b/src/display/canvas-bpath.cpp new file mode 100644 index 000000000..a6434ae3d --- /dev/null +++ b/src/display/canvas-bpath.cpp @@ -0,0 +1,485 @@ +#define __SP_CANVAS_BPATH_C__ + +/* + * Simple bezier bpath CanvasItem for inkscape + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sp-canvas-util.h" +#include "canvas-bpath.h" +#include "display/display-forward.h" +#include "display/curve.h" +#include +#include +#include +#include +#include + +void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride); + +static void sp_canvas_bpath_class_init (SPCanvasBPathClass *klass); +static void sp_canvas_bpath_init (SPCanvasBPath *path); +static void sp_canvas_bpath_destroy (GtkObject *object); + +static void sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf); +static double sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_canvas_bpath_get_type (void) +{ + static GtkType type = 0; + if (!type) { + GtkTypeInfo info = { + "SPCanvasBPath", + sizeof (SPCanvasBPath), + sizeof (SPCanvasBPathClass), + (GtkClassInitFunc) sp_canvas_bpath_class_init, + (GtkObjectInitFunc) sp_canvas_bpath_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_canvas_bpath_class_init (SPCanvasBPathClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = GTK_OBJECT_CLASS (klass); + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_canvas_bpath_destroy; + + item_class->update = sp_canvas_bpath_update; + item_class->render = sp_canvas_bpath_render; + item_class->point = sp_canvas_bpath_point; +} + +static void +sp_canvas_bpath_init (SPCanvasBPath * bpath) +{ + bpath->fill_rgba = 0x000000ff; + bpath->fill_rule = SP_WIND_RULE_EVENODD; + + bpath->stroke_rgba = 0x00000000; + bpath->stroke_width = 1.0; + bpath->stroke_linejoin = SP_STROKE_LINEJOIN_MITER; + bpath->stroke_linecap = SP_STROKE_LINECAP_BUTT; + bpath->stroke_miterlimit = 11.0; + + bpath->fill_shp=NULL; + bpath->stroke_shp=NULL; +} + +static void +sp_canvas_bpath_destroy (GtkObject *object) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (object); + if (cbp->fill_shp) { + delete cbp->fill_shp; + cbp->fill_shp = NULL; + } + + if (cbp->stroke_shp) { + delete cbp->stroke_shp; + cbp->stroke_shp = NULL; + } + if (cbp->curve) { + cbp->curve = sp_curve_unref (cbp->curve); + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_bpath_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (((SPCanvasItemClass *) parent_class)->update) + ((SPCanvasItemClass *) parent_class)->update (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + if (cbp->fill_shp) { + delete cbp->fill_shp; + cbp->fill_shp = NULL; + } + + if (cbp->stroke_shp) { + delete cbp->stroke_shp; + cbp->stroke_shp = NULL; + } + if (!cbp->curve) return; + + NRRect dbox; + + dbox.x0 = dbox.y0 = 0.0; + dbox.x1 = dbox.y1 = -1.0; + + if ((cbp->fill_rgba & 0xff) || (cbp->stroke_rgba & 0xff)) { + Path* thePath=new Path; + thePath->LoadArtBPath(cbp->curve->bpath, affine, true); + thePath->Convert(0.25); + if ((cbp->fill_rgba & 0xff) && (cbp->curve->end > 2)) { + Shape* theShape=new Shape; + thePath->Fill(theShape,0); + if ( cbp->fill_shp == NULL ) cbp->fill_shp=new Shape; + if ( cbp->fill_rule == SP_WIND_RULE_EVENODD ) { + cbp->fill_shp->ConvertToShape(theShape,fill_oddEven); + } else { + cbp->fill_shp->ConvertToShape(theShape,fill_nonZero); + } + delete theShape; + cbp->fill_shp->CalcBBox(); + if ( cbp->fill_shp->leftX < cbp->fill_shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0 = cbp->fill_shp->leftX; dbox.x1 = cbp->fill_shp->rightX; + dbox.y0 = cbp->fill_shp->topY; dbox.y1 = cbp->fill_shp->bottomY; + } else { + if ( cbp->fill_shp->leftX < dbox.x0 ) dbox.x0=cbp->fill_shp->leftX; + if ( cbp->fill_shp->rightX > dbox.x1 ) dbox.x1=cbp->fill_shp->rightX; + if ( cbp->fill_shp->topY < dbox.y0 ) dbox.y0=cbp->fill_shp->topY; + if ( cbp->fill_shp->bottomY > dbox.y1 ) dbox.y1=cbp->fill_shp->bottomY; + } + } + } + if ((cbp->stroke_rgba & 0xff) && (cbp->curve->end > 1)) { + JoinType join=join_straight; +// Shape* theShape=new Shape; + ButtType butt=butt_straight; + if ( cbp->stroke_shp == NULL ) cbp->stroke_shp=new Shape; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_BUTT ) butt=butt_straight; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_ROUND ) butt=butt_round; + if ( cbp->stroke_linecap == SP_STROKE_LINECAP_SQUARE ) butt=butt_square; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_MITER ) join=join_pointy; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_ROUND ) join=join_round; + if ( cbp->stroke_linejoin == SP_STROKE_LINEJOIN_BEVEL ) join=join_straight; + thePath->Stroke(cbp->stroke_shp,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit ); + // thePath->Stroke(theShape,false,0.5*cbp->stroke_width, join,butt,cbp->stroke_width*cbp->stroke_miterlimit ); + // cbp->stroke_shp->ConvertToShape(theShape,fill_nonZero); + + cbp->stroke_shp->CalcBBox(); + if ( cbp->stroke_shp->leftX < cbp->stroke_shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0 = cbp->stroke_shp->leftX;dbox.x1 = cbp->stroke_shp->rightX; + dbox.y0 = cbp->stroke_shp->topY;dbox.y1 = cbp->stroke_shp->bottomY; + } else { + if ( cbp->stroke_shp->leftX < dbox.x0 ) dbox.x0 = cbp->stroke_shp->leftX; + if ( cbp->stroke_shp->rightX > dbox.x1 ) dbox.x1 = cbp->stroke_shp->rightX; + if ( cbp->stroke_shp->topY < dbox.y0 ) dbox.y0 = cbp->stroke_shp->topY; + if ( cbp->stroke_shp->bottomY > dbox.y1 ) dbox.y1 = cbp->stroke_shp->bottomY; + } + } +// delete theShape; + } + delete thePath; + } + + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +static void +sp_canvas_bpath_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + sp_canvas_prepare_buffer(buf); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + if ( cbp->fill_shp ) { + nr_pixblock_render_bpath_rgba (cbp->fill_shp,cbp->fill_rgba,area,(char*)buf->buf, buf->buf_rowstride); + } + if ( cbp->stroke_shp ) { + nr_pixblock_render_bpath_rgba (cbp->stroke_shp,cbp->stroke_rgba,area,(char*)buf->buf, buf->buf_rowstride); + } + +} + +#define BIGVAL 1e18 + +static double +sp_canvas_bpath_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCanvasBPath *cbp = SP_CANVAS_BPATH (item); + + if (cbp->fill_shp && (cbp->fill_shp->PtWinding(p) > 0 )) { + *actual_item = item; + return 0.0; + } + if (cbp->stroke_shp ) { + if (cbp->stroke_shp->PtWinding(p) > 0 ) { + *actual_item = item; + return 0.0; + } + return distance(cbp->stroke_shp, p); + } + if (cbp->fill_shp) { + return distance(cbp->fill_shp, p); + } + + return BIGVAL; +} + +SPCanvasItem * +sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve) +{ + g_return_val_if_fail (parent != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL); + + SPCanvasItem *item = sp_canvas_item_new (parent, SP_TYPE_CANVAS_BPATH, NULL); + + sp_canvas_bpath_set_bpath (SP_CANVAS_BPATH (item), curve); + + return item; +} + +void +sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + if (cbp->curve) { + cbp->curve = sp_curve_unref (cbp->curve); + } + + if (curve) { + cbp->curve = sp_curve_ref (curve); + } + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + +void +sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + cbp->fill_rgba = rgba; + cbp->fill_rule = rule; + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + +void +sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap) +{ + g_return_if_fail (cbp != NULL); + g_return_if_fail (SP_IS_CANVAS_BPATH (cbp)); + + cbp->stroke_rgba = rgba; + cbp->stroke_width = MAX (width, 0.1); + cbp->stroke_linejoin = join; + cbp->stroke_linecap = cap; + + sp_canvas_item_request_update (SP_CANVAS_ITEM (cbp)); +} + + + +static void +bpath_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven) +{ + union { + uint8_t comp[4]; + uint32_t col; + } tempCol; + if ( st >= en ) return; + tempCol.col=*(uint32_t*)data; + + unsigned int r, g, b, a; + r = NR_RGBA32_R (tempCol.col); + g = NR_RGBA32_G (tempCol.col); + b = NR_RGBA32_B (tempCol.col); + a = NR_RGBA32_A (tempCol.col); + if (a == 0) return; + + vst*=a; + ven*=a; + + if ( vst < 0 ) vst=0; + if ( vst > 255 ) vst=255; + if ( ven < 0 ) ven=0; + if ( ven > 255 ) ven=255; + float sv=vst; + float dv=ven-vst; + int len=en-st; + uint8_t* d=(uint8_t*)dest.buffer; + + d+=3*(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( sv > 249.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = NR_COMPOSEN11 (r, 255, d[0]); + d[1] = NR_COMPOSEN11 (g, 255, d[1]); + d[2] = NR_COMPOSEN11 (b, 255, d[2]); + d += 3; + len -= 1; + } + } else { + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + d[0] = NR_COMPOSEN11 (r, c0_24, d[0]); + d[1] = NR_COMPOSEN11 (g, c0_24, d[1]); + d[2] = NR_COMPOSEN11 (b, c0_24, d[2]); + d += 3; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + d[0] = NR_COMPOSEN11 (r, c0_24, d[0]); + d[1] = NR_COMPOSEN11 (g, c0_24, d[1]); + d[2] = NR_COMPOSEN11 (b, c0_24, d[2]); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=65536; + dv*=65536; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + d[0] = NR_COMPOSEN11 (r, ca, d[0]); + d[1] = NR_COMPOSEN11 (g, ca, d[1]); + d[2] = NR_COMPOSEN11 (b, ca, d[2]); + d += 3; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_bpath_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + + if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return; + if ( il < area.x0 ) il=area.x0; + if ( it < area.y0 ) it=area.y0; + if ( ir > area.x1 ) ir=area.x1; + if ( ib > area.y1 ) ib=area.y1; + +/* // version par FloatLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),1.0); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0); + } else { + theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0); + } + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndRaster(); + delete theI; + delete theIL; */ + + // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + + for (int i = 0; i < 4; i++) + theS->QuickScan(curY, curPt, ((float)(y+0.25*(i+1))), + fill_oddEven, theI[i], 0.25); + + theIL->Copy(4,theI); + // theI[0]->Affiche(); + // theIL->Affiche(); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL; +} diff --git a/src/display/canvas-bpath.h b/src/display/canvas-bpath.h new file mode 100644 index 000000000..072fe1087 --- /dev/null +++ b/src/display/canvas-bpath.h @@ -0,0 +1,99 @@ +#ifndef __SP_CANVAS_BPATH_H__ +#define __SP_CANVAS_BPATH_H__ + +/* + * Simple bezier bpath CanvasItem for inkscape + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include + +#include + +struct SPCanvasBPath; +struct SPCanvasBPathClass; +struct SPCurve; + +#define SP_TYPE_CANVAS_BPATH (sp_canvas_bpath_get_type ()) +#define SP_CANVAS_BPATH(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CANVAS_BPATH, SPCanvasBPath)) +#define SP_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CANVAS_BPATH, SPCanvasBPathClass)) +#define SP_IS_CANVAS_BPATH(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CANVAS_BPATH)) +#define SP_IS_CANVAS_BPATH_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CANVAS_BPATH)) + +#define bpath_liv + +class Shape; + +/* stroke-linejoin */ + +typedef enum { + SP_STROKE_LINEJOIN_MITER, + SP_STROKE_LINEJOIN_ROUND, + SP_STROKE_LINEJOIN_BEVEL +} SPStrokeJoinType; + +/* stroke-linecap */ + +typedef enum { + SP_STROKE_LINECAP_BUTT, + SP_STROKE_LINECAP_ROUND, + SP_STROKE_LINECAP_SQUARE +} SPStrokeCapType; + + +/* fill-rule */ +/* clip-rule */ + +typedef enum { + SP_WIND_RULE_NONZERO, + SP_WIND_RULE_INTERSECT, + SP_WIND_RULE_EVENODD, + SP_WIND_RULE_POSITIVE +} SPWindRule; + + +struct SPCanvasBPath { + SPCanvasItem item; + + /* Line def */ + SPCurve *curve; + + /* Fill attributes */ + guint32 fill_rgba; + SPWindRule fill_rule; + + /* Line attributes */ + guint32 stroke_rgba; + gdouble stroke_width; + SPStrokeJoinType stroke_linejoin; + SPStrokeCapType stroke_linecap; + gdouble stroke_miterlimit; + + /* State */ + Shape *fill_shp; + Shape *stroke_shp; +}; + +struct SPCanvasBPathClass { + SPCanvasItemClass parent_class; +}; + +GtkType sp_canvas_bpath_get_type (void); + +SPCanvasItem *sp_canvas_bpath_new (SPCanvasGroup *parent, SPCurve *curve); + +void sp_canvas_bpath_set_bpath (SPCanvasBPath *cbp, SPCurve *curve); +void sp_canvas_bpath_set_fill (SPCanvasBPath *cbp, guint32 rgba, SPWindRule rule); +void sp_canvas_bpath_set_stroke (SPCanvasBPath *cbp, guint32 rgba, gdouble width, SPStrokeJoinType join, SPStrokeCapType cap); + + + +#endif + diff --git a/src/display/canvas-grid.cpp b/src/display/canvas-grid.cpp new file mode 100644 index 000000000..6618c2358 --- /dev/null +++ b/src/display/canvas-grid.cpp @@ -0,0 +1,308 @@ +#define SP_CANVAS_GRID_C + +/* + * SPCGrid + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + + +#include "sp-canvas-util.h" +#include "canvas-grid.h" +#include "display-forward.h" +#include + +enum { + ARG_0, + ARG_ORIGINX, + ARG_ORIGINY, + ARG_SPACINGX, + ARG_SPACINGY, + ARG_COLOR, + ARG_EMPCOLOR, + ARG_EMPSPACING +}; + + +static void sp_cgrid_class_init (SPCGridClass *klass); +static void sp_cgrid_init (SPCGrid *grid); +static void sp_cgrid_destroy (GtkObject *object); +static void sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); + +static void sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_cgrid_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass * parent_class; + +GtkType +sp_cgrid_get_type (void) +{ + static GtkType cgrid_type = 0; + + if (!cgrid_type) { + GtkTypeInfo cgrid_info = { + "SPCGrid", + sizeof (SPCGrid), + sizeof (SPCGridClass), + (GtkClassInitFunc) sp_cgrid_class_init, + (GtkObjectInitFunc) sp_cgrid_init, + NULL, NULL, + (GtkClassInitFunc) NULL + }; + cgrid_type = gtk_type_unique (sp_canvas_item_get_type (), &cgrid_info); + } + return cgrid_type; +} + +static void +sp_cgrid_class_init (SPCGridClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + gtk_object_add_arg_type ("SPCGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX); + gtk_object_add_arg_type ("SPCGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY); + gtk_object_add_arg_type ("SPCGrid::spacingx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGX); + gtk_object_add_arg_type ("SPCGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY); + gtk_object_add_arg_type ("SPCGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR); + gtk_object_add_arg_type ("SPCGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR); + gtk_object_add_arg_type ("SPCGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING); + + object_class->destroy = sp_cgrid_destroy; + object_class->set_arg = sp_cgrid_set_arg; + + item_class->update = sp_cgrid_update; + item_class->render = sp_cgrid_render; +} + +static void +sp_cgrid_init (SPCGrid *grid) +{ + grid->origin[NR::X] = grid->origin[NR::Y] = 0.0; + grid->spacing[NR::X] = grid->spacing[NR::Y] = 8.0; + grid->color = 0x0000ff7f; + grid->empcolor = 0x3F3FFF40; + grid->empspacing = 5; +} + +static void +sp_cgrid_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CGRID (object)); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_cgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + SPCanvasItem *item = SP_CANVAS_ITEM (object); + SPCGrid *grid = SP_CGRID (object); + + switch (arg_id) { + case ARG_ORIGINX: + grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_ORIGINY: + grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_SPACINGX: + grid->spacing[NR::X] = GTK_VALUE_DOUBLE (* arg); + if (grid->spacing[NR::X] < 0.01) grid->spacing[NR::X] = 0.01; + sp_canvas_item_request_update (item); + break; + case ARG_SPACINGY: + grid->spacing[NR::Y] = GTK_VALUE_DOUBLE (* arg); + if (grid->spacing[NR::Y] < 0.01) grid->spacing[NR::Y] = 0.01; + sp_canvas_item_request_update (item); + break; + case ARG_COLOR: + grid->color = GTK_VALUE_INT (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_EMPCOLOR: + grid->empcolor = GTK_VALUE_INT (* arg); + sp_canvas_item_request_update (item); + break; + case ARG_EMPSPACING: + grid->empspacing = GTK_VALUE_INT (* arg); + // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl; + sp_canvas_item_request_update (item); + break; + default: + break; + } +} + +static void +sp_grid_hline (SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba) +{ + if ((y >= buf->rect.y0) && (y < buf->rect.y1)) { + guint r, g, b, a; + gint x0, x1, x; + guchar *p; + r = NR_RGBA32_R (rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + x0 = MAX (buf->rect.x0, xs); + x1 = MIN (buf->rect.x1, xe + 1); + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (x = x0; x < x1; x++) { + p[0] = NR_COMPOSEN11 (r, a, p[0]); + p[1] = NR_COMPOSEN11 (g, a, p[1]); + p[2] = NR_COMPOSEN11 (b, a, p[2]); + p += 3; + } + } +} + +static void +sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba) +{ + if ((x >= buf->rect.x0) && (x < buf->rect.x1)) { + guint r, g, b, a; + gint y0, y1, y; + guchar *p; + r = NR_RGBA32_R(rgba); + g = NR_RGBA32_G (rgba); + b = NR_RGBA32_B (rgba); + a = NR_RGBA32_A (rgba); + y0 = MAX (buf->rect.y0, ys); + y1 = MIN (buf->rect.y1, ye + 1); + p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3; + for (y = y0; y < y1; y++) { + p[0] = NR_COMPOSEN11 (r, a, p[0]); + p[1] = NR_COMPOSEN11 (g, a, p[1]); + p[2] = NR_COMPOSEN11 (b, a, p[2]); + p += buf->buf_rowstride; + } + } +} + +/** + \brief This function renders the grid on a particular canvas buffer + \param item The grid to render on the buffer + \param buf The buffer to render the grid on + + This function gets called a touch more than you might believe, + about once per tile. This means that it could probably be optimized + and help things out. + + Basically this function has to determine where in the canvas it is, + and how that associates with the grid. It does this first by looking + at the bounding box of the buffer, and then calculates where the grid + starts in that buffer. It will then step through grid lines until + it is outside of the buffer. + + For each grid line it is drawn using the function \c sp_grid_hline + or \c sp_grid_vline. These are convience functions for the sake + of making the function easier to read. + + Also, there are emphisized lines on the grid. While the \c syg and + \c sxg variable track grid positioning, the \c xlinestart and \c + ylinestart variables track the 'count' of what lines they are. If + that count is a multiple of the line seperation between emphisis + lines, then that line is drawn in the emphisis color. +*/ +static void +sp_cgrid_render (SPCanvasItem * item, SPCanvasBuf * buf) +{ + SPCGrid *grid = SP_CGRID (item); + + sp_canvas_prepare_buffer (buf); + + const gdouble sxg = floor ((buf->rect.x0 - grid->ow[NR::X]) / grid->sw[NR::X]) * grid->sw[NR::X] + grid->ow[NR::X]; + const gint xlinestart = (gint) Inkscape::round((sxg - grid->ow[NR::X]) / grid->sw[NR::X]); + const gdouble syg = floor ((buf->rect.y0 - grid->ow[NR::Y]) / grid->sw[NR::Y]) * grid->sw[NR::Y] + grid->ow[NR::Y]; + const gint ylinestart = (gint) Inkscape::round((syg - grid->ow[NR::Y]) / grid->sw[NR::Y]); + + gint ylinenum; + gdouble y; + for (y = syg, ylinenum = ylinestart; y < buf->rect.y1; y += grid->sw[NR::Y], ylinenum++) { + const gint y0 = (gint) Inkscape::round(y); + const gint y1 = (gint) Inkscape::round(y + grid->sw[NR::Y]); + + if (!grid->scaled[NR::Y] && + (ylinenum % grid->empspacing) == 0) { + sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->empcolor); + } else { + sp_grid_hline (buf, y0, buf->rect.x0, buf->rect.x1 - 1, grid->color); + } + + gint xlinenum; + gdouble x; + for (x = sxg, xlinenum = xlinestart; x < buf->rect.x1; x += grid->sw[NR::X], xlinenum++) { + const gint ix = (gint) Inkscape::round(x); + if (!grid->scaled[NR::X] && + (xlinenum % grid->empspacing) == 0) { + sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->empcolor); + } else { + sp_grid_vline (buf, ix, y0 + 1, y1 - 1, grid->color); + } + } + } +} + +static void +sp_cgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCGrid *grid = SP_CGRID (item); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + grid->ow = grid->origin * affine; + grid->sw = grid->spacing * affine; + grid->sw -= NR::Point(affine[4], affine[5]); + + for(int dim = 0; dim < 2; dim++) { + gint scaling_factor = grid->empspacing; + + if (scaling_factor <= 1) + scaling_factor = 5; + + grid->scaled[dim] = FALSE; + grid->sw[dim] = fabs (grid->sw[dim]); + while (grid->sw[dim] < 8.0) { + grid->scaled[dim] = TRUE; + grid->sw[dim] *= scaling_factor; + /* First pass, go up to the major line spacing, then + keep increasing by two. */ + scaling_factor = 2; + } + } + + if (grid->empspacing == 0) { + grid->scaled[NR::Y] = TRUE; + grid->scaled[NR::X] = TRUE; + } + + sp_canvas_request_redraw (item->canvas, + -1000000, -1000000, + 1000000, 1000000); + + item->x1 = item->y1 = -1000000; + item->x2 = item->y2 = 1000000; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/canvas-grid.h b/src/display/canvas-grid.h new file mode 100644 index 000000000..0f3791773 --- /dev/null +++ b/src/display/canvas-grid.h @@ -0,0 +1,49 @@ +#ifndef SP_CANVAS_GRID_H +#define SP_CANVAS_GRID_H + +/* + * SPCGrid + * + * Generic (and quite unintelligent) grid item for gnome canvas + * + * Copyright (C) Lauris Kaplinski 2000 + * + */ + +#include + + + +#define SP_TYPE_CGRID (sp_cgrid_get_type ()) +#define SP_CGRID(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CGRID, SPCGrid)) +#define SP_CGRID_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CGRID, SPCGridClass)) +#define SP_IS_CGRID(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CGRID)) +#define SP_IS_CGRID_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CGRID)) + + +/** \brief All the variables that are tracked for a grid specific + canvas item. */ +struct SPCGrid : public SPCanvasItem{ + NR::Point origin; /**< Origin of the grid */ + NR::Point spacing; /**< Spacing between elements of the grid */ + guint32 color; /**< Color for normal lines */ + guint32 empcolor; /**< Color for emphisis lines */ + gint empspacing; /**< Spacing between emphisis lines */ + bool scaled[2]; /**< Whether the grid is in scaled mode, which can + be different in the X or Y direction, hense two + variables */ + NR::Point ow; /**< Transformed origin by the affine for the zoom */ + NR::Point sw; /**< Transformed spacing by the affine for the zoom */ +}; + +struct SPCGridClass { + SPCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GtkType sp_cgrid_get_type (void); + + + +#endif diff --git a/src/display/curve.cpp b/src/display/curve.cpp new file mode 100644 index 000000000..a8a6e354e --- /dev/null +++ b/src/display/curve.cpp @@ -0,0 +1,1289 @@ +#define __CURVE_C__ + +/** \file + * Routines for SPCurve and for NArtBpath arrays in general. + */ + +/* + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + + +#include +#include +#include +#include + +#define SP_CURVE_LENSTEP 32 + +static bool sp_bpath_good(NArtBpath const bpath[]); +static NArtBpath *sp_bpath_clean(NArtBpath const bpath[]); +static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[]); +static unsigned sp_bpath_length(NArtBpath const bpath[]); +static bool sp_bpath_closed(NArtBpath const bpath[]); + +/* Constructors */ + +/** + * The returned curve's state is as if sp_curve_reset has just been called on it. + */ +SPCurve * +sp_curve_new() +{ + return sp_curve_new_sized(SP_CURVE_LENSTEP); +} + +/** + * Like sp_curve_new, but overriding the default initial capacity. + * + * The returned curve's state is as if sp_curve_reset has just been called on it. + * + * \param length Initial number of NArtBpath elements allocated for bpath (including NR_END + * element). + */ +SPCurve * +sp_curve_new_sized(gint length) +{ + g_return_val_if_fail(length > 0, NULL); + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = nr_new(NArtBpath, length); + curve->bpath->code = NR_END; + curve->end = 0; + curve->length = length; + curve->substart = 0; + curve->sbpath = false; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = false; + + return curve; +} + +/** + * Convert NArtBpath object to SPCurve object. + * + * \return new SPCurve, or NULL if the curve was not created for some reason. + */ +SPCurve * +sp_curve_new_from_bpath(NArtBpath *bpath) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + if (!sp_bpath_good(bpath)) { + NArtBpath *new_bpath = sp_bpath_clean(bpath); + if (new_bpath == NULL) { + return NULL; + } + nr_free(bpath); + bpath = new_bpath; + } + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = bpath; + curve->length = sp_bpath_length(bpath); + curve->end = curve->length - 1; + gint i = curve->end; + for (; i > 0; i--) + if ((curve->bpath[i].code == NR_MOVETO) || + (curve->bpath[i].code == NR_MOVETO_OPEN)) + break; + curve->substart = i; + curve->sbpath = false; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = sp_bpath_closed(bpath); + + return curve; +} + +/** + * Construct an SPCurve from read-only, static storage. + * + * We could treat read-onliness and staticness (i.e. can't call free on bpath) as orthogonal + * attributes, but at the time of writing we have only one caller. + */ +SPCurve * +sp_curve_new_from_static_bpath(NArtBpath const *bpath) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + bool sbpath; + if (!sp_bpath_good(bpath)) { + NArtBpath *new_bpath = sp_bpath_clean(bpath); + g_return_val_if_fail(new_bpath != NULL, NULL); + sbpath = false; + bpath = new_bpath; + } else { + sbpath = true; + } + + SPCurve *curve = g_new(SPCurve, 1); + + curve->refcount = 1; + curve->bpath = const_cast(bpath); + curve->length = sp_bpath_length(bpath); + curve->end = curve->length - 1; + gint i = curve->end; + for (; i > 0; i--) + if ((curve->bpath[i].code == NR_MOVETO) || + (curve->bpath[i].code == NR_MOVETO_OPEN)) + break; + curve->substart = i; + curve->sbpath = sbpath; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = sp_bpath_closed(bpath); + + return curve; +} + +/** + * Convert const NArtBpath array to SPCurve. + * + * \return new SPCurve, or NULL if the curve was not created for some reason. + */ +SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + NArtBpath *new_bpath; + if (!sp_bpath_good(bpath)) { + new_bpath = sp_bpath_clean(bpath); + g_return_val_if_fail(new_bpath != NULL, NULL); + } else { + unsigned const len = sp_bpath_length(bpath); + new_bpath = nr_new(NArtBpath, len); + memcpy(new_bpath, bpath, len * sizeof(NArtBpath)); + } + + SPCurve *curve = sp_curve_new_from_bpath(new_bpath); + + if (!curve) + nr_free(new_bpath); + + return curve; +} + +/** + * Increase refcount of curve. + * + * \todo should this be shared with other refcounting code? + */ +SPCurve * +sp_curve_ref(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + curve->refcount += 1; + + return curve; +} + +/** + * Decrease refcount of curve, with possible destruction. + * + * \todo should this be shared with other refcounting code? + */ +SPCurve * +sp_curve_unref(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + curve->refcount -= 1; + + if (curve->refcount < 1) { + if ((!curve->sbpath) && (curve->bpath)) { + nr_free(curve->bpath); + } + g_free(curve); + } + + return NULL; +} + +/** + * Add space for more paths in curve. + */ +static void +sp_curve_ensure_space(SPCurve *curve, gint space) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(space > 0); + + if (curve->end + space < curve->length) + return; + + if (space < SP_CURVE_LENSTEP) + space = SP_CURVE_LENSTEP; + + curve->bpath = nr_renew(curve->bpath, NArtBpath, curve->length + space); + + curve->length += space; +} + +/** + * Create new curve from its own bpath array. + */ +SPCurve * +sp_curve_copy(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + return sp_curve_new_from_foreign_bpath(curve->bpath); +} + +/** + * Return new curve that is the concatenation of all curves in list. + */ +SPCurve * +sp_curve_concat(GSList const *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + gint length = 0; + + for (GSList const *l = list; l != NULL; l = l->next) { + SPCurve *c = (SPCurve *) l->data; + length += c->end; + } + + SPCurve *new_curve = sp_curve_new_sized(length + 1); + + NArtBpath *bp = new_curve->bpath; + + for (GSList const *l = list; l != NULL; l = l->next) { + SPCurve *c = (SPCurve *) l->data; + memcpy(bp, c->bpath, c->end * sizeof(NArtBpath)); + bp += c->end; + } + + bp->code = NR_END; + + new_curve->end = length; + gint i; + for (i = new_curve->end; i > 0; i--) { + if ((new_curve->bpath[i].code == NR_MOVETO) || + (new_curve->bpath[i].code == NR_MOVETO_OPEN) ) + break; + } + + new_curve->substart = i; + + return new_curve; +} + +/** + * Returns a list of new curves corresponding to the subpaths in \a curve. + */ +GSList * +sp_curve_split(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + gint p = 0; + GSList *l = NULL; + + while (p < curve->end) { + gint i = 1; + while ((curve->bpath[p + i].code == NR_LINETO) || + (curve->bpath[p + i].code == NR_CURVETO)) + i++; + SPCurve *new_curve = sp_curve_new_sized(i + 1); + memcpy(new_curve->bpath, curve->bpath + p, i * sizeof(NArtBpath)); + new_curve->end = i; + new_curve->bpath[i].code = NR_END; + new_curve->substart = 0; + new_curve->closed = (new_curve->bpath->code == NR_MOVETO); + new_curve->hascpt = (new_curve->bpath->code == NR_MOVETO_OPEN); + l = g_slist_append(l, new_curve); + /** \todo + * effic: Use g_slist_prepend instead. Either work backwards from + * the end of curve, or work forwards as at present but do + * g_slist_reverse before returning. + */ + p += i; + } + + return l; +} + +/** + * Transform all paths in curve, template helper. + */ +template +static void +tmpl_curve_transform(SPCurve *const curve, M const &m) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + + for (gint i = 0; i < curve->end; i++) { + NArtBpath *p = curve->bpath + i; + switch (p->code) { + case NR_MOVETO: + case NR_MOVETO_OPEN: + case NR_LINETO: { + p->setC(3, p->c(3) * m); + break; + } + case NR_CURVETO: + for (unsigned i = 1; i <= 3; ++i) { + p->setC(i, p->c(i) * m); + } + break; + default: + g_warning("Illegal pathcode %d", p->code); + break; + } + } +} + +/** + * Transform all paths in curve using matrix. + */ +void +sp_curve_transform(SPCurve *const curve, NR::Matrix const &m) +{ + tmpl_curve_transform(curve, m); +} + +/** + * Transform all paths in curve using NR::translate. + */ +void +sp_curve_transform(SPCurve *const curve, NR::translate const &m) +{ + tmpl_curve_transform(curve, m); +} + + +/* Methods */ + +/** + * Set curve to empty curve. + */ +void +sp_curve_reset(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + + curve->bpath->code = NR_END; + curve->end = 0; + curve->substart = 0; + curve->hascpt = false; + curve->posSet = false; + curve->moving = false; + curve->closed = false; +} + +/* Several consecutive movetos are ALLOWED */ + +/** + * Calls sp_curve_moveto() with point made of given coordinates. + */ +void +sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y) +{ + sp_curve_moveto(curve, NR::Point(x, y)); +} + +/** + * Perform a moveto to a point, thus starting a new subpath. + */ +void +sp_curve_moveto(SPCurve *curve, NR::Point const &p) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(!curve->moving); + + curve->substart = curve->end; + curve->hascpt = true; + curve->posSet = true; + curve->movePos = p; +} + +/** + * Calls sp_curve_lineto() with a point's coordinates. + */ +void +sp_curve_lineto(SPCurve *curve, NR::Point const &p) +{ + sp_curve_lineto(curve, p[NR::X], p[NR::Y]); +} + +/** + * Adds a line to the current subpath. + */ +void +sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + + if (curve->moving) { + /* fix endpoint */ + g_return_if_fail(!curve->posSet); + g_return_if_fail(curve->end > 1); + NArtBpath *bp = curve->bpath + curve->end - 1; + g_return_if_fail(bp->code == NR_LINETO); + bp->x3 = x; + bp->y3 = y; + curve->moving = false; + return; + } + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->closed = false; + return; + } + + /* add line */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end++; +} + +/// Unused +void +sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + + if (curve->moving) { + /* change endpoint */ + g_return_if_fail(!curve->posSet); + g_return_if_fail(curve->end > 1); + NArtBpath *bp = curve->bpath + curve->end - 1; + g_return_if_fail(bp->code == NR_LINETO); + bp->x3 = x; + bp->y3 = y; + return; + } + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->moving = true; + curve->closed = false; + return; + } + + /* add line */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = NR_END; + curve->end++; + curve->moving = true; +} + +/** + * Calls sp_curve_curveto() with coordinates of three points. + */ +void +sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2) +{ + using NR::X; + using NR::Y; + sp_curve_curveto(curve, + p0[X], p0[Y], + p1[X], p1[Y], + p2[X], p2[Y]); +} + +/** + * Adds a bezier segment to the current subpath. + */ +void +sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->moving); + + if (curve->posSet) { + /* start a new segment */ + sp_curve_ensure_space(curve, 2); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_MOVETO_OPEN; + bp->setC(3, curve->movePos); + bp++; + bp->code = NR_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = NR_END; + curve->end += 2; + curve->posSet = false; + curve->closed = false; + return; + } + + /* add curve */ + + g_return_if_fail(curve->end > 1); + sp_curve_ensure_space(curve, 1); + NArtBpath *bp = curve->bpath + curve->end; + bp->code = NR_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = NR_END; + curve->end++; +} + +/** + * Close current subpath by possibly adding a line between start and end. + */ +void +sp_curve_closepath(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->posSet); + g_return_if_fail(!curve->moving); + g_return_if_fail(!curve->closed); + /* We need at least moveto, curveto, end. */ + g_return_if_fail(curve->end - curve->substart > 1); + + { + NArtBpath *bs = curve->bpath + curve->substart; + NArtBpath *be = curve->bpath + curve->end - 1; + + if (bs->c(3) != be->c(3)) { + sp_curve_lineto(curve, bs->c(3)); + bs = curve->bpath + curve->substart; + } + + bs->code = NR_MOVETO; + } + curve->closed = true; + + for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) { + /** \todo + * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of + * the closed boolean). + */ + if (bp->code == NR_MOVETO_OPEN) { + curve->closed = false; + break; + } + } + + curve->hascpt = false; +} + +/** Like sp_curve_closepath() but sets the end point of the current + command to the subpath start point instead of adding a new lineto. + + Used for freehand drawing when the user draws back to the start point. +**/ +void +sp_curve_closepath_current(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(!curve->sbpath); + g_return_if_fail(curve->hascpt); + g_return_if_fail(!curve->posSet); + g_return_if_fail(!curve->closed); + /* We need at least moveto, curveto, end. */ + g_return_if_fail(curve->end - curve->substart > 1); + + { + NArtBpath *bs = curve->bpath + curve->substart; + NArtBpath *be = curve->bpath + curve->end - 1; + + be->x3 = bs->x3; + be->y3 = bs->y3; + + bs->code = NR_MOVETO; + } + curve->closed = true; + + for (NArtBpath const *bp = curve->bpath; bp->code != NR_END; bp++) { + /** \todo + * effic: Maintain a count of NR_MOVETO_OPEN's (e.g. instead of + * the closed boolean). + */ + if (bp->code == NR_MOVETO_OPEN) { + curve->closed = false; + break; + } + } + + curve->hascpt = false; + curve->moving = false; +} + +/** + * True if no paths are in curve. + */ +bool +sp_curve_empty(SPCurve *curve) +{ + g_return_val_if_fail(curve != NULL, TRUE); + + return (curve->bpath->code == NR_END); +} + +/** + * Return last subpath or NULL. + */ +NArtBpath * +sp_curve_last_bpath(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + if (curve->end == 0) { + return NULL; + } + + return curve->bpath + curve->end - 1; +} + +/** + * Return first subpath or NULL. + */ +NArtBpath * +sp_curve_first_bpath(SPCurve const *curve) +{ + g_return_val_if_fail(curve != NULL, NULL); + + if (curve->end == 0) { + return NULL; + } + + return curve->bpath; +} + +/** + * Return first point of first subpath or (0,0). + */ +NR::Point +sp_curve_first_point(SPCurve const *const curve) +{ + NArtBpath *const bpath = sp_curve_first_bpath(curve); + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return the second point of first subpath or curve->movePos if curve too short. + */ +NR::Point +sp_curve_second_point(SPCurve const *const curve) +{ + g_return_val_if_fail(curve != NULL, NR::Point(0, 0)); + + if (curve->end < 1) { + return curve->movePos; + } + + NArtBpath *bpath = NULL; + if (curve->end < 2) { + bpath = curve->bpath; + } else { + bpath = curve->bpath + 1; + } + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return the second-last point of last subpath or curve->movePos if curve too short. + */ +NR::Point +sp_curve_penultimate_point(SPCurve const *const curve) +{ + g_return_val_if_fail(curve != NULL, NR::Point(0, 0)); + + if (curve->end < 2) { + return curve->movePos; + } + + NArtBpath *const bpath = curve->bpath + curve->end - 2; + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +/** + * Return last point of last subpath or (0,0). + */ +NR::Point +sp_curve_last_point(SPCurve const *const curve) +{ + NArtBpath *const bpath = sp_curve_last_bpath(curve); + g_return_val_if_fail(bpath != NULL, NR::Point(0, 0)); + return bpath->c(3); +} + +inline static bool +is_moveto(NRPathcode const c) +{ + return c == NR_MOVETO || c == NR_MOVETO_OPEN; +} + +/** + * Returns \a curve but drawn in the opposite direction. + * Should result in the same shape, but + * with all its markers drawn facing the other direction. + **/ +SPCurve * +sp_curve_reverse(SPCurve const *curve) +{ + /* We need at least moveto, curveto, end. */ + g_return_val_if_fail(curve->end - curve->substart > 1, NULL); + + NArtBpath const *be = curve->bpath + curve->end - 1; + + g_assert(is_moveto(curve->bpath[curve->substart].code)); + g_assert(is_moveto(curve->bpath[0].code)); + g_assert((be+1)->code == NR_END); + + SPCurve *new_curve = sp_curve_new_sized(curve->length); + sp_curve_moveto(new_curve, be->c(3)); + + for (NArtBpath const *bp = be; ; --bp) { + switch (bp->code) { + case NR_MOVETO: + g_assert(new_curve->bpath[new_curve->substart].code == NR_MOVETO_OPEN); + new_curve->bpath[new_curve->substart].code = NR_MOVETO; + /* FALL-THROUGH */ + case NR_MOVETO_OPEN: + if (bp == curve->bpath) { + return new_curve; + } + sp_curve_moveto(new_curve, (bp-1)->c(3)); + break; + + case NR_LINETO: + sp_curve_lineto(new_curve, (bp-1)->c(3)); + break; + + case NR_CURVETO: + sp_curve_curveto(new_curve, bp->c(2), bp->c(1), (bp-1)->c(3)); + break; + + default: + g_assert_not_reached(); + } + } +} + +/** + * Append \a curve2 to \a curve. + */ +void +sp_curve_append(SPCurve *curve, + SPCurve const *curve2, + bool use_lineto) +{ + g_return_if_fail(curve != NULL); + g_return_if_fail(curve2 != NULL); + + if (curve2->end < 1) + return; + + NArtBpath const *bs = curve2->bpath; + + bool closed = curve->closed; + + for (NArtBpath const *bp = bs; bp->code != NR_END; bp++) { + switch (bp->code) { + case NR_MOVETO_OPEN: + if (use_lineto && curve->hascpt) { + sp_curve_lineto(curve, bp->x3, bp->y3); + use_lineto = FALSE; + } else { + if (closed) sp_curve_closepath(curve); + sp_curve_moveto(curve, bp->x3, bp->y3); + } + closed = false; + break; + + case NR_MOVETO: + if (use_lineto && curve->hascpt) { + sp_curve_lineto(curve, bp->x3, bp->y3); + use_lineto = FALSE; + } else { + if (closed) sp_curve_closepath(curve); + sp_curve_moveto(curve, bp->x3, bp->y3); + } + closed = true; + break; + + case NR_LINETO: + sp_curve_lineto(curve, bp->x3, bp->y3); + break; + + case NR_CURVETO: + sp_curve_curveto(curve, bp->x1, bp->y1, bp->x2, bp->y2, bp->x3, bp->y3); + break; + + case NR_END: + g_assert_not_reached(); + } + } + + if (closed) { + sp_curve_closepath(curve); + } +} + +/** + * Append \a c1 to \a c0 with possible fusing of close endpoints. + */ +SPCurve * +sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance) +{ + g_return_val_if_fail(c0 != NULL, NULL); + g_return_val_if_fail(c1 != NULL, NULL); + g_return_val_if_fail(!c0->closed, NULL); + g_return_val_if_fail(!c1->closed, NULL); + + if (c1->end < 1) { + return c0; + } + + NArtBpath *be = sp_curve_last_bpath(c0); + if (be) { + NArtBpath const *bs = sp_curve_first_bpath(c1); + if ( bs + && ( fabs( bs->x3 - be->x3 ) <= tolerance ) + && ( fabs( bs->y3 - be->y3 ) <= tolerance ) ) + { + /** \todo + * fixme: Strictly we mess in case of multisegment mixed + * open/close curves + */ + bool closed = false; + for (bs = bs + 1; bs->code != NR_END; bs++) { + switch (bs->code) { + case NR_MOVETO_OPEN: + if (closed) sp_curve_closepath(c0); + sp_curve_moveto(c0, bs->x3, bs->y3); + closed = false; + break; + case NR_MOVETO: + if (closed) sp_curve_closepath(c0); + sp_curve_moveto(c0, bs->x3, bs->y3); + closed = true; + break; + case NR_LINETO: + sp_curve_lineto(c0, bs->x3, bs->y3); + break; + case NR_CURVETO: + sp_curve_curveto(c0, bs->x1, bs->y1, bs->x2, bs->y2, bs->x3, bs->y3); + break; + case NR_END: + g_assert_not_reached(); + } + } + } else { + sp_curve_append(c0, c1, TRUE); + } + } else { + sp_curve_append(c0, c1, TRUE); + } + + return c0; +} + +/** + * Remove last segment of curve. + */ +void +sp_curve_backspace(SPCurve *curve) +{ + g_return_if_fail(curve != NULL); + + if (curve->end > 0) { + curve->end -= 1; + if (curve->end > 0) { + NArtBpath *bp = curve->bpath + curve->end - 1; + if ((bp->code == NR_MOVETO) || + (bp->code == NR_MOVETO_OPEN) ) + { + curve->hascpt = true; + curve->posSet = true; + curve->closed = false; + curve->movePos = bp->c(3); + curve->end -= 1; + } + } + curve->bpath[curve->end].code = NR_END; + } +} + +/* Private methods */ + +/** + * True if all subpaths in bpath array pass consistency check. + */ +static bool sp_bpath_good(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + NArtBpath const *bp = bpath; + while (bp->code != NR_END) { + bp = sp_bpath_check_subpath(bp); + if (bp == NULL) + return false; + } + + return true; +} + +/** + * Return copy of a bpath array, discarding any inconsistencies. + */ +static NArtBpath *sp_bpath_clean(NArtBpath const bpath[]) +{ + NArtBpath *new_bpath = nr_new(NArtBpath, sp_bpath_length(bpath)); + + NArtBpath const *bp = bpath; + NArtBpath *np = new_bpath; + + while (bp->code != NR_END) { + if (sp_bpath_check_subpath(bp)) { + *np++ = *bp++; + while ((bp->code == NR_LINETO) || + (bp->code == NR_CURVETO)) + *np++ = *bp++; + } else { + bp++; + while ((bp->code == NR_LINETO) || + (bp->code == NR_CURVETO)) + bp++; + } + } + + if (np == new_bpath) { + nr_free(new_bpath); + return NULL; + } + + np->code = NR_END; + np += 1; + + new_bpath = nr_renew(new_bpath, NArtBpath, np - new_bpath); + + return new_bpath; +} + +/** + * Perform consistency check of bpath array. + * \return Address of NR_END node or NULL. + */ +static NArtBpath const *sp_bpath_check_subpath(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, NULL); + + bool closed; + if (bpath->code == NR_MOVETO) { + closed = true; + } else if (bpath->code == NR_MOVETO_OPEN) { + closed = false; + } else { + return NULL; + } + + gint len = 0; + gint i; + /** \todo + * effic: consider checking for END/MOVE/MOVETO inside switch block + */ + for (i = 1; (bpath[i].code != NR_END) && (bpath[i].code != NR_MOVETO) && (bpath[i].code != NR_MOVETO_OPEN); i++) { + switch (bpath[i].code) { + case NR_LINETO: + case NR_CURVETO: + len++; + break; + default: + return NULL; + } + } + + if (closed) { + if (len < 1) + return NULL; + + if ((bpath->x3 != bpath[i-1].x3) || (bpath->y3 != bpath[i-1].y3)) + return NULL; + } else { + if (len < 1) + return NULL; + } + + return bpath + i; +} + +/** + * Returns index of first NR_END bpath in array. + */ +static unsigned sp_bpath_length(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + unsigned ret = 0; + while ( bpath[ret].code != NR_END ) { + ++ret; + } + ++ret; + + return ret; +} + +/** + * \brief + * + * \todo + * fixme: this is bogus -- it doesn't check for nr_moveto, which will indicate + * a closing of the subpath it's nonsense to talk about a path as a whole + * being closed, although maybe someone would want that for some other reason? + * Oh, also, if the bpath just ends, then it's *open*. I hope nobody is using + * this code for anything. + */ +static bool sp_bpath_closed(NArtBpath const bpath[]) +{ + g_return_val_if_fail(bpath != NULL, FALSE); + + for (NArtBpath const *bp = bpath; bp->code != NR_END; bp++) { + if (bp->code == NR_MOVETO_OPEN) { + return false; + } + } + + return true; +} + +/** + * Returns length of bezier segment. + */ +static double +bezier_len(NR::Point const &c0, + NR::Point const &c1, + NR::Point const &c2, + NR::Point const &c3, + double const threshold) +{ + /** \todo + * The SVG spec claims that a closed form exists, but for the moment I'll + * use a stupid algorithm. + */ + double const lbound = L2( c3 - c0 ); + double const ubound = L2( c1 - c0 ) + L2( c2 - c1 ) + L2( c3 - c2 ); + double ret; + if ( ubound - lbound <= threshold ) { + ret = .5 * ( lbound + ubound ); + } else { + NR::Point const a1( .5 * ( c0 + c1 ) ); + NR::Point const b2( .5 * ( c2 + c3 ) ); + NR::Point const c12( .5 * ( c1 + c2 ) ); + NR::Point const a2( .5 * ( a1 + c12 ) ); + NR::Point const b1( .5 * ( c12 + b2 ) ); + NR::Point const midpoint( .5 * ( a2 + b1 ) ); + double const rec_threshold = .625 * threshold; + ret = bezier_len(c0, a1, a2, midpoint, rec_threshold) + bezier_len(midpoint, b1, b2, c3, rec_threshold); + if (!(lbound - 1e-2 <= ret && ret <= ubound + 1e-2)) { + using NR::X; using NR::Y; + g_warning("ret=%f outside of expected bounds [%f, %f] for {(%.0f %.0f) (%.0f %.0f) (%.0f %.0f) (%.0f %.0f)}", + ret, lbound, ubound, c0[X], c0[Y], c1[X], c1[Y], c2[X], c2[Y], c3[X], c3[Y]); + } + } + return ret; +} + +/** + * Returns total length of curve, excluding length of closepath segments. + */ +static double +sp_curve_distance_including_space(SPCurve const *const curve, double seg2len[]) +{ + g_return_val_if_fail(curve != NULL, 0.); + + double ret = 0.0; + + if ( curve->bpath->code == NR_END ) { + return ret; + } + + NR::Point prev(curve->bpath->c(3)); + for (gint i = 1; i < curve->end; ++i) { + NArtBpath &p = curve->bpath[i]; + double seg_len = 0; + switch (p.code) { + case NR_MOVETO_OPEN: + case NR_MOVETO: + case NR_LINETO: + seg_len = L2(p.c(3) - prev); + break; + + case NR_CURVETO: + seg_len = bezier_len(prev, p.c(1), p.c(2), p.c(3), 1.); + break; + + case NR_END: + return ret; + } + seg2len[i - 1] = seg_len; + ret += seg_len; + prev = p.c(3); + } + g_assert(!(ret < 0)); + return ret; +} + +/** + * Like sp_curve_distance_including_space(), but ensures that the + * result >= 1e-18: uses 1 per segment if necessary. + */ +static double +sp_curve_nonzero_distance_including_space(SPCurve const *const curve, double seg2len[]) +{ + double const real_dist(sp_curve_distance_including_space(curve, seg2len)); + if (real_dist >= 1e-18) { + return real_dist; + } else { + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + for (unsigned i = 0; i < nSegs; ++i) { + seg2len[i] = 1.; + } + return (double) nSegs; + } +} + +void +sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &new_p0, NR::Point const &new_p1) +{ + if (sp_curve_empty(curve)) { + return; + } + g_assert(unsigned(SP_CURVE_LENGTH(curve)) + 1 == sp_bpath_length(curve->bpath)); + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + g_assert(nSegs != 0); + double *const seg2len = new double[nSegs]; + double const tot_len = sp_curve_nonzero_distance_including_space(curve, seg2len); + NR::Point const offset0( new_p0 - sp_curve_first_point(curve) ); + NR::Point const offset1( new_p1 - sp_curve_last_point(curve) ); + curve->bpath->setC(3, new_p0); + double begin_dist = 0.; + for (unsigned si = 0; si < nSegs; ++si) { + double const end_dist = begin_dist + seg2len[si]; + NArtBpath &p = curve->bpath[1 + si]; + switch (p.code) { + case NR_LINETO: + case NR_MOVETO: + case NR_MOVETO_OPEN: + p.setC(3, p.c(3) + NR::Lerp(end_dist / tot_len, offset0, offset1)); + break; + + case NR_CURVETO: + for (unsigned ci = 1; ci <= 3; ++ci) { + p.setC(ci, p.c(ci) + Lerp((begin_dist + ci * seg2len[si] / 3.) / tot_len, offset0, offset1)); + } + break; + + default: + g_assert_not_reached(); + } + + begin_dist = end_dist; + } + g_assert(L1(curve->bpath[nSegs].c(3) - new_p1) < 1.); + /* Explicit set for better numerical properties. */ + curve->bpath[nSegs].setC(3, new_p1); + delete [] seg2len; +} + +void +sp_curve_move_endpoints(SPCurve *curve, NR::Point const &new_p0, + NR::Point const &new_p1) +{ + if (sp_curve_empty(curve)) { + return; + } + unsigned const nSegs = SP_CURVE_LENGTH(curve) - 1; + g_assert(nSegs != 0); + + curve->bpath->setC(3, new_p0); + curve->bpath[nSegs].setC(3, new_p1); +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/curve.h b/src/display/curve.h new file mode 100644 index 000000000..d63796140 --- /dev/null +++ b/src/display/curve.h @@ -0,0 +1,133 @@ +#ifndef SEEN_DISPLAY_CURVE_H +#define SEEN_DISPLAY_CURVE_H + +/** \file + * Wrapper around an array of NArtBpath objects. + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include +#include + +#include "libnr/nr-forward.h" +#include "libnr/nr-point.h" + +/// Wrapper around NArtBpath. +struct SPCurve { + gint refcount; + NArtBpath *bpath; + + /// Index in bpath[] of NR_END element. + gint end; + + /// Allocated size (i.e., capacity) of bpath[] array. Not to be confused + /// with the SP_CURVE_LENGTH macro, which returns the logical length of + /// the path (i.e., index of NR_END). + gint length; + + /// Index in bpath[] of the start (i.e., moveto element) of the last + /// subpath in this path. + gint substart; + + /// Previous moveto position. + /// \note This is used for coalescing moveto's, whereas if we're to + /// conform to the SVG spec then we mustn't coalesce movetos if we have + /// midpoint markers. Ref: + /// http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + /// (first subitem of the item about zero-length path segments) + NR::Point movePos; + + /// True iff bpath points to read-only, static storage (see callers of + /// sp_curve_new_from_static_bpath), in which case we shouldn't free + /// bpath and shouldn't write through it. + bool sbpath : 1; + + /// True iff current point is defined. Initially false for a new curve; + /// becomes true after moveto; becomes false on closepath. Curveto, + /// lineto etc. require hascpt; hascpt remains true after lineto/curveto. + bool hascpt : 1; + + /// True iff previous was moveto. + bool posSet : 1; + + /// True iff bpath end is moving. + bool moving : 1; + + /// True iff all subpaths are closed. + bool closed : 1; +}; + +#define SP_CURVE_LENGTH(c) (((SPCurve const *)(c))->end) +#define SP_CURVE_BPATH(c) (((SPCurve const *)(c))->bpath) +#define SP_CURVE_SEGMENT(c,i) (((SPCurve const *)(c))->bpath + (i)) + +/* Constructors */ + +SPCurve *sp_curve_new(); +SPCurve *sp_curve_new_sized(gint length); +SPCurve *sp_curve_new_from_bpath(NArtBpath *bpath); +SPCurve *sp_curve_new_from_static_bpath(NArtBpath const *bpath); +SPCurve *sp_curve_new_from_foreign_bpath(NArtBpath const bpath[]); + +SPCurve *sp_curve_ref(SPCurve *curve); +SPCurve *sp_curve_unref(SPCurve *curve); + +SPCurve *sp_curve_copy(SPCurve *curve); +SPCurve *sp_curve_concat(GSList const *list); +GSList *sp_curve_split(SPCurve const *curve); +void sp_curve_transform(SPCurve *curve, NR::Matrix const &); +void sp_curve_transform(SPCurve *curve, NR::translate const &); +void sp_curve_stretch_endpoints(SPCurve *curve, NR::Point const &, NR::Point const &); +void sp_curve_move_endpoints(SPCurve *curve, NR::Point const &, + NR::Point const &); + +/* Methods */ + +void sp_curve_reset(SPCurve *curve); + +void sp_curve_moveto(SPCurve *curve, NR::Point const &p); +void sp_curve_moveto(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_lineto(SPCurve *curve, NR::Point const &p); +void sp_curve_lineto(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_lineto_moving(SPCurve *curve, gdouble x, gdouble y); +void sp_curve_curveto(SPCurve *curve, NR::Point const &p0, NR::Point const &p1, NR::Point const &p2); +void sp_curve_curveto(SPCurve *curve, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2); +void sp_curve_closepath(SPCurve *curve); +void sp_curve_closepath_current(SPCurve *curve); + +SPCurve *sp_curve_append_continuous(SPCurve *c0, SPCurve const *c1, gdouble tolerance); + +#define sp_curve_is_empty sp_curve_empty +bool sp_curve_empty(SPCurve *curve); +NArtBpath *sp_curve_last_bpath(SPCurve const *curve); +NArtBpath *sp_curve_first_bpath(SPCurve const *curve); +NR::Point sp_curve_first_point(SPCurve const *curve); +NR::Point sp_curve_last_point(SPCurve const *curve); +NR::Point sp_curve_second_point(SPCurve const *curve); +NR::Point sp_curve_penultimate_point(SPCurve const *curve); + +void sp_curve_append(SPCurve *curve, SPCurve const *curve2, bool use_lineto); +SPCurve *sp_curve_reverse(SPCurve const *curve); +void sp_curve_backspace(SPCurve *curve); + + +#endif /* !SEEN_DISPLAY_CURVE_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/display-forward.h b/src/display/display-forward.h new file mode 100644 index 000000000..2af6f2c44 --- /dev/null +++ b/src/display/display-forward.h @@ -0,0 +1,46 @@ +#ifndef SEEN_DISPLAY_DISPLAY_FORWARD_H +#define SEEN_DISPLAY_DISPLAY_FORWARD_H + +#include + +struct SPCanvas; +struct SPCanvasClass; +struct SPCanvasItem; +struct SPCanvasItemClass; +struct SPCanvasGroup; +struct SPCanvasGroupClass; +struct SPCurve; + + +#define SP_TYPE_CANVAS_ITEM (sp_canvas_item_get_type()) +#define SP_CANVAS_ITEM(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_ITEM, SPCanvasItem)) +#define SP_IS_CANVAS_ITEM(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_ITEM)) +#define SP_CANVAS_ITEM_GET_CLASS(o) (GTK_CHECK_GET_CLASS((o), SP_TYPE_CANVAS_ITEM, SPCanvasItemClass)) + +GType sp_canvas_item_get_type(); + +#define SP_TYPE_CANVAS_GROUP (sp_canvas_group_get_type()) +#define SP_CANVAS_GROUP(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS_GROUP, SPCanvasGroup)) +#define SP_IS_CANVAS_GROUP(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS_GROUP)) + +GType sp_canvas_group_get_type(); + +#define SP_TYPE_CANVAS (sp_canvas_get_type()) +#define SP_CANVAS(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CANVAS, SPCanvas)) +#define SP_IS_CANVAS(obj) (GTK_CHECK_TYPE((obj), SP_TYPE_CANVAS)) + +GType sp_canvas_get_type(); + + +#endif /* !SEEN_DISPLAY_DISPLAY_FORWARD_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/gnome-canvas-acetate.cpp b/src/display/gnome-canvas-acetate.cpp new file mode 100644 index 000000000..1a58c4b19 --- /dev/null +++ b/src/display/gnome-canvas-acetate.cpp @@ -0,0 +1,100 @@ +#define __SP_CANVAS_ACETATE_C__ + +/* + * Infinite invisible canvas item + * + * Author: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998-1999 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display-forward.h" +#include "gnome-canvas-acetate.h" + +static void sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass); +static void sp_canvas_acetate_init (SPCanvasAcetate *acetate); +static void sp_canvas_acetate_destroy (GtkObject *object); + +static void sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static double sp_canvas_acetate_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_canvas_acetate_get_type (void) +{ + static GtkType acetate_type = 0; + if (!acetate_type) { + GtkTypeInfo acetate_info = { + "SPCanvasAcetate", + sizeof (SPCanvasAcetate), + sizeof (SPCanvasAcetateClass), + (GtkClassInitFunc) sp_canvas_acetate_class_init, + (GtkObjectInitFunc) sp_canvas_acetate_init, + NULL, NULL, NULL + }; + acetate_type = gtk_type_unique (sp_canvas_item_get_type (), &acetate_info); + } + return acetate_type; +} + +static void +sp_canvas_acetate_class_init (SPCanvasAcetateClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + object_class->destroy = sp_canvas_acetate_destroy; + + item_class->update = sp_canvas_acetate_update; + item_class->point = sp_canvas_acetate_point; +} + +static void +sp_canvas_acetate_init (SPCanvasAcetate *acetate) +{ + /* Nothing here */ +} + +static void +sp_canvas_acetate_destroy (GtkObject *object) +{ + SPCanvasAcetate *acetate; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_ACETATE (object)); + + acetate = SP_CANVAS_ACETATE (object); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_canvas_acetate_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + item->x1 = -G_MAXINT; + item->y1 = -G_MAXINT; + item->x2 = G_MAXINT; + item->y2 = G_MAXINT; +} + +static double +sp_canvas_acetate_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + *actual_item = item; + + return 0.0; +} + diff --git a/src/display/gnome-canvas-acetate.h b/src/display/gnome-canvas-acetate.h new file mode 100644 index 000000000..40574e1bf --- /dev/null +++ b/src/display/gnome-canvas-acetate.h @@ -0,0 +1,41 @@ +#ifndef __SP_CANVAS_ACETATE_H__ +#define __SP_CANVAS_ACETATE_H__ + +/* + * Infinite invisible canvas item + * + * Author: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998-1999 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "display/sp-canvas.h" + + +#define GNOME_TYPE_CANVAS_ACETATE (sp_canvas_acetate_get_type ()) +#define SP_CANVAS_ACETATE(obj) (GTK_CHECK_CAST ((obj), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetate)) +#define SP_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ACETATE, SPCanvasAcetateClass)) +#define GNOME_IS_CANVAS_ACETATE(obj) (GTK_CHECK_TYPE ((obj), GNOME_TYPE_CANVAS_ACETATE)) +#define GNOME_IS_CANVAS_ACETATE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ACETATE)) + + +struct SPCanvasAcetate { + SPCanvasItem item; +}; + +struct SPCanvasAcetateClass { + SPCanvasItemClass parent_class; +}; + +GtkType sp_canvas_acetate_get_type (void); + + + +#endif diff --git a/src/display/guideline.cpp b/src/display/guideline.cpp new file mode 100644 index 000000000..d44ac8ab8 --- /dev/null +++ b/src/display/guideline.cpp @@ -0,0 +1,198 @@ +#define __SP_GUIDELINE_C__ + +/* + * Infinite horizontal/vertical line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "guideline.h" + +static void sp_guideline_class_init(SPGuideLineClass *c); +static void sp_guideline_init(SPGuideLine *guideline); +static void sp_guideline_destroy(GtkObject *object); + +static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf); + +static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + +static SPCanvasItemClass *parent_class; + +GType sp_guideline_get_type() +{ + static GType guideline_type = 0; + + if (!guideline_type) { + static const GTypeInfo guideline_info = + { + sizeof (SPGuideLineClass), + NULL, NULL, + (GClassInitFunc) sp_guideline_class_init, + NULL, NULL, + sizeof (SPGuideLine), + 16, + (GInstanceInitFunc) sp_guideline_init, + NULL, + }; + + guideline_type = g_type_register_static(SP_TYPE_CANVAS_ITEM, "SPGuideLine", &guideline_info, (GTypeFlags) 0); + } + + return guideline_type; +} + +static void sp_guideline_class_init(SPGuideLineClass *c) +{ + parent_class = (SPCanvasItemClass*) g_type_class_peek_parent(c); + + GtkObjectClass *object_class = (GtkObjectClass *) c; + object_class->destroy = sp_guideline_destroy; + + SPCanvasItemClass *item_class = (SPCanvasItemClass *) c; + item_class->update = sp_guideline_update; + item_class->render = sp_guideline_render; + item_class->point = sp_guideline_point; +} + +static void sp_guideline_init(SPGuideLine *gl) +{ + gl->rgba = 0x0000ff7f; + + gl->vertical = 0; + gl->sensitive = 0; +} + +static void sp_guideline_destroy(GtkObject *object) +{ + GTK_OBJECT_CLASS(parent_class)->destroy(object); +} + +static void sp_guideline_render(SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPGuideLine const *gl = SP_GUIDELINE (item); + + sp_canvas_prepare_buffer(buf); + + unsigned int const r = NR_RGBA32_R (gl->rgba); + unsigned int const g = NR_RGBA32_G (gl->rgba); + unsigned int const b = NR_RGBA32_B (gl->rgba); + unsigned int const a = NR_RGBA32_A (gl->rgba); + + int p0, p1, step; + unsigned char *d; + + if (gl->vertical) { + + if (gl->position < buf->rect.x0 || gl->position >= buf->rect.x1) { + return; + } + + p0 = buf->rect.y0; + p1 = buf->rect.y1; + step = buf->buf_rowstride; + d = buf->buf + 3 * (gl->position - buf->rect.x0); + + } else { + + if (gl->position < buf->rect.y0 || gl->position >= buf->rect.y1) { + return; + } + + p0 = buf->rect.x0; + p1 = buf->rect.x1; + step = 3; + d = buf->buf + (gl->position - buf->rect.y0) * buf->buf_rowstride; + } + + for (int p = p0; p < p1; p++) { + d[0] = NR_COMPOSEN11(r, a, d[0]); + d[1] = NR_COMPOSEN11(g, a, d[1]); + d[2] = NR_COMPOSEN11(b, a, d[2]); + d += step; + } +} + +static void sp_guideline_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPGuideLine *gl = SP_GUIDELINE(item); + + if (((SPCanvasItemClass *) parent_class)->update) { + ((SPCanvasItemClass *) parent_class)->update(item, affine, flags); + } + + if (gl->vertical) { + gl->position = (int) (affine[4] + 0.5); + sp_canvas_update_bbox (item, gl->position, -1000000, gl->position + 1, 1000000); + } else { + gl->position = (int) (affine[5] - 0.5); + sp_canvas_update_bbox (item, -1000000, gl->position, 1000000, gl->position + 1); + } +} + +static double sp_guideline_point(SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPGuideLine *gl = SP_GUIDELINE (item); + + if (!gl->sensitive) { + return NR_HUGE; + } + + *actual_item = item; + + if (gl->vertical) { + return MAX(fabs(gl->position - p[NR::X])-1, 0); + } else { + return MAX(fabs(gl->position - p[NR::Y])-1, 0); + } +} + +SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical) +{ + SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_GUIDELINE, NULL); + + SPGuideLine *gl = SP_GUIDELINE(item); + + gl->vertical = vertical; + sp_guideline_set_position(gl, position); + + return item; +} + +void sp_guideline_set_position(SPGuideLine *gl, double position) +{ + sp_canvas_item_affine_absolute(SP_CANVAS_ITEM (gl), + NR::Matrix(NR::translate(position, position))); +} + +void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba) +{ + gl->rgba = rgba; + + sp_canvas_item_request_update(SP_CANVAS_ITEM(gl)); +} + +void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive) +{ + gl->sensitive = sensitive; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/guideline.h b/src/display/guideline.h new file mode 100644 index 000000000..22f0af69a --- /dev/null +++ b/src/display/guideline.h @@ -0,0 +1,55 @@ +#ifndef __SP_GUIDELINE_H__ +#define __SP_GUIDELINE_H__ + +/* + * Infinite horizontal/vertical line; the visual representation of SPGuide. + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-canvas.h" + +#define SP_TYPE_GUIDELINE (sp_guideline_get_type()) +#define SP_GUIDELINE(o) (GTK_CHECK_CAST((o), SP_TYPE_GUIDELINE, SPGuideLine)) +#define SP_IS_GUIDELINE(o) (GTK_CHECK_TYPE((o), SP_TYPE_GUIDELINE)) + +struct SPGuideLine { + SPCanvasItem item; + + guint32 rgba; + + int position; + + unsigned int vertical : 1; + unsigned int sensitive : 1; +}; + +struct SPGuideLineClass { + SPCanvasItemClass parent_class; +}; + +GType sp_guideline_get_type(); + +SPCanvasItem *sp_guideline_new(SPCanvasGroup *parent, double position, unsigned int vertical); + +void sp_guideline_set_position(SPGuideLine *gl, double position); +void sp_guideline_set_color(SPGuideLine *gl, unsigned int rgba); +void sp_guideline_set_sensitive(SPGuideLine *gl, int sensitive); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/makefile.in b/src/display/makefile.in new file mode 100644 index 000000000..9d9426809 --- /dev/null +++ b/src/display/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) display/all + +clean %.a %.o: + cd .. && $(MAKE) display/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/display/nr-arena-forward.h b/src/display/nr-arena-forward.h new file mode 100644 index 000000000..67f62a78b --- /dev/null +++ b/src/display/nr-arena-forward.h @@ -0,0 +1,51 @@ +#ifndef __NR_ARENA_FORWARD_H__ +#define __NR_ARENA_FORWARD_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +struct NRArena; +struct NRArenaClass; + +struct NRArenaItem; +struct NRArenaItemClass; + +struct NRArenaGroup; +struct NRArenaGroupClass; + +struct NRArenaShape; +struct NRArenaShapeClass; + +struct NRArenaShapeGroup; +struct NRArenaShapeGroupClass; + +struct NRArenaImage; +struct NRArenaImageClass; + +struct NRArenaGlyphs; +struct NRArenaGlyphsClass; + +struct NRArenaGlyphsGroup; +struct NRArenaGlyphsGroupClass; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-arena-glyphs.cpp b/src/display/nr-arena-glyphs.cpp new file mode 100644 index 000000000..861b8baf8 --- /dev/null +++ b/src/display/nr-arena-glyphs.cpp @@ -0,0 +1,679 @@ +#define __NR_ARENA_GLYPHS_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + * + */ + + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include "../style.h" +#include "nr-arena.h" +#include "nr-arena-glyphs.h" + +#ifdef test_glyph_liv +#include "../display/canvas-bpath.h" +#include +#include +#include + +// defined in nr-arena-shape.cpp +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS); +#endif + +static void nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass); +static void nr_arena_glyphs_init (NRArenaGlyphs *glyphs); +static void nr_arena_glyphs_finalize (NRObject *object); + +static guint nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static guint nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *glyphs_parent_class; + +NRType +nr_arena_glyphs_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaGlyphs", + sizeof (NRArenaGlyphsClass), + sizeof (NRArenaGlyphs), + (void (*) (NRObjectClass *)) nr_arena_glyphs_class_init, + (void (*) (NRObject *)) nr_arena_glyphs_init); + } + return type; +} + +static void +nr_arena_glyphs_class_init (NRArenaGlyphsClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + glyphs_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_glyphs_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_glyphs_update; + item_class->clip = nr_arena_glyphs_clip; + item_class->pick = nr_arena_glyphs_pick; +} + +static void +nr_arena_glyphs_init (NRArenaGlyphs *glyphs) +{ + glyphs->style = NULL; + nr_matrix_set_identity(&glyphs->g_transform); + glyphs->font = NULL; + glyphs->glyph = 0; + + glyphs->rfont = NULL; + glyphs->sfont = NULL; + glyphs->x = glyphs->y = 0.0; + +// nr_matrix_set_identity(&glyphs->cached_tr); +// glyphs->cached_shp=NULL; +// glyphs->cached_shp_dirty=false; +// glyphs->cached_style_dirty=false; + +// glyphs->stroke_shp=NULL; +} + +static void +nr_arena_glyphs_finalize (NRObject *object) +{ + NRArenaGlyphs *glyphs=static_cast(object); + +// if (glyphs->cached_shp) { +// delete glyphs->cached_shp; +// glyphs->cached_shp = NULL; +// } +// if (glyphs->stroke_shp) { +// delete glyphs->stroke_shp; +// glyphs->stroke_shp = NULL; +// } + + if (glyphs->rfont) { + glyphs->rfont->Unref(); + glyphs->rfont=NULL; + } + if (glyphs->sfont) { + glyphs->sfont->Unref(); + glyphs->sfont=NULL; + } + + if (glyphs->font) { + glyphs->font->Unref(); + glyphs->font=NULL; + } + + if (glyphs->style) { + sp_style_unref (glyphs->style); + glyphs->style = NULL; + } + + ((NRObjectClass *) glyphs_parent_class)->finalize (object); +} + +static guint +nr_arena_glyphs_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRArenaGlyphs *glyphs; + raster_font *rfont; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font || !glyphs->style) return NR_ARENA_ITEM_STATE_ALL; + if ((glyphs->style->fill.type == SP_PAINT_TYPE_NONE) && (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE)) return NR_ARENA_ITEM_STATE_ALL; + + NRRect bbox; + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + + if (glyphs->style->fill.type != SP_PAINT_TYPE_NONE) { + NRMatrix t; + nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform); + glyphs->x = t.c[4]; + glyphs->y = t.c[5]; + t.c[4]=0; + t.c[5]=0; + rfont = glyphs->font->RasterFont(t, 0); + if (glyphs->rfont) glyphs->rfont->Unref(); + glyphs->rfont = rfont; + + if (glyphs->style->stroke.type == SP_PAINT_TYPE_NONE) { // Optimization: do fill bbox only if there's no stroke + NRRect narea; + if ( glyphs->rfont ) glyphs->rfont->BBox(glyphs->glyph, &narea); + bbox.x0 = narea.x0 + glyphs->x; + bbox.y0 = narea.y0 + glyphs->y; + bbox.x1 = narea.x1 + glyphs->x; + bbox.y1 = narea.y1 + glyphs->y; + } + } + + if (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE) { + /* Build state data */ + NRMatrix t; + nr_matrix_multiply (&t, &glyphs->g_transform, &gc->transform); + glyphs->x = t.c[4]; + glyphs->y = t.c[5]; + t.c[4]=0; + t.c[5]=0; + + const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + if ( fabs(glyphs->style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord + font_style nstyl; + nstyl.transform = t; + nstyl.stroke_width=MAX (0.125, glyphs->style->stroke_width.computed * scale); + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ) nstyl.stroke_cap=butt_straight; + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ) nstyl.stroke_cap=butt_round; + if ( glyphs->style->stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ) nstyl.stroke_cap=butt_square; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ) nstyl.stroke_join=join_pointy; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ) nstyl.stroke_join=join_round; + if ( glyphs->style->stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ) nstyl.stroke_join=join_straight; + nstyl.stroke_miter_limit = glyphs->style->stroke_miterlimit.value; + nstyl.nbDash=0; + nstyl.dashes=NULL; + if ( glyphs->style->stroke_dash.n_dash > 0 ) { + nstyl.nbDash=glyphs->style->stroke_dash.n_dash; + nstyl.dashes=(double*)malloc(nstyl.nbDash*sizeof(double)); + for (int i = 0; i < nstyl.nbDash; i++) nstyl.dashes[i]= glyphs->style->stroke_dash.dash[i] * scale; + } + rfont = glyphs->font->RasterFont( nstyl); + if ( nstyl.dashes ) free(nstyl.dashes); + if (glyphs->sfont) glyphs->sfont->Unref(); + glyphs->sfont = rfont; + + NRRect narea; + if ( glyphs->sfont ) glyphs->sfont->BBox(glyphs->glyph, &narea); + narea.x0-=nstyl.stroke_width; + narea.y0-=nstyl.stroke_width; + narea.x1+=nstyl.stroke_width; + narea.y1+=nstyl.stroke_width; + bbox.x0 = narea.x0 + glyphs->x; + bbox.y0 = narea.y0 + glyphs->y; + bbox.x1 = narea.x1 + glyphs->x; + bbox.y1 = narea.y1 + glyphs->y; + } + } + if (nr_rect_d_test_empty(&bbox)) return NR_ARENA_ITEM_STATE_ALL; + + item->bbox.x0 = (gint32)(bbox.x0 - 1.0); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0); + nr_arena_request_render_rect (item->arena, &item->bbox); + + return NR_ARENA_ITEM_STATE_ALL; +} + +static guint +nr_arena_glyphs_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGlyphs *glyphs; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font ) return item->state; + + /* TODO : render to greyscale pixblock provided for clipping */ + + return item->state; +} + +static NRArenaItem * +nr_arena_glyphs_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky) +{ + NRArenaGlyphs *glyphs; + + glyphs = NR_ARENA_GLYPHS (item); + + if (!glyphs->font ) return NULL; + if (!glyphs->style) return NULL; + + const double x = p[NR::X]; + const double y = p[NR::Y]; + /* With text we take a simple approach: pick if the point is in a characher bbox */ + if ((x >= item->bbox.x0) && (y >= item->bbox.y0) && (x <= item->bbox.x1) && (y <= item->bbox.y1)) return item; + +/* NR::Point const thePt = p; + if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) { + if (glyphs->stroke_shp->PtWinding(thePt) > 0 ) return item; + } + if (delta > 1e-3) { + if (glyphs->stroke_shp && (glyphs->style->stroke.type != SP_PAINT_TYPE_NONE)) { + if ( glyphs->stroke_shp->DistanceLE(thePt, delta)) return item; + } + }*/ + + return NULL; +} + +void +nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs, SPCurve *curve, unsigned int lieutenant, font_instance *font, gint glyph, const NRMatrix *transform) +{ + nr_return_if_fail (glyphs != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs)); + + nr_arena_item_request_render (NR_ARENA_ITEM (glyphs)); + + // glyphs->cached_shp_dirty=true; + + if (transform) { + glyphs->g_transform = *transform; + } else { + nr_matrix_set_identity (&glyphs->g_transform); + } + + //printf("glyph_setpath "); + if ( font ) font->Ref(); + if ( glyphs->font ) glyphs->font->Unref(); + glyphs->font=font; + glyphs->glyph = glyph; + + nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style) +{ + nr_return_if_fail (glyphs != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS (glyphs)); + +// glyphs->cached_style_dirty=true; + + if (style) sp_style_ref (style); + if (glyphs->style) sp_style_unref (glyphs->style); + glyphs->style = style; + + nr_arena_item_request_update (NR_ARENA_ITEM (glyphs), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static guint +nr_arena_glyphs_fill_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m) +{ + NRArenaItem *item; + + /* fixme: area == m->area, so merge these */ + + item = NR_ARENA_ITEM (glyphs); + + if (glyphs->rfont && nr_rect_l_test_intersect (area, &item->bbox)) { + raster_glyph* g=glyphs->rfont->GetGlyph(glyphs->glyph); + if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m); + } + + return item->state; +} + +static guint +nr_arena_glyphs_stroke_mask (NRArenaGlyphs *glyphs, NRRectL *area, NRPixBlock *m) +{ + NRArenaItem *item; + + item = NR_ARENA_ITEM (glyphs); + if (glyphs->sfont && nr_rect_l_test_intersect (area, &item->bbox)) { + raster_glyph* g=glyphs->sfont->GetGlyph(glyphs->glyph); + if ( g ) g->Blit(NR::Point(glyphs->x, glyphs->y),*m); + } +/* if (glyphs->stroke_shp && nr_rect_l_test_intersect (area, &item->bbox)) { + NRPixBlock gb; + gint x, y; + nr_pixblock_setup_fast (&gb, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + // art_gray_svp_aa is just fillung apparently + // dunno why it's used here instead of its libnr counterpart + nr_pixblock_render_shape_mask_or (gb,glyphs->stroke_shp); + for (y = area->y0; y < area->y1; y++) { + guchar *d, *s; + d = NR_PIXBLOCK_PX (m) + (y - area->y0) * m->rs; + s = NR_PIXBLOCK_PX (&gb) + (y - area->y0) * gb.rs; + for (x = area->x0; x < area->x1; x++) { + *d = (*d) + ((255 - *d) * (*s) / 255); + d += 1; + s += 1; + } + } + nr_pixblock_release (&gb); + m->empty = FALSE; + }*/ + + return item->state; +} + +static void nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass); +static void nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group); +static void nr_arena_glyphs_group_finalize (NRObject *object); + +static guint nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static unsigned int nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static unsigned int nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky); + +static NRArenaGroupClass *group_parent_class; + +NRType +nr_arena_glyphs_group_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_GROUP, + "NRArenaGlyphsGroup", + sizeof (NRArenaGlyphsGroupClass), + sizeof (NRArenaGlyphsGroup), + (void (*) (NRObjectClass *)) nr_arena_glyphs_group_class_init, + (void (*) (NRObject *)) nr_arena_glyphs_group_init); + } + return type; +} + +static void +nr_arena_glyphs_group_class_init (NRArenaGlyphsGroupClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + group_parent_class = (NRArenaGroupClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_glyphs_group_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_glyphs_group_update; + item_class->render = nr_arena_glyphs_group_render; + item_class->clip = nr_arena_glyphs_group_clip; + item_class->pick = nr_arena_glyphs_group_pick; +} + +static void +nr_arena_glyphs_group_init (NRArenaGlyphsGroup *group) +{ + group->style = NULL; + group->paintbox.x0 = group->paintbox.y0 = 0.0F; + group->paintbox.x1 = group->paintbox.y1 = 1.0F; + + group->fill_painter = NULL; + group->stroke_painter = NULL; +} + +static void +nr_arena_glyphs_group_finalize (NRObject *object) +{ + NRArenaGlyphsGroup *group=static_cast(object); + + if (group->fill_painter) { + sp_painter_free (group->fill_painter); + group->fill_painter = NULL; + } + + if (group->stroke_painter) { + sp_painter_free (group->stroke_painter); + group->stroke_painter = NULL; + } + + if (group->style) { + sp_style_unref (group->style); + group->style = NULL; + } + + ((NRObjectClass *) group_parent_class)->finalize (object); +} + +static guint +nr_arena_glyphs_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRArenaGlyphsGroup *group = NR_ARENA_GLYPHS_GROUP (item); + + if (group->fill_painter) { + sp_painter_free (group->fill_painter); + group->fill_painter = NULL; + } + + if (group->stroke_painter) { + sp_painter_free (group->stroke_painter); + group->stroke_painter = NULL; + } + + item->render_opacity = TRUE; + if (group->style->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + group->fill_painter = sp_paint_server_painter_new (SP_STYLE_FILL_SERVER (group->style), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &group->paintbox); + item->render_opacity = FALSE; + } + + if (group->style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) { + group->stroke_painter = sp_paint_server_painter_new (SP_STYLE_STROKE_SERVER (group->style), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &group->paintbox); + item->render_opacity = FALSE; + } + + if ( item->render_opacity == TRUE && group->style->stroke.type != SP_PAINT_TYPE_NONE && group->style->fill.type != SP_PAINT_TYPE_NONE ) { + item->render_opacity=FALSE; + } + + if (((NRArenaItemClass *) group_parent_class)->update) + return ((NRArenaItemClass *) group_parent_class)->update (item, area, gc, state, reset); + + return NR_ARENA_ITEM_STATE_ALL; +} + +/* This sucks - as soon, as we have inheritable renderprops, do something with that opacity */ + +static unsigned int +nr_arena_glyphs_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaItem *child; + + NRArenaGroup *group = NR_ARENA_GROUP (item); + NRArenaGlyphsGroup *ggroup = NR_ARENA_GLYPHS_GROUP (item); + SPStyle const *style = ggroup->style; + + guint ret = item->state; + + /* Fill */ + if (style->fill.type != SP_PAINT_TYPE_NONE || item->arena->rendermode == RENDERMODE_OUTLINE) { + NRPixBlock m; + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + + /* Render children fill mask */ + for (child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, &m); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) { + nr_pixblock_release (&m); + return ret; + } + } + + /* Composite into buffer */ + if (style->fill.type == SP_PAINT_TYPE_COLOR || item->arena->rendermode == RENDERMODE_OUTLINE) { + guint32 rgba; + if (item->arena->rendermode == RENDERMODE_OUTLINE) { + // In outline mode, render fill only, using outlinecolor + rgba = item->arena->outlinecolor; + } else if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&style->fill.value.color, + SP_SCALE24_TO_FLOAT (style->fill_opacity.value) * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&style->fill.value.color, SP_SCALE24_TO_FLOAT (style->fill_opacity.value)); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (style->fill.type == SP_PAINT_TYPE_PAINTSERVER) { + if (ggroup->fill_painter) { + nr_arena_render_paintserver_fill (pb, area, ggroup->fill_painter, SP_SCALE24_TO_FLOAT (style->fill_opacity.value), &m); + } + } + + nr_pixblock_release (&m); + } + + /* Stroke */ + if (style->stroke.type != SP_PAINT_TYPE_NONE && !(item->arena->rendermode == RENDERMODE_OUTLINE)) { + NRPixBlock m; + guint32 rgba; + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + /* Render children stroke mask */ + for (child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_stroke_mask (NR_ARENA_GLYPHS (child), area, &m); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) { + nr_pixblock_release (&m); + return ret; + } + } + /* Composite into buffer */ + switch (style->stroke.type) { + case SP_PAINT_TYPE_COLOR: + if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color, + SP_SCALE24_TO_FLOAT (style->stroke_opacity.value) * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&style->stroke.value.color, + SP_SCALE24_TO_FLOAT (style->stroke_opacity.value)); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + break; + case SP_PAINT_TYPE_PAINTSERVER: + if (ggroup->stroke_painter) { + nr_arena_render_paintserver_fill (pb, area, ggroup->stroke_painter, SP_SCALE24_TO_FLOAT (style->stroke_opacity.value), &m); + } + break; + default: + break; + } + nr_pixblock_release (&m); + } + + return ret; +} + +static unsigned int +nr_arena_glyphs_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + guint ret = item->state; + + /* Render children fill mask */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_glyphs_fill_mask (NR_ARENA_GLYPHS (child), area, pb); + if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) return ret; + } + + return ret; +} + +static NRArenaItem * +nr_arena_glyphs_group_pick (NRArenaItem *item, NR::Point p, gdouble delta, unsigned int sticky) +{ + NRArenaItem *picked = NULL; + + if (((NRArenaItemClass *) group_parent_class)->pick) + picked = ((NRArenaItemClass *) group_parent_class)->pick (item, p, delta, sticky); + + if (picked) picked = item; + + return picked; +} + +void +nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *sg) +{ + NRArenaGroup *group = NR_ARENA_GROUP (sg); + + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + + while (group->children) { + nr_arena_item_remove_child (NR_ARENA_ITEM (group), group->children); + } +} + +void +nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *sg, font_instance *font, int glyph, const NRMatrix *transform) +{ + NRArenaGroup *group; + NRBPath bpath; + + group = NR_ARENA_GROUP (sg); + + if ( font ) bpath.path=(NArtBpath*)font->ArtBPath(glyph); else bpath.path=NULL; + if ( bpath.path ) { + + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + + NRArenaItem *new_arena; + new_arena = NRArenaGlyphs::create(group->arena); + nr_arena_item_append_child (NR_ARENA_ITEM (group), new_arena); + nr_arena_item_unref (new_arena); + nr_arena_glyphs_set_path (NR_ARENA_GLYPHS (new_arena), NULL, FALSE, font, glyph, transform); + nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (new_arena), sg->style); + } + +} + +void +nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *sg, SPStyle *style) +{ + NRArenaGroup *group; + NRArenaItem *child; + + nr_return_if_fail (sg != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (sg)); + + group = NR_ARENA_GROUP (sg); + + if (style) sp_style_ref (style); + if (sg->style) sp_style_unref (sg->style); + sg->style = style; + + for (child = group->children; child != NULL; child = child->next) { + nr_return_if_fail (NR_IS_ARENA_GLYPHS (child)); + nr_arena_glyphs_set_style (NR_ARENA_GLYPHS (child), sg->style); + } + + nr_arena_item_request_update (NR_ARENA_ITEM (sg), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *gg, const NRRect *pbox) +{ + nr_return_if_fail (gg != NULL); + nr_return_if_fail (NR_IS_ARENA_GLYPHS_GROUP (gg)); + nr_return_if_fail (pbox != NULL); + + if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) { + gg->paintbox.x0 = pbox->x0; + gg->paintbox.y0 = pbox->y0; + gg->paintbox.x1 = pbox->x1; + gg->paintbox.y1 = pbox->y1; + } else { + /* fixme: We kill warning, although not sure what to do here (Lauris) */ + gg->paintbox.x0 = gg->paintbox.y0 = 0.0F; + gg->paintbox.x1 = gg->paintbox.y1 = 256.0F; + } + + nr_arena_item_request_update (NR_ARENA_ITEM (gg), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + diff --git a/src/display/nr-arena-glyphs.h b/src/display/nr-arena-glyphs.h new file mode 100644 index 000000000..23b74a378 --- /dev/null +++ b/src/display/nr-arena-glyphs.h @@ -0,0 +1,109 @@ +#ifndef __NR_ARENA_GLYPHS_H__ +#define __NR_ARENA_GLYPHS_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + * + */ + +#define NR_TYPE_ARENA_GLYPHS (nr_arena_glyphs_get_type ()) +#define NR_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS, NRArenaGlyphs)) +#define NR_IS_ARENA_GLYPHS(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS)) + +#include + +#include +#include +#include +#include + +#define test_glyph_liv + +class Shape; + +NRType nr_arena_glyphs_get_type (void); + +struct NRArenaGlyphs : public NRArenaItem { + /* Glyphs data */ + SPStyle *style; + NRMatrix g_transform; + font_instance *font; + gint glyph; + + raster_font *rfont; + raster_font *sfont; + float x, y; + +// NRMatrix cached_tr; +// Shape *cached_shp; +// bool cached_shp_dirty; +// bool cached_style_dirty; + +// Shape *stroke_shp; + + static NRArenaGlyphs *create(NRArena *arena) { + NRArenaGlyphs *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GLYPHS)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGlyphsClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_glyphs_set_path (NRArenaGlyphs *glyphs, + SPCurve *curve, unsigned int lieutenant, + font_instance *font, int glyph, + const NRMatrix *transform); +void nr_arena_glyphs_set_style (NRArenaGlyphs *glyphs, SPStyle *style); + +/* Integrated group of component glyphss */ + +typedef struct NRArenaGlyphsGroup NRArenaGlyphsGroup; +typedef struct NRArenaGlyphsGroupClass NRArenaGlyphsGroupClass; + +#include "nr-arena-group.h" + +#define NR_TYPE_ARENA_GLYPHS_GROUP (nr_arena_glyphs_group_get_type ()) +#define NR_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_GLYPHS_GROUP, NRArenaGlyphsGroup)) +#define NR_IS_ARENA_GLYPHS_GROUP(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_GLYPHS_GROUP)) + +NRType nr_arena_glyphs_group_get_type (void); + +struct NRArenaGlyphsGroup : public NRArenaGroup { + SPStyle *style; + NRRect paintbox; + /* State data */ + SPPainter *fill_painter; + SPPainter *stroke_painter; + + static NRArenaGlyphsGroup *create(NRArena *arena) { + NRArenaGlyphsGroup *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GLYPHS_GROUP)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGlyphsGroupClass { + NRArenaGroupClass parent_class; +}; + +/* Utility functions */ + +void nr_arena_glyphs_group_clear (NRArenaGlyphsGroup *group); + +void nr_arena_glyphs_group_add_component (NRArenaGlyphsGroup *group, font_instance *font, int glyph, const NRMatrix *transform); + +void nr_arena_glyphs_group_set_style (NRArenaGlyphsGroup *group, SPStyle *style); + +void nr_arena_glyphs_group_set_paintbox (NRArenaGlyphsGroup *group, const NRRect *pbox); + +#endif diff --git a/src/display/nr-arena-group.cpp b/src/display/nr-arena-group.cpp new file mode 100644 index 000000000..64274202f --- /dev/null +++ b/src/display/nr-arena-group.cpp @@ -0,0 +1,256 @@ +#define __NR_ARENA_GROUP_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "nr-arena-group.h" + +static void nr_arena_group_class_init (NRArenaGroupClass *klass); +static void nr_arena_group_init (NRArenaGroup *group); + +static NRArenaItem *nr_arena_group_children (NRArenaItem *item); +static NRArenaItem *nr_arena_group_last_child (NRArenaItem *item); +static void nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +static void nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child); +static void nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +static unsigned int nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); +static unsigned int nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static unsigned int nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *parent_class; + +NRType +nr_arena_group_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaGroup", + sizeof (NRArenaGroupClass), + sizeof (NRArenaGroup), + (void (*) (NRObjectClass *)) nr_arena_group_class_init, + (void (*) (NRObject *)) nr_arena_group_init); + } + return type; +} + +static void +nr_arena_group_class_init (NRArenaGroupClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->children = nr_arena_group_children; + item_class->last_child = nr_arena_group_last_child; + item_class->add_child = nr_arena_group_add_child; + item_class->set_child_position = nr_arena_group_set_child_position; + item_class->remove_child = nr_arena_group_remove_child; + item_class->update = nr_arena_group_update; + item_class->render = nr_arena_group_render; + item_class->clip = nr_arena_group_clip; + item_class->pick = nr_arena_group_pick; +} + +static void +nr_arena_group_init (NRArenaGroup *group) +{ + group->transparent = FALSE; + group->children = NULL; + group->last = NULL; + nr_matrix_set_identity (&group->child_transform); + +#ifdef arena_item_tile_cache + group->skipCaching=true; +#endif + +} + +static NRArenaItem * +nr_arena_group_children (NRArenaItem *item) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + return group->children; +} + +static NRArenaItem * +nr_arena_group_last_child (NRArenaItem *item) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + return group->last; +} + +static void +nr_arena_group_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (!ref) { + group->children = nr_arena_item_attach_ref (item, child, NULL, group->children); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + if (ref == group->last) group->last = child; + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_group_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (child == group->last) group->last = child->prev; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + group->children = nr_arena_item_detach_unref (item, child); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_group_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + if (child == group->last) group->last = child->prev; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + group->children = nr_arena_item_detach_unref (item, child); + } + + if (!ref) { + group->children = nr_arena_item_attach_ref (item, child, NULL, group->children); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + if (ref == group->last) group->last = child; + + nr_arena_item_request_render (child); +} + +static unsigned int +nr_arena_group_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + unsigned int newstate; + + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int beststate = NR_ARENA_ITEM_STATE_ALL; + + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + NRGC cgc(gc); + nr_matrix_multiply (&cgc.transform, &group->child_transform, &gc->transform); + newstate = nr_arena_item_invoke_update (child, area, &cgc, state, reset); + beststate = beststate & newstate; + } + + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + nr_rect_l_set_empty (&item->bbox); + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + + return beststate; +} + +static unsigned int +nr_arena_group_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int ret = item->state; + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_item_invoke_render (child, area, pb, flags); + if (ret & NR_ARENA_ITEM_STATE_INVALID) break; + } + + return ret; +} + +static unsigned int +nr_arena_group_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + unsigned int ret = item->state; + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = group->children; child != NULL; child = child->next) { + ret = nr_arena_item_invoke_clip (child, area, pb); + if (ret & NR_ARENA_ITEM_STATE_INVALID) break; + } + + return ret; +} + +static NRArenaItem * +nr_arena_group_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + NRArenaGroup *group = NR_ARENA_GROUP (item); + + for (NRArenaItem *child = group->last; child != NULL; child = child->prev) { + NRArenaItem *picked = nr_arena_item_invoke_pick (child, p, delta, sticky); + if (picked) + return (group->transparent) ? picked : item; + } + + return NULL; +} + +void +nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent) +{ + nr_return_if_fail (group != NULL); + nr_return_if_fail (NR_IS_ARENA_GROUP (group)); + + group->transparent = transparent; +} + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t) +{ + NRMatrix nt(t); + nr_arena_group_set_child_transform(group, &nt); +} + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t) +{ + if (!t) t = &NR_MATRIX_IDENTITY; + + if (!NR_MATRIX_DF_TEST_CLOSE (t, &group->child_transform, NR_EPSILON)) { + nr_arena_item_request_render (NR_ARENA_ITEM (group)); + group->child_transform = *t; + nr_arena_item_request_update (NR_ARENA_ITEM (group), NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + + diff --git a/src/display/nr-arena-group.h b/src/display/nr-arena-group.h new file mode 100644 index 000000000..b33495362 --- /dev/null +++ b/src/display/nr-arena-group.h @@ -0,0 +1,46 @@ +#ifndef __NR_ARENA_GROUP_H__ +#define __NR_ARENA_GROUP_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#define NR_TYPE_ARENA_GROUP (nr_arena_group_get_type ()) +#define NR_ARENA_GROUP(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_GROUP, NRArenaGroup)) +#define NR_IS_ARENA_GROUP(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_GROUP)) + +#include "nr-arena-item.h" + +NRType nr_arena_group_get_type (void); + +struct NRArenaGroup : public NRArenaItem{ + unsigned int transparent : 1; + NRArenaItem *children; + NRArenaItem *last; + NRMatrix child_transform; + + static NRArenaGroup *create(NRArena *arena) { + NRArenaGroup *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_GROUP)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaGroupClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_group_set_transparent (NRArenaGroup *group, unsigned int transparent); + +void nr_arena_group_set_child_transform(NRArenaGroup *group, NR::Matrix const &t); +void nr_arena_group_set_child_transform(NRArenaGroup *group, NRMatrix const *t); + +#endif diff --git a/src/display/nr-arena-image.cpp b/src/display/nr-arena-image.cpp new file mode 100644 index 000000000..ee566d2dc --- /dev/null +++ b/src/display/nr-arena-image.cpp @@ -0,0 +1,258 @@ +#define __NR_ARENA_IMAGE_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include "../prefs-utils.h" +#include "nr-arena-image.h" + +int nr_arena_image_x_sample = 1; +int nr_arena_image_y_sample = 1; + +/* + * NRArenaCanvasImage + * + */ + +static void nr_arena_image_class_init (NRArenaImageClass *klass); +static void nr_arena_image_init (NRArenaImage *image); +static void nr_arena_image_finalize (NRObject *object); + +static unsigned int nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); +static unsigned int nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static NRArenaItem *nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *parent_class; + +NRType +nr_arena_image_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaImage", + sizeof (NRArenaImageClass), + sizeof (NRArenaImage), + (void (*) (NRObjectClass *)) nr_arena_image_class_init, + (void (*) (NRObject *)) nr_arena_image_init); + } + return type; +} + +static void +nr_arena_image_class_init (NRArenaImageClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_image_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->update = nr_arena_image_update; + item_class->render = nr_arena_image_render; + item_class->pick = nr_arena_image_pick; +} + +static void +nr_arena_image_init (NRArenaImage *image) +{ + image->px = NULL; + + image->pxw = image->pxh = image->pxrs = 0; + image->x = image->y = 0.0; + image->width = 256.0; + image->height = 256.0; + + nr_matrix_set_identity (&image->grid2px); +} + +static void +nr_arena_image_finalize (NRObject *object) +{ + NRArenaImage *image = NR_ARENA_IMAGE (object); + + image->px = NULL; + + ((NRObjectClass *) parent_class)->finalize (object); +} + +static unsigned int +nr_arena_image_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + NRMatrix grid2px; + + NRArenaImage *image = NR_ARENA_IMAGE (item); + + /* Request render old */ + nr_arena_item_request_render (item); + + /* Copy affine */ + nr_matrix_invert (&grid2px, &gc->transform); + double hscale, vscale; // todo: replace with NR::scale + if (image->px) { + hscale = image->pxw / image->width; + vscale = image->pxh / image->height; + } else { + hscale = 1.0; + vscale = 1.0; + } + + image->grid2px[0] = grid2px.c[0] * hscale; + image->grid2px[2] = grid2px.c[2] * hscale; + image->grid2px[4] = grid2px.c[4] * hscale; + image->grid2px[1] = grid2px.c[1] * vscale; + image->grid2px[3] = grid2px.c[3] * vscale; + image->grid2px[5] = grid2px.c[5] * vscale; + + image->grid2px[4] -= image->x * hscale; + image->grid2px[5] -= image->y * vscale; + + /* Calculate bbox */ + if (image->px) { + NRRect bbox; + + bbox.x0 = image->x; + bbox.y0 = image->y; + bbox.x1 = image->x + image->width; + bbox.y1 = image->y + image->height; + nr_rect_d_matrix_transform (&bbox, &bbox, &gc->transform); + + item->bbox.x0 = (int) floor (bbox.x0); + item->bbox.y0 = (int) floor (bbox.y0); + item->bbox.x1 = (int) ceil (bbox.x1); + item->bbox.y1 = (int) ceil (bbox.y1); + } else { + item->bbox.x0 = (int) gc->transform[4]; + item->bbox.y0 = (int) gc->transform[5]; + item->bbox.x1 = item->bbox.x0 - 1; + item->bbox.y1 = item->bbox.y0 - 1; + } + + nr_arena_item_request_render (item); + + return NR_ARENA_ITEM_STATE_ALL; +} + +#define FBITS 12 +#define b2i (image->grid2px) + +static unsigned int +nr_arena_image_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + nr_arena_image_x_sample = prefs_get_int_attribute ("options.bitmapoversample", "value", 1); + nr_arena_image_y_sample = nr_arena_image_x_sample; + + NRArenaImage *image = NR_ARENA_IMAGE (item); + + if (!image->px) return item->state; + + guint32 Falpha = item->opacity; + if (Falpha < 1) return item->state; + + unsigned char * dpx = NR_PIXBLOCK_PX (pb); + const int drs = pb->rs; + const int dw = pb->area.x1 - pb->area.x0; + const int dh = pb->area.y1 - pb->area.y0; + + unsigned char * spx = image->px; + const int srs = image->pxrs; + const int sw = image->pxw; + const int sh = image->pxh; + + NR::Matrix d2s; + + d2s[0] = b2i[0]; + d2s[1] = b2i[1]; + d2s[2] = b2i[2]; + d2s[3] = b2i[3]; + d2s[4] = b2i[0] * pb->area.x0 + b2i[2] * pb->area.y0 + b2i[4]; + d2s[5] = b2i[1] * pb->area.x0 + b2i[3] * pb->area.y0 + b2i[5]; + + if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) { + /* fixme: This is not implemented yet (Lauris) */ + /* nr_R8G8B8_R8G8B8_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); */ + } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) { + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) { + + //FIXME: The _N_N_N_ version gives a gray border around images, see bug 906376 + // This mode is only used when exporting, screen rendering always has _P_P_P_, so I decided to simply replace it for now + // Feel free to propose a better fix + + //nr_R8G8B8A8_N_R8G8B8A8_N_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + nr_R8G8B8A8_P_R8G8B8A8_P_R8G8B8A8_N_TRANSFORM (dpx, dw, dh, drs, spx, sw, sh, srs, d2s, Falpha, nr_arena_image_x_sample, nr_arena_image_y_sample); + } + + pb->empty = FALSE; + + return item->state; +} + +static NRArenaItem * +nr_arena_image_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + NRArenaImage *image = NR_ARENA_IMAGE (item); + + if (!image->px) return NULL; + + unsigned char * const pixels = image->px; + const int width = image->pxw; + const int height = image->pxh; + const int rowstride = image->pxrs; + NR::Point tp = p * image->grid2px; + const int ix = (int)(tp[NR::X]); + const int iy = (int)(tp[NR::Y]); + + if ((ix < 0) || (iy < 0) || (ix >= width) || (iy >= height)) + return NULL; + + unsigned char *pix_ptr = pixels + iy * rowstride + ix * 4; + // is the alpha not transparent? + return (pix_ptr[3] > 0) ? item : NULL; +} + +/* Utility */ + +void +nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs) +{ + nr_return_if_fail (image != NULL); + nr_return_if_fail (NR_IS_ARENA_IMAGE (image)); + + image->px = (unsigned char *) px; + image->pxw = pxw; + image->pxh = pxh; + image->pxrs = pxrs; + + nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height) +{ + nr_return_if_fail (image != NULL); + nr_return_if_fail (NR_IS_ARENA_IMAGE (image)); + + image->x = x; + image->y = y; + image->width = width; + image->height = height; + + nr_arena_item_request_update (NR_ARENA_ITEM (image), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + diff --git a/src/display/nr-arena-image.h b/src/display/nr-arena-image.h new file mode 100644 index 000000000..8c5afc55f --- /dev/null +++ b/src/display/nr-arena-image.h @@ -0,0 +1,51 @@ +#ifndef __NR_ARENA_IMAGE_H__ +#define __NR_ARENA_IMAGE_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_IMAGE (nr_arena_image_get_type ()) +#define NR_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_IMAGE, NRArenaImage)) +#define NR_IS_ARENA_IMAGE(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_IMAGE)) + +#include +#include "nr-arena-item.h" + +NRType nr_arena_image_get_type (void); + +struct NRArenaImage : public NRArenaItem { + unsigned char *px; + unsigned int pxw; + unsigned int pxh; + unsigned int pxrs; + + double x, y; + double width, height; + + /* From GRID to PIXELS */ + NR::Matrix grid2px; + + static NRArenaImage *create(NRArena *arena) { + NRArenaImage *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_IMAGE)); + obj->init(arena); + return obj; + } +}; + +struct NRArenaImageClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_image_set_pixels (NRArenaImage *image, const unsigned char *px, unsigned int pxw, unsigned int pxh, unsigned int pxrs); +void nr_arena_image_set_geometry (NRArenaImage *image, double x, double y, double width, double height); + +#endif diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp new file mode 100644 index 000000000..ccabe7b28 --- /dev/null +++ b/src/display/nr-arena-item.cpp @@ -0,0 +1,1155 @@ +#define __NR_ARENA_ITEM_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noNR_ARENA_ITEM_VERBOSE +#define noNR_ARENA_ITEM_DEBUG_CASCADE + + +#include +#include +#include "nr-arena.h" +#include "nr-arena-item.h" +//#include "nr-arena-group.h" + + +static void nr_arena_item_class_init (NRArenaItemClass *klass); +static void nr_arena_item_init (NRArenaItem *item); +static void nr_arena_item_private_finalize (NRObject *object); + +#ifdef arena_item_tile_cache +bool insert_cache(NRArenaItem* owner,int th,int tv,NRPixBlock *ipb,NRPixBlock *mpb,double activity,double duration); +void remove_caches(NRArenaItem* owner); +bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask); +#endif + +static NRObjectClass *parent_class; + +NRType +nr_arena_item_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_OBJECT, + "NRArenaItem", + sizeof (NRArenaItemClass), + sizeof (NRArenaItem), + (void (*) (NRObjectClass *)) nr_arena_item_class_init, + (void (*) (NRObject *)) nr_arena_item_init); + } + return type; +} + +static void +nr_arena_item_class_init (NRArenaItemClass *klass) +{ + NRObjectClass *object_class; + + object_class = (NRObjectClass *) klass; + + parent_class = ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_item_private_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +NRArenaItem::NRArenaItem() { + // clear all reverse-pointing pointers before finalization + clearOnceInaccessible(&arena); + clearOnceInaccessible(&parent); + clearOnceInaccessible(&prev); +} + +static void +nr_arena_item_init (NRArenaItem *item) +{ + item->arena = NULL; + item->parent = NULL; + item->next = item->prev = NULL; + + item->key = 0; + + item->state = 0; + item->sensitive = TRUE; + item->visible = TRUE; + + memset(&item->bbox, 0, sizeof(item->bbox)); + item->transform = NULL; + item->opacity = 255; + item->render_opacity = FALSE; + +#ifdef arena_item_tile_cache + item->activity=0.0; + item->skipCaching=false; +#endif + + item->transform = NULL; + item->clip = NULL; + item->mask = NULL; + item->px = NULL; + item->data = NULL; +} + +static void +nr_arena_item_private_finalize (NRObject *object) +{ + NRArenaItem *item=static_cast(object); + +#ifdef arena_item_tile_cache + remove_caches(item); +#endif + + if (item->px) { + nr_free (item->px); + } + + if (item->transform) { + nr_free (item->transform); + } + + ((NRObjectClass *) (parent_class))->finalize (object); +} + +NRArenaItem * +nr_arena_item_children (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + if (NR_ARENA_ITEM_VIRTUAL (item, children)) + return NR_ARENA_ITEM_VIRTUAL (item, children) (item); + + return NULL; +} + +NRArenaItem * +nr_arena_item_last_child (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + if (NR_ARENA_ITEM_VIRTUAL (item, last_child)) { + return NR_ARENA_ITEM_VIRTUAL (item, last_child) (item); + } else { + NRArenaItem *ref; + ref = nr_arena_item_children (item); + if (ref) while (ref->next) ref = ref->next; + return ref; + } +} + +void +nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == NULL); + nr_return_if_fail (child->prev == NULL); + nr_return_if_fail (child->next == NULL); + nr_return_if_fail (child->arena == item->arena); + nr_return_if_fail (child != ref); + nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref)); + nr_return_if_fail (!ref || (ref->parent == item)); + + if (NR_ARENA_ITEM_VIRTUAL (item, add_child)) + NR_ARENA_ITEM_VIRTUAL (item, add_child) (item, child, ref); +} + +void +nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == item); + + if (NR_ARENA_ITEM_VIRTUAL (item, remove_child)) + NR_ARENA_ITEM_VIRTUAL (item, remove_child) (item, child); +} + +void +nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (child->parent == item); + nr_return_if_fail (!ref || NR_IS_ARENA_ITEM (ref)); + nr_return_if_fail (!ref || (ref->parent == item)); + + if (NR_ARENA_ITEM_VIRTUAL (item, set_child_position)) + NR_ARENA_ITEM_VIRTUAL (item, set_child_position) (item, child, ref); +} + +NRArenaItem * +nr_arena_item_ref (NRArenaItem *item) +{ + nr_object_ref ((NRObject *) item); + + return item; +} + +NRArenaItem * +nr_arena_item_unref (NRArenaItem *item) +{ + nr_object_unref ((NRObject *) item); + + return NULL; +} + +unsigned int +nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset) +{ + NRGC childgc(gc); + + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (!(state & NR_ARENA_ITEM_STATE_INVALID), NR_ARENA_ITEM_STATE_INVALID); + +#ifdef NR_ARENA_ITEM_DEBUG_CASCADE + printf ("Update %s:%p %x %x %x\n", nr_type_name_from_instance ((GTypeInstance *) item), item, state, item->state, reset); +#endif + + /* return if in error */ + if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state; + /* Set reset flags according to propagation status */ + if (item->propagate) { + reset |= ~item->state; + item->propagate = FALSE; + } + /* Reset our state */ + item->state &= ~reset; + /* Return if NOP */ + if (!(~item->state & state)) return item->state; + /* Test whether to return immediately */ + if (area && (item->state & NR_ARENA_ITEM_STATE_BBOX)) { + if (!nr_rect_l_test_intersect (area, &item->bbox)) return item->state; + } + + /* Reset image cache, if not to be kept */ + if (!(item->state & NR_ARENA_ITEM_STATE_IMAGE) && (item->px)) { + nr_free (item->px); + item->px = NULL; + } +#ifdef arena_item_tile_cache + remove_caches(item); +#endif + + /* Set up local gc */ + childgc = *gc; + if (item->transform) { + nr_matrix_multiply (&childgc.transform, item->transform, &childgc.transform); + } + + /* Invoke the real method */ + item->state = NR_ARENA_ITEM_VIRTUAL (item, update) (item, area, &childgc, state, reset); + if (item->state & NR_ARENA_ITEM_STATE_INVALID) return item->state; + /* Clipping */ + if (item->clip) { + unsigned int newstate; + newstate = nr_arena_item_invoke_update (item->clip, area, &childgc, state, reset); + if (newstate & NR_ARENA_ITEM_STATE_INVALID) { + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + nr_rect_l_intersect (&item->bbox, &item->bbox, &item->clip->bbox); + } + /* Masking */ + if (item->mask) { + unsigned int newstate; + newstate = nr_arena_item_invoke_update (item->mask, area, &childgc, state, reset); + if (newstate & NR_ARENA_ITEM_STATE_INVALID) { + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + nr_rect_l_intersect (&item->bbox, &item->bbox, &item->mask->bbox); + } + + return item->state; +} + +/** + * Render item to pixblock. + * + * \return Has NR_ARENA_ITEM_STATE_RENDER set on success. + */ + +unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags) +{ + NRRectL carea; + NRPixBlock *dpb; + NRPixBlock cpb; + unsigned int state; + + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (item->state & NR_ARENA_ITEM_STATE_BBOX, item->state); + +#ifdef NR_ARENA_ITEM_VERBOSE + printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1); +#endif + +#ifdef arena_item_tile_cache + item->activity*=0.5; +#endif + + /* If we are outside bbox just return successfully */ + if (!item->visible) return item->state | NR_ARENA_ITEM_STATE_RENDER; + nr_rect_l_intersect (&carea, area, &item->bbox); + if (nr_rect_l_test_empty (&carea)) return item->state | NR_ARENA_ITEM_STATE_RENDER; + + if (item->px) { + /* Has cache pixblock, render this and return */ + nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P, + /* fixme: This probably cannot overflow, because we render only if visible */ + /* fixme: and pixel cache is there only for small items */ + /* fixme: But this still needs extra check (Lauris) */ + item->bbox.x0, item->bbox.y0, + item->bbox.x1, item->bbox.y1, + item->px, 4 * (item->bbox.x1 - item->bbox.x0), FALSE, FALSE); + nr_blit_pixblock_pixblock (pb, &cpb); + nr_pixblock_release (&cpb); + pb->empty = FALSE; + return item->state | NR_ARENA_ITEM_STATE_RENDER; + } + + dpb = pb; + bool canCache=false; +#ifdef arena_item_tile_cache + bool checkCache=false; + int tile_h=0,tile_v=0; +#endif + /* Setup cache if we can */ + if ((!(flags & NR_ARENA_ITEM_RENDER_NO_CACHE)) && + (carea.x0 <= item->bbox.x0) && (carea.y0 <= item->bbox.y0) && + (carea.x1 >= item->bbox.x1) && (carea.y1 >= item->bbox.y1) && + (((item->bbox.x1 - item->bbox.x0) * (item->bbox.y1 - item->bbox.y0)) <= 4096)) { + // Item bbox is fully in renderable area and size is acceptable + carea.x0 = item->bbox.x0; + carea.y0 = item->bbox.y0; + carea.x1 = item->bbox.x1; + carea.y1 = item->bbox.y1; + item->px = nr_new (unsigned char, 4 * (carea.x1 - carea.x0) * (carea.y1 - carea.y0)); + nr_pixblock_setup_extern (&cpb, NR_PIXBLOCK_MODE_R8G8B8A8P, + carea.x0, carea.y0, carea.x1, carea.y1, + item->px, 4 * (carea.x1 - carea.x0), TRUE, TRUE); + dpb = &cpb; + // Set nocache flag for downstream rendering + flags |= NR_ARENA_ITEM_RENDER_NO_CACHE; + } else { +#ifdef arena_item_tile_cache + if ( item->skipCaching ) { + } else { + int tl=area->x0&(~127); + int tt=area->y0&(~127); + if ( area->x1 <= tl+128 && area->y1 <= tt+128 ) { + checkCache=true; + tile_h=tl/128; + tile_v=tt/128; + int surf=(area->x1-area->x0)*(area->y1-area->y0); + if ( surf >= 4096 ) { + canCache=true; + carea.x0=tl; + carea.y0=tt; + carea.x1=tl+128; + carea.y1=tt+128; + } + } + } +#endif + } + +#ifdef arena_item_tile_cache + item->activity+=1.0; +#endif + +#ifdef arena_item_tile_cache + if ( checkCache ) { + NRPixBlock ipb, mpb; + bool hasMask; + if ( test_cache(item,tile_h,tile_v,ipb,mpb,hasMask) ) { + // youpi! c'etait deja cache + if ( hasMask ) { + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + } else if ( ((item->opacity != 255) && !item->render_opacity) ) { + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + } else { + nr_blit_pixblock_pixblock (pb, &ipb); + } + pb->empty = FALSE; + return item->state | NR_ARENA_ITEM_STATE_RENDER; + } + } +#endif + if ( canCache ) { +#ifdef arena_item_tile_cache + // nota: exclusif de dpb != pb, donc pas de cas particulier a la fin + NRPixBlock ipb, mpb; + + // struct timeval start_time,end_time; + // gettimeofday(&start_time,NULL); + GTimeVal start_time,end_time; + g_get_current_time (&start_time); + int duration=0; + + /* Setup and render item buffer */ + nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + ipb.empty = FALSE; + + if (item->clip || item->mask) { + /* Setup mask pixblock */ + nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + /* Do clip if needed */ + if (item->clip) { + state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + mpb.empty = FALSE; + } + /* Do mask if needed */ + if (item->mask) { + NRPixBlock tpb; + /* Set up yet another temporary pixblock */ + nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&tpb); + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + /* Composite with clip */ + if (item->clip) { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = NR_PREMUL (d[0], m); + s += 4; + d += 1; + } + } + } else { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = m; + s += 4; + d += 1; + } + } + mpb.empty = FALSE; + } + nr_pixblock_release (&tpb); + } + /* Multiply with opacity if needed */ + if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) { + int x, y; + unsigned int a; + a = item->opacity; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *d; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + d[0] = NR_PREMUL (d[0], a); + d += 1; + } + } + } + /* Compose rendering pixblock int destination */ + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + if ( insert_cache(item,tile_h,tile_v,&ipb,&mpb,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } else if ( ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) ) { + /* Opacity only */ + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } else { + // gettimeofday(&end_time,NULL); + g_get_current_time (&end_time); + duration=(end_time.tv_sec-start_time.tv_sec)*1000+(end_time.tv_usec-start_time.tv_usec)/1000; + if ( !(ipb.empty) ) { + nr_blit_pixblock_pixblock (dpb, &ipb); + if ( insert_cache(item,tile_h,tile_v,&ipb,NULL,item->activity,(double)duration) ) { + } else { + nr_pixblock_release (&ipb); + } + dpb->empty = FALSE; + } else { + nr_pixblock_release (&ipb); + } + } +#endif + } else { + /* Determine, whether we need temporary buffer */ + if (item->clip || item->mask || ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE)) { + NRPixBlock ipb, mpb; + + /* Setup and render item buffer */ + nr_pixblock_setup_fast (&ipb, NR_PIXBLOCK_MODE_R8G8B8A8P, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, &ipb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + ipb.empty = FALSE; + + if (item->clip || item->mask) { + /* Setup mask pixblock */ + nr_pixblock_setup_fast (&mpb, NR_PIXBLOCK_MODE_A8, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + /* Do clip if needed */ + if (item->clip) { + state = nr_arena_item_invoke_clip (item->clip, &carea, &mpb); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + mpb.empty = FALSE; + } + /* Do mask if needed */ + if (item->mask) { + NRPixBlock tpb; + /* Set up yet another temporary pixblock */ + nr_pixblock_setup_fast (&tpb, NR_PIXBLOCK_MODE_R8G8B8A8N, carea.x0, carea.y0, carea.x1, carea.y1, TRUE); + state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (item->mask, &carea, &tpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + nr_pixblock_release (&tpb); + nr_pixblock_release (&mpb); + nr_pixblock_release (&ipb); + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + /* Composite with clip */ + if (item->clip) { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = NR_PREMUL (d[0], m); + s += 4; + d += 1; + } + } + } else { + int x, y; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&tpb) + (y - carea.y0) * tpb.rs; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + unsigned int m; + m = ((s[0] + s[1] + s[2]) * s[3] + 127) / (3 * 255); + d[0] = m; + s += 4; + d += 1; + } + } + mpb.empty = FALSE; + } + nr_pixblock_release (&tpb); + } + /* Multiply with opacity if needed */ + if ((item->opacity != 255) && !item->render_opacity && item->arena->rendermode != RENDERMODE_OUTLINE) { + int x, y; + unsigned int a; + a = item->opacity; + for (y = carea.y0; y < carea.y1; y++) { + unsigned char *d; + d = NR_PIXBLOCK_PX (&mpb) + (y - carea.y0) * mpb.rs; + for (x = carea.x0; x < carea.x1; x++) { + d[0] = NR_PREMUL (d[0], a); + d += 1; + } + } + } + /* Compose rendering pixblock int destination */ + nr_blit_pixblock_pixblock_mask (dpb, &ipb, &mpb); + nr_pixblock_release (&mpb); + } else { + /* Opacity only */ + nr_blit_pixblock_pixblock_alpha (dpb, &ipb, item->opacity); + } + nr_pixblock_release (&ipb); + dpb->empty = FALSE; + } else { + /* Just render */ + state = NR_ARENA_ITEM_VIRTUAL (item, render) (item, &carea, dpb, flags); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + /* Clean up and return error */ + if (dpb != pb) nr_pixblock_release (dpb); + item->state |= NR_ARENA_ITEM_STATE_INVALID; + return item->state; + } + dpb->empty = FALSE; + } + + if (dpb != pb) { + /* Have to blit from cache */ + nr_blit_pixblock_pixblock (pb, dpb); + nr_pixblock_release (dpb); + pb->empty = FALSE; + item->state |= NR_ARENA_ITEM_STATE_IMAGE; + } + } + return item->state | NR_ARENA_ITEM_STATE_RENDER; +} + +unsigned int +nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + nr_return_val_if_fail (item != NULL, NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NR_ARENA_ITEM_STATE_INVALID); + /* we originally short-circuited if the object state included + * NR_ARENA_ITEM_STATE_CLIP (and showed a warning on the console); + * anyone know why we stopped doing so? + */ + nr_return_val_if_fail ((pb->area.x1 - pb->area.x0) >= (area->x1 - area->x0), NR_ARENA_ITEM_STATE_INVALID); + nr_return_val_if_fail ((pb->area.y1 - pb->area.y0) >= (area->y1 - area->y0), NR_ARENA_ITEM_STATE_INVALID); + +#ifdef NR_ARENA_ITEM_VERBOSE + printf ("Invoke render %p: %d %d - %d %d\n", item, area->x0, area->y0, area->x1, area->y1); +#endif + + if (item->visible && nr_rect_l_test_intersect (area, &item->bbox)) { + /* Need render that item */ + if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->clip) + return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS(item))->clip (item, area, pb); + } + + return item->state; +} + +NRArenaItem * +nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + // Sometimes there's no BBOX in item->state, reason unknown (bug 992817); I made this not an assert to remove the warning + if (!(item->state & NR_ARENA_ITEM_STATE_BBOX) || !(item->state & NR_ARENA_ITEM_STATE_PICK)) + return NULL; + + if (!sticky && !(item->visible && item->sensitive)) return NULL; + + // TODO: rewrite using NR::Rect + const double x = p[NR::X]; + const double y = p[NR::Y]; + + if (((x + delta) >= item->bbox.x0) && + ((x - delta) < item->bbox.x1) && + ((y + delta) >= item->bbox.y0) && + ((y - delta) < item->bbox.y1)) { + if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick) + return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->pick (item, p, delta, sticky); + } + + return NULL; +} + +void +nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!(reset & NR_ARENA_ITEM_STATE_INVALID)); + + if (propagate && !item->propagate) item->propagate = TRUE; + + if (item->state & reset) { + item->state &= ~reset; + if (item->parent) { + nr_arena_item_request_update (item->parent, reset, FALSE); + } else { + nr_arena_request_update (item->arena, item); + } + } +} + +void +nr_arena_item_request_render (NRArenaItem *item) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + nr_arena_request_render_rect (item->arena, &item->bbox); +} + +/* Public */ + +NRArenaItem * +nr_arena_item_unparent (NRArenaItem *item) +{ + nr_return_val_if_fail (item != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (item), NULL); + + nr_arena_item_request_render (item); + + if (item->parent) { + nr_arena_item_remove_child (item->parent, item); + } + + return NULL; +} + +void +nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child) +{ + nr_return_if_fail (parent != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (parent)); + nr_return_if_fail (child != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (child)); + nr_return_if_fail (parent->arena == child->arena); + nr_return_if_fail (child->parent == NULL); + nr_return_if_fail (child->prev == NULL); + nr_return_if_fail (child->next == NULL); + + nr_arena_item_add_child (parent, child, nr_arena_item_last_child (parent)); +} + +void +nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform) +{ + NRMatrix const t(transform); + nr_arena_item_set_transform(item, &t); +} + +void +nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform) +{ + const NRMatrix *ms, *md; + + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (!transform && !item->transform) return; + + md = (item->transform) ? item->transform : &NR_MATRIX_IDENTITY; + ms = (transform) ? transform : &NR_MATRIX_IDENTITY; + + if (!NR_MATRIX_DF_TEST_CLOSE (md, ms, NR_EPSILON)) { + nr_arena_item_request_render (item); + if (!transform || nr_matrix_test_identity (transform, NR_EPSILON)) { + /* Set to identity affine */ + if (item->transform) nr_free (item->transform); + item->transform = NULL; + } else { + if (!item->transform) item->transform = nr_new (NRMatrix, 1); + *item->transform = *transform; + } + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_opacity (NRArenaItem *item, double opacity) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + nr_arena_item_request_render (item); + + item->opacity = (unsigned int) (opacity * 255.9999); +} + +void +nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + /* fixme: mess with pick/repick... */ + + item->sensitive = sensitive; +} + +void +nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + item->visible = visible; + + nr_arena_item_request_render (item); +} + +void +nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!clip || NR_IS_ARENA_ITEM (clip)); + + if (clip != item->clip) { + nr_arena_item_request_render (item); + if (item->clip) item->clip = nr_arena_item_detach_unref (item, item->clip); + if (clip) item->clip = nr_arena_item_attach_ref (item, clip, NULL, NULL); + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask) +{ + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + nr_return_if_fail (!mask || NR_IS_ARENA_ITEM (mask)); + + if (mask != item->mask) { + nr_arena_item_request_render (item); + if (item->mask) item->mask = nr_arena_item_detach_unref (item, item->mask); + if (mask) item->mask = nr_arena_item_attach_ref (item, mask, NULL, NULL); + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, TRUE); + } +} + +void +nr_arena_item_set_order (NRArenaItem *item, int order) +{ + NRArenaItem *children, *child, *ref; + int pos; + + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (!item->parent) return; + + children = nr_arena_item_children (item->parent); + + ref = NULL; + pos = 0; + for (child = children; child != NULL; child = child->next) { + if (pos >= order) break; + if (child != item) { + ref = child; + pos += 1; + } + } + + nr_arena_item_set_child_position (item->parent, item, ref); +} + +/* Helpers */ + +NRArenaItem * +nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next) +{ + nr_return_val_if_fail (parent != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL); + nr_return_val_if_fail (child != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL); + nr_return_val_if_fail (child->parent == NULL, NULL); + nr_return_val_if_fail (child->prev == NULL, NULL); + nr_return_val_if_fail (child->next == NULL, NULL); + nr_return_val_if_fail (!prev || NR_IS_ARENA_ITEM (prev), NULL); + nr_return_val_if_fail (!prev || (prev->parent == parent), NULL); + nr_return_val_if_fail (!prev || (prev->next == next), NULL); + nr_return_val_if_fail (!next || NR_IS_ARENA_ITEM (next), NULL); + nr_return_val_if_fail (!next || (next->parent == parent), NULL); + nr_return_val_if_fail (!next || (next->prev == prev), NULL); + + child->parent = parent; + child->prev = prev; + child->next = next; + + if (prev) prev->next = child; + if (next) next->prev = child; + + return child; +} + +NRArenaItem * +nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child) +{ + NRArenaItem *prev, *next; + + nr_return_val_if_fail (parent != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (parent), NULL); + nr_return_val_if_fail (child != NULL, NULL); + nr_return_val_if_fail (NR_IS_ARENA_ITEM (child), NULL); + nr_return_val_if_fail (child->parent == parent, NULL); + + prev = child->prev; + next = child->next; + + child->parent = NULL; + child->prev = NULL; + child->next = NULL; + + if (prev) prev->next = next; + if (next) next->prev = prev; + + return next; +} + +/* + * + * caches + * + */ + +#ifdef arena_item_tile_cache +typedef struct cache_entry { + int key; + double score; + NRArenaItem* owner; + int th,tv; + int prev,next; + NRPixBlock ipb; + bool hasMask; + NRPixBlock mpb; +} cache_entry; + +int hash_max=2048,hash_fill=1024; + +int *keys=NULL; +int nbCch=0; + +int nbEnt=0,maxEnt=0; +cache_entry* entries=NULL; + +//#define tile_cache_stats +#ifdef tile_cache_stats +double hits=0,misses=0; +int hitMissCount=0; +#endif + +int hash_that(NRArenaItem* owner,int th,int tv) +{ + int res=GPOINTER_TO_INT(owner); + res*=17; + res+=th; + res*=59; + res+=tv; + res*=217; + if ( res < 0 ) res=-res; + res%=hash_max; + return res; +} + +bool test_cache(NRArenaItem* owner,int th,int tv,NRPixBlock &ipb,NRPixBlock &mpb,bool &hasMask) +{ + if ( keys == NULL ) { + hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048); + hash_fill=(hash_max*3)/4; + keys=(int*)malloc(hash_max*sizeof(int)); + for (int i=0;i= 0 && cur < nbEnt ) { + if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) { + hasMask=entries[cur].hasMask; + ipb=entries[cur].ipb; + mpb=entries[cur].mpb; +#ifdef tile_cache_stats + hits+=1.0; +#endif + return true; + } + cur=entries[cur].next; + } +#ifdef tile_cache_stats + misses+=1.0; +#endif + return false; +} +void remove_one_cache(int no) +{ + if ( no < 0 || no >= nbEnt ) return; + + nr_pixblock_release(&entries[no].ipb); + if ( entries[no].hasMask ) nr_pixblock_release(&entries[no].mpb); + + if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=entries[no].next; + if ( entries[no].next >= 0 ) entries[entries[no].next].prev=entries[no].prev; + if ( entries[no].prev < 0 ) keys[entries[no].key]=entries[no].next; + entries[no].prev=entries[no].next=entries[no].key=-1; + + if ( no == nbEnt-1 ) { + nbEnt--; + return; + } + entries[no]=entries[--nbEnt]; + if ( entries[no].prev >= 0 ) entries[entries[no].prev].next=no; + if ( entries[no].next >= 0 ) entries[entries[no].next].prev=no; + if ( entries[no].prev < 0 ) keys[entries[no].key]=no; +} +void remove_caches(NRArenaItem* owner) +{ + if ( keys == NULL ) { + hash_max = prefs_get_int_attribute ("options.arenatilescachesize", "value", 2048); + hash_fill=(hash_max*3)/4; + keys=(int*)malloc(hash_max*sizeof(int)); + for (int i=0;i=0;i--) { + if ( entries[i].owner == owner ) { + remove_one_cache(i); + } + } +} +void age_cache(void) +{ + for (int i=0;i 100 ) { + hitMissCount=0; + printf("hit/miss = %f used/total=%i/%i\n",(misses>0.001)?hits/misses:100000.0,nbEnt,hash_max); // localizing ok + } +#endif + int key=hash_that(owner,th,tv); + double nScore=/*activity**/duration; + + if ( keys[key] >= 0 ) { + int cur=keys[key]; + while ( cur >= 0 && cur < nbEnt ) { + if ( entries[cur].owner == owner && entries[cur].th == th && entries[cur].tv == tv ) { + remove_one_cache(cur); + break; + } + cur=entries[cur].next; + } + } + + bool doAdd=false; + if ( nbEnt < hash_fill ) { + doAdd=true; + } else { + double worstS=entries[0].score; + int worstE=0; + for (int i=1;i= maxEnt ) { + maxEnt=2*nbEnt+1; + entries=(cache_entry*)realloc(entries,maxEnt*sizeof(cache_entry)); + } + entries[nbEnt].key=key; + entries[nbEnt].score=nScore; + entries[nbEnt].owner=owner; + entries[nbEnt].th=th; + entries[nbEnt].tv=tv; + entries[nbEnt].prev=entries[nbEnt].next=-1; + entries[nbEnt].ipb=*ipb; + if ( mpb ) { + entries[nbEnt].hasMask=true; + entries[nbEnt].mpb=*mpb; + } else { + entries[nbEnt].hasMask=false; + } + entries[nbEnt].next=keys[key]; + if ( entries[nbEnt].next >= 0 ) entries[entries[nbEnt].next].prev=nbEnt; + keys[key]=nbEnt; + + nbEnt++; + return true; +} +#endif + + + + diff --git a/src/display/nr-arena-item.h b/src/display/nr-arena-item.h new file mode 100644 index 000000000..f43152ff9 --- /dev/null +++ b/src/display/nr-arena-item.h @@ -0,0 +1,188 @@ +#ifndef __NR_ARENA_ITEM_H__ +#define __NR_ARENA_ITEM_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_ITEM (nr_arena_item_get_type ()) +#define NR_ARENA_ITEM(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA_ITEM, NRArenaItem)) +#define NR_IS_ARENA_ITEM(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA_ITEM)) + +#define NR_ARENA_ITEM_VIRTUAL(i,m) (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (i))->m) + +/* + * NRArenaItem state flags + */ + +/* + * NR_ARENA_ITEM_STATE_INVALID + * + * If set or retuned indicates, that given object is in error. + * Calling method has to return immediately, with appropriate + * error flag. + */ + +#define NR_ARENA_ITEM_STATE_INVALID (1 << 0) + + +#define NR_ARENA_ITEM_STATE_BBOX (1 << 1) +#define NR_ARENA_ITEM_STATE_COVERAGE (1 << 2) +#define NR_ARENA_ITEM_STATE_DRAFT (1 << 3) +#define NR_ARENA_ITEM_STATE_RENDER (1 << 4) +#define NR_ARENA_ITEM_STATE_CLIP (1 << 5) +#define NR_ARENA_ITEM_STATE_MASK (1 << 6) +#define NR_ARENA_ITEM_STATE_PICK (1 << 7) +#define NR_ARENA_ITEM_STATE_IMAGE (1 << 8) + +#define NR_ARENA_ITEM_STATE_NONE 0x0000 +#define NR_ARENA_ITEM_STATE_ALL 0x01fe + +#define NR_ARENA_ITEM_STATE(i,s) (NR_ARENA_ITEM (i)->state & (s)) +#define NR_ARENA_ITEM_SET_STATE(i,s) (NR_ARENA_ITEM (i)->state |= (s)) +#define NR_ARENA_ITEM_UNSET_STATE(i,s) (NR_ARENA_ITEM (i)->state &= ~(s)) + +#define NR_ARENA_ITEM_RENDER_NO_CACHE (1 << 0) + +#include +#include +#include +#include +#include "nr-arena-forward.h" + +// My testing shows that disabling cache reduces the amount +// of leaked memory when many documents are loaded one from the other, +// while there's no noticeable change in speed +//#define arena_item_tile_cache + +struct NRGC { + NRGC(NRGC const *p) : parent(p) {} + NRGC const *parent; + NRMatrix transform; +}; + +struct NRArenaItem : public NRObject { + NRArenaItem(); + + NRArena *arena; + NRArenaItem *parent; + NRArenaItem *next; + NRArenaItem *prev; + + /* Item state */ + unsigned int state : 16; + unsigned int propagate : 1; + unsigned int sensitive : 1; + unsigned int visible : 1; + /* Whether items renders opacity itself */ + unsigned int render_opacity : 1; + /* Opacity itself */ + unsigned int opacity : 8; + + /* Key for secondary rendering */ + unsigned int key; + +#ifdef arena_item_tile_cache + bool skipCaching; + double activity; +#endif + + /* BBox in grid coordinates */ + NRRectL bbox; + /* Our affine */ + NRMatrix *transform; + /* Clip item */ + NRArenaItem *clip; + /* Mask item */ + NRArenaItem *mask; + /* Rendered buffer */ + unsigned char *px; + + /* Single data member */ + void *data; + + void init(NRArena *arena) { + this->arena = arena; + } +}; + +struct NRArenaItemClass : public NRObjectClass { + NRArenaItem * (* children) (NRArenaItem *item); + NRArenaItem * (* last_child) (NRArenaItem *item); + void (* add_child) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + void (* remove_child) (NRArenaItem *item, NRArenaItem *child); + void (* set_child_position) (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + + unsigned int (* update) (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); + unsigned int (* render) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); + unsigned int (* clip) (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); + NRArenaItem * (* pick) (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); +}; + +#define NR_ARENA_ITEM_ARENA(ai) (((NRArenaItem *) (ai))->arena) + +NRType nr_arena_item_get_type (void); + +NRArenaItem *nr_arena_item_ref (NRArenaItem *item); +NRArenaItem *nr_arena_item_unref (NRArenaItem *item); + +NRArenaItem *nr_arena_item_children (NRArenaItem *item); +NRArenaItem *nr_arena_item_last_child (NRArenaItem *item); +void nr_arena_item_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +void nr_arena_item_remove_child (NRArenaItem *item, NRArenaItem *child); +void nr_arena_item_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +/* + * Invoke update to given state, if item is inside area + * + * area == NULL is infinite + * gc is PARENT gc for invoke, CHILD gc in corresponding virtual method + * state - requested to state (bitwise or of requested flags) + * reset - reset to state (bitwise or of flags to reset) + */ + +unsigned int nr_arena_item_invoke_update (NRArenaItem *item, NRRectL *area, NRGC *gc, unsigned int state, unsigned int reset); + +unsigned int nr_arena_item_invoke_render(NRArenaItem *item, NRRectL const *area, NRPixBlock *pb, unsigned int flags); + +unsigned int nr_arena_item_invoke_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +NRArenaItem *nr_arena_item_invoke_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +void nr_arena_item_request_update (NRArenaItem *item, unsigned int reset, unsigned int propagate); +void nr_arena_item_request_render (NRArenaItem *item); + +/* Public */ + +NRArenaItem *nr_arena_item_unparent (NRArenaItem *item); + +void nr_arena_item_append_child (NRArenaItem *parent, NRArenaItem *child); + +void nr_arena_item_set_transform(NRArenaItem *item, NR::Matrix const &transform); +void nr_arena_item_set_transform(NRArenaItem *item, NRMatrix const *transform); +void nr_arena_item_set_opacity (NRArenaItem *item, double opacity); +void nr_arena_item_set_sensitive (NRArenaItem *item, unsigned int sensitive); +void nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible); +void nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip); +void nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask); +void nr_arena_item_set_order (NRArenaItem *item, int order); + +/* Helpers */ + +NRArenaItem *nr_arena_item_attach_ref (NRArenaItem *parent, NRArenaItem *child, NRArenaItem *prev, NRArenaItem *next); +NRArenaItem *nr_arena_item_detach_unref (NRArenaItem *parent, NRArenaItem *child); + +#define NR_ARENA_ITEM_SET_DATA(i,v) (((NRArenaItem *) (i))->data = (v)) +#define NR_ARENA_ITEM_GET_DATA(i) (((NRArenaItem *) (i))->data) + +#define NR_ARENA_ITEM_SET_KEY(i,k) (((NRArenaItem *) (i))->key = (k)) +#define NR_ARENA_ITEM_GET_KEY(i) (((NRArenaItem *) (i))->key) + +#endif diff --git a/src/display/nr-arena-shape.cpp b/src/display/nr-arena-shape.cpp new file mode 100644 index 000000000..4fd381ad4 --- /dev/null +++ b/src/display/nr-arena-shape.cpp @@ -0,0 +1,1298 @@ +#define __NR_ARENA_SHAPE_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//int showRuns=0; +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS); + +static void nr_arena_shape_class_init (NRArenaShapeClass *klass); +static void nr_arena_shape_init (NRArenaShape *shape); +static void nr_arena_shape_finalize (NRObject *object); + +static NRArenaItem *nr_arena_shape_children (NRArenaItem *item); +static void nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); +static void nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child); +static void nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref); + +static guint nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset); +static unsigned int nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags); +static guint nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb); +static NRArenaItem *nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int sticky); + +static NRArenaItemClass *shape_parent_class; + +NRType +nr_arena_shape_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ARENA_ITEM, + "NRArenaShape", + sizeof (NRArenaShapeClass), + sizeof (NRArenaShape), + (void (*) (NRObjectClass *)) nr_arena_shape_class_init, + (void (*) (NRObject *)) nr_arena_shape_init); + } + return type; +} + +static void +nr_arena_shape_class_init (NRArenaShapeClass *klass) +{ + NRObjectClass *object_class; + NRArenaItemClass *item_class; + + object_class = (NRObjectClass *) klass; + item_class = (NRArenaItemClass *) klass; + + shape_parent_class = (NRArenaItemClass *) ((NRObjectClass *) klass)->parent; + + object_class->finalize = nr_arena_shape_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; + + item_class->children = nr_arena_shape_children; + item_class->add_child = nr_arena_shape_add_child; + item_class->set_child_position = nr_arena_shape_set_child_position; + item_class->remove_child = nr_arena_shape_remove_child; + item_class->update = nr_arena_shape_update; + item_class->render = nr_arena_shape_render; + item_class->clip = nr_arena_shape_clip; + item_class->pick = nr_arena_shape_pick; +} + +static void +nr_arena_shape_init (NRArenaShape *shape) +{ + shape->curve = NULL; + shape->style = NULL; + shape->paintbox.x0 = shape->paintbox.y0 = 0.0F; + shape->paintbox.x1 = shape->paintbox.y1 = 256.0F; + + nr_matrix_set_identity (&shape->ctm); + shape->fill_painter = NULL; + shape->stroke_painter = NULL; + shape->cached_fill = NULL; + shape->cached_stroke = NULL; + shape->fill_shp = NULL; + shape->stroke_shp = NULL; + + shape->delayed_shp = false; + + shape->approx_bbox.x0 = shape->approx_bbox.y0 = 0; + shape->approx_bbox.x1 = shape->approx_bbox.y1 = 0; + nr_matrix_set_identity(&shape->cached_fctm); + nr_matrix_set_identity(&shape->cached_sctm); + + shape->markers = NULL; +} + +static void +nr_arena_shape_finalize (NRObject *object) +{ + NRArenaShape *shape = (NRArenaShape *) (object); + + if (shape->fill_shp) delete shape->fill_shp; + if (shape->stroke_shp) delete shape->stroke_shp; + if (shape->cached_fill) delete shape->cached_fill; + if (shape->cached_stroke) delete shape->cached_stroke; + if (shape->fill_painter) sp_painter_free (shape->fill_painter); + if (shape->stroke_painter) sp_painter_free (shape->stroke_painter); + + if (shape->style) sp_style_unref (shape->style); + if (shape->curve) sp_curve_unref (shape->curve); + + ((NRObjectClass *) shape_parent_class)->finalize (object); +} + +static NRArenaItem * +nr_arena_shape_children (NRArenaItem *item) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + return shape->markers; +} + +static void +nr_arena_shape_add_child (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (!ref) { + shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_shape_remove_child (NRArenaItem *item, NRArenaItem *child) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + shape->markers = nr_arena_item_detach_unref (item, child); + } + + nr_arena_item_request_update (item, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +nr_arena_shape_set_child_position (NRArenaItem *item, NRArenaItem *child, NRArenaItem *ref) +{ + NRArenaShape *shape = (NRArenaShape *) item; + + if (child->prev) { + nr_arena_item_detach_unref (item, child); + } else { + shape->markers = nr_arena_item_detach_unref (item, child); + } + + if (!ref) { + shape->markers = nr_arena_item_attach_ref (item, child, NULL, shape->markers); + } else { + ref->next = nr_arena_item_attach_ref (item, child, ref, ref->next); + } + + nr_arena_item_request_render (child); +} + +void nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc); +void nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc); +void nr_arena_shape_add_bboxes(NRArenaShape* shape,NRRect &bbox); + +static guint +nr_arena_shape_update (NRArenaItem *item, NRRectL *area, NRGC *gc, guint state, guint reset) +{ + NRRect bbox; + + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + unsigned int beststate = NR_ARENA_ITEM_STATE_ALL; + + unsigned int newstate; + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + newstate = nr_arena_item_invoke_update (child, area, gc, state, reset); + beststate = beststate & newstate; + } + + if (!(state & NR_ARENA_ITEM_STATE_RENDER)) { + /* We do not have to create rendering structures */ + shape->ctm = gc->transform; + if (state & NR_ARENA_ITEM_STATE_BBOX) { + if (shape->curve) { + NRBPath bp; + /* fixme: */ + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + bp.path = shape->curve->bpath; + nr_path_matrix_bbox_union(&bp, gc->transform, &bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + } + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + } + return (state | item->state); + } + + shape->delayed_shp=true; + shape->ctm = gc->transform; + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + + if (shape->curve) { + NRBPath bp; + /* fixme: */ + bbox.x0 = bbox.y0 = NR_HUGE; + bbox.x1 = bbox.y1 = -NR_HUGE; + bp.path = shape->curve->bpath; + nr_path_matrix_bbox_union(&bp, gc->transform, &bbox); + if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) { + float width, scale; + scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + width = MAX (0.125, shape->_stroke.width * scale); + if ( fabs(shape->_stroke.width * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord + bbox.x0-=width; + bbox.x1+=width; + bbox.y0-=width; + bbox.y1+=width; + } + // those pesky miters, now + float miterMax=width*shape->_stroke.mitre_limit; + if ( miterMax > 0.01 ) { + // grunt mode. we should compute the various miters instead (one for each point on the curve) + bbox.x0-=miterMax; + bbox.x1+=miterMax; + bbox.y0-=miterMax; + bbox.y1+=miterMax; + } + } + } else { + } + shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F); + shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F); + shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + if ( area && nr_rect_l_test_intersect (area, &shape->approx_bbox) ) shape->delayed_shp=false; + + /* Release state data */ + if (TRUE || !nr_matrix_test_transform_equal (&gc->transform, &shape->ctm, NR_EPSILON)) { + /* Concept test */ + if (shape->fill_shp) { + delete shape->fill_shp; + shape->fill_shp = NULL; + } + } + if (shape->stroke_shp) { + delete shape->stroke_shp; + shape->stroke_shp = NULL; + } + if (shape->fill_painter) { + sp_painter_free (shape->fill_painter); + shape->fill_painter = NULL; + } + if (shape->stroke_painter) { + sp_painter_free (shape->stroke_painter); + shape->stroke_painter = NULL; + } + + if (!shape->curve || !shape->style) return NR_ARENA_ITEM_STATE_ALL; + if (sp_curve_is_empty (shape->curve)) return NR_ARENA_ITEM_STATE_ALL; + if ( ( shape->_fill.paint.type() == NRArenaShape::Paint::NONE ) && + ( shape->_stroke.paint.type() == NRArenaShape::Paint::NONE ) ) + { + return NR_ARENA_ITEM_STATE_ALL; + } + + /* Build state data */ + if ( shape->delayed_shp ) { + item->bbox=shape->approx_bbox; + } else { + nr_arena_shape_update_stroke(shape,gc); + nr_arena_shape_update_fill(shape,gc); + + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + + shape->approx_bbox.x0 = (gint32)(bbox.x0 - 1.0F); + shape->approx_bbox.y0 = (gint32)(bbox.y0 - 1.0F); + shape->approx_bbox.x1 = (gint32)(bbox.x1 + 1.9999F); + shape->approx_bbox.y1 = (gint32)(bbox.y1 + 1.9999F); + } + + if (nr_rect_d_test_empty (&bbox)) return NR_ARENA_ITEM_STATE_ALL; + + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + nr_arena_request_render_rect (item->arena, &item->bbox); + + item->render_opacity = TRUE; + if ( shape->_fill.paint.type() == NRArenaShape::Paint::SERVER ) { + if (gc && gc->parent) { + shape->fill_painter = sp_paint_server_painter_new (shape->_fill.paint.server(), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &shape->paintbox); + } + item->render_opacity = FALSE; + } + if ( shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER ) { + if (gc && gc->parent) { + shape->stroke_painter = sp_paint_server_painter_new (shape->_stroke.paint.server(), + NR::Matrix (&gc->transform), NR::Matrix (&gc->parent->transform), + &shape->paintbox); + } + item->render_opacity = FALSE; + } + if ( item->render_opacity == TRUE && + shape->_fill.paint.type() != NRArenaShape::Paint::NONE && + shape->_stroke.paint.type() != NRArenaShape::Paint::NONE ) + { + // don't merge item opacity with paint opacity if there is a stroke on the fill + item->render_opacity = FALSE; + } + + if (beststate & NR_ARENA_ITEM_STATE_BBOX) { + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + nr_rect_l_union (&item->bbox, &item->bbox, &child->bbox); + } + } + + return NR_ARENA_ITEM_STATE_ALL; +} + +int matrix_is_isometry(NR::Matrix p) { + NR::Matrix tp; + // transposition + tp[0]=p[0]; + tp[1]=p[2]; + tp[2]=p[1]; + tp[3]=p[3]; + for(int i = 4; i < 6; i++) // shut valgrind up :) + tp[i] = p[i] = 0; + NR::Matrix isom = tp*p; // A^T * A = adjunct? + // Is the adjunct nearly an identity function? + if (isom.is_translation(0.01)) { + // the transformation is an isometry -> no need to recompute + // the uncrossed polygon + if ( p.det() < 0 ) + return -1; + else + return 1; + } + return 0; +} + +void +nr_arena_shape_update_fill(NRArenaShape *shape,NRGC *gc) +{ + shape->delayed_shp = false; + if ((shape->_fill.paint.type() != NRArenaShape::Paint::NONE) && + ((shape->curve->end > 2) || (shape->curve->bpath[1].code == NR_CURVETO)) ) { + if (TRUE || !shape->fill_shp) { + NR::Matrix cached_to_new; + int isometry = 0; + if ( shape->cached_fill ) { + cached_to_new = shape->cached_fctm.inverse()*gc->transform; + isometry = matrix_is_isometry(cached_to_new); + } + if ( isometry == 0 ) { + if ( shape->cached_fill == NULL ) shape->cached_fill=new Shape; + shape->cached_fill->Reset(); + + Path* thePath=new Path; + Shape* theShape=new Shape; + { + NR::Matrix tempMat(gc->transform); + thePath->LoadArtBPath(shape->curve->bpath,tempMat,true); + } + + thePath->Convert(1.0); + + thePath->Fill(theShape, 0); + + if ( shape->_fill.rule == NRArenaShape::EVEN_ODD ) { + shape->cached_fill->ConvertToShape(theShape, fill_oddEven); + // alternatively, this speeds up rendering of oddeven shapes but disables AA :( + //shape->cached_fill->Copy(theShape); + } else { + shape->cached_fill->ConvertToShape(theShape, fill_nonZero); + } + shape->cached_fctm=gc->transform; + delete theShape; + delete thePath; + if ( shape->fill_shp == NULL ) + shape->fill_shp = new Shape; + + shape->fill_shp->Copy(shape->cached_fill); + + } else { + + if ( shape->fill_shp == NULL ) + shape->fill_shp=new Shape; + shape->fill_shp->Reset(shape->cached_fill->numberOfPoints(), + shape->cached_fill->numberOfEdges()); + for (int i = 0; i < shape->cached_fill->numberOfPoints(); i++) + shape->fill_shp->AddPoint(shape->cached_fill->getPoint(i).x * cached_to_new); + if ( isometry == 1 ) { + for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++) + shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).st, + shape->cached_fill->getEdge(i).en); + } else if ( isometry == -1 ) { // need to flip poly. + for (int i = 0; i < shape->cached_fill->numberOfEdges(); i++) + shape->fill_shp->AddEdge(shape->cached_fill->getEdge(i).en, + shape->cached_fill->getEdge(i).st); + } + shape->fill_shp->ForceToPolygon(); + shape->fill_shp->needPointsSorting(); + shape->fill_shp->needEdgesSorting(); + } + } + } +} + +void +nr_arena_shape_update_stroke(NRArenaShape *shape,NRGC* gc) +{ + SPStyle* style = shape->style; + + shape->delayed_shp = false; + + const float scale = NR_MATRIX_DF_EXPANSION (&gc->transform); + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE || + ((shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) && + ( fabs(shape->_stroke.width * scale) > 0.01 ))) { // sinon c'est 0=oon veut pas de bord + + float width = MAX (0.125, shape->_stroke.width * scale); + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) + width = 0.5; // 1 pixel wide, independent of zoom + + NR::Matrix cached_to_new; + + int isometry = 0; + if ( shape->cached_stroke ) { + cached_to_new = shape->cached_sctm.inverse() * gc->transform; + isometry = matrix_is_isometry(cached_to_new); + } + + if ( isometry == 0 ) { + if ( shape->cached_stroke == NULL ) shape->cached_stroke=new Shape; + shape->cached_stroke->Reset(); + Path* thePath = new Path; + Shape* theShape = new Shape; + { + NR::Matrix tempMat(gc->transform); + thePath->LoadArtBPath(shape->curve->bpath, tempMat, true); + } + + if (NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) + thePath->Convert(1.0); + else + thePath->Convert(4.0); // slightly rougher & faster + + if (style->stroke_dash.n_dash && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) { + double dlen = 0.0; + for (int i = 0; i < style->stroke_dash.n_dash; i++) { + dlen += style->stroke_dash.dash[i] * scale; + } + if (dlen >= 1.0) { + NRVpathDash dash; + dash.offset = style->stroke_dash.offset * scale; + dash.n_dash = style->stroke_dash.n_dash; + dash.dash = g_new (double, dash.n_dash); + for (int i = 0; i < dash.n_dash; i++) { + dash.dash[i] = style->stroke_dash.dash[i] * scale; + } + int nbD=dash.n_dash; + float *dashs=(float*)malloc((nbD+1)*sizeof(float)); + while ( dash.offset >= dlen ) dash.offset-=dlen; + dashs[0]=dash.dash[0]; + for (int i=1; iDashPolyline(0.0,0.0,dlen,nbD,dashs,true,dash.offset); + free(dashs); + g_free (dash.dash); + } + } + ButtType butt=butt_straight; + switch(shape->_stroke.cap) { + case NRArenaShape::BUTT_CAP: + butt = butt_straight; + break; + case NRArenaShape::ROUND_CAP: + butt = butt_round; + break; + case NRArenaShape::SQUARE_CAP: + butt = butt_square; + break; + } + JoinType join=join_straight; + switch(shape->_stroke.join) { + case NRArenaShape::MITRE_JOIN: + join = join_pointy; + break; + case NRArenaShape::ROUND_JOIN: + join = join_round; + break; + case NRArenaShape::BEVEL_JOIN: + join = join_straight; + break; + } + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + butt = butt_straight; + join = join_straight; + } + + thePath->Stroke(theShape, false, 0.5*width, join, butt, + 0.5*width*shape->_stroke.mitre_limit); + + + if (NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + // speeds it up, but uses evenodd for the stroke shape (which does not matter for 1-pixel wide outline) + shape->cached_stroke->Copy(theShape); + } else { + shape->cached_stroke->ConvertToShape(theShape, fill_nonZero); + } + + shape->cached_sctm=gc->transform; + delete thePath; + delete theShape; + if ( shape->stroke_shp == NULL ) shape->stroke_shp=new Shape; + + shape->stroke_shp->Copy(shape->cached_stroke); + + } else { + + if ( shape->stroke_shp == NULL ) + shape->stroke_shp=new Shape; + shape->stroke_shp->Reset(shape->cached_stroke->numberOfPoints(), shape->cached_stroke->numberOfEdges()); + for (int i = 0; i < shape->cached_stroke->numberOfPoints(); i++) + shape->stroke_shp->AddPoint(shape->cached_stroke->getPoint(i).x * cached_to_new); + if ( isometry == 1 ) { + for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++) + shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).st, + shape->cached_stroke->getEdge(i).en); + } else if ( isometry == -1 ) { + for (int i = 0; i < shape->cached_stroke->numberOfEdges(); i++) + shape->stroke_shp->AddEdge(shape->cached_stroke->getEdge(i).en, + shape->cached_stroke->getEdge(i).st); + } + shape->stroke_shp->ForceToPolygon(); + shape->stroke_shp->needPointsSorting(); + shape->stroke_shp->needEdgesSorting(); + } + } +} + + +void +nr_arena_shape_add_bboxes(NRArenaShape* shape, NRRect &bbox) +{ + if ( shape->stroke_shp ) { + shape->stroke_shp->CalcBBox(); + shape->stroke_shp->leftX=floor(shape->stroke_shp->leftX); + shape->stroke_shp->rightX=ceil(shape->stroke_shp->rightX); + shape->stroke_shp->topY=floor(shape->stroke_shp->topY); + shape->stroke_shp->bottomY=ceil(shape->stroke_shp->bottomY); + if ( bbox.x0 >= bbox.x1 ) { + if ( shape->stroke_shp->leftX < shape->stroke_shp->rightX ) { + bbox.x0=shape->stroke_shp->leftX; + bbox.x1=shape->stroke_shp->rightX; + } + } else { + if ( shape->stroke_shp->leftX < bbox.x0 ) + bbox.x0=shape->stroke_shp->leftX; + if ( shape->stroke_shp->rightX > bbox.x1 ) + bbox.x1=shape->stroke_shp->rightX; + } + if ( bbox.y0 >= bbox.y1 ) { + if ( shape->stroke_shp->topY < shape->stroke_shp->bottomY ) { + bbox.y0=shape->stroke_shp->topY; + bbox.y1=shape->stroke_shp->bottomY; + } + } else { + if ( shape->stroke_shp->topY < bbox.y0 ) + bbox.y0=shape->stroke_shp->topY; + if ( shape->stroke_shp->bottomY > bbox.y1 ) + bbox.y1=shape->stroke_shp->bottomY; + } + } + if ( shape->fill_shp ) { + shape->fill_shp->CalcBBox(); + shape->fill_shp->leftX=floor(shape->fill_shp->leftX); + shape->fill_shp->rightX=ceil(shape->fill_shp->rightX); + shape->fill_shp->topY=floor(shape->fill_shp->topY); + shape->fill_shp->bottomY=ceil(shape->fill_shp->bottomY); + if ( bbox.x0 >= bbox.x1 ) { + if ( shape->fill_shp->leftX < shape->fill_shp->rightX ) { + bbox.x0=shape->fill_shp->leftX; + bbox.x1=shape->fill_shp->rightX; + } + } else { + if ( shape->fill_shp->leftX < bbox.x0 ) bbox.x0=shape->fill_shp->leftX; + if ( shape->fill_shp->rightX > bbox.x1 ) bbox.x1=shape->fill_shp->rightX; + } + if ( bbox.y0 >= bbox.y1 ) { + if ( shape->fill_shp->topY < shape->fill_shp->bottomY ) { + bbox.y0=shape->fill_shp->topY; + bbox.y1=shape->fill_shp->bottomY; + } + } else { + if ( shape->fill_shp->topY < bbox.y0 ) bbox.y0=shape->fill_shp->topY; + if ( shape->fill_shp->bottomY > bbox.y1 ) bbox.y1=shape->fill_shp->bottomY; + } + } +} +static unsigned int +nr_arena_shape_render (NRArenaItem *item, NRRectL *area, NRPixBlock *pb, unsigned int flags) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return item->state; + if (!shape->style) return item->state; + + if ( shape->delayed_shp ) { + if ( nr_rect_l_test_intersect (area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); +/* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + SPStyle const *style = shape->style; + if ( shape->fill_shp && NR_ARENA_ITEM(shape)->arena->rendermode != RENDERMODE_OUTLINE) { + NRPixBlock m; + guint32 rgba; + + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m,shape->fill_shp); + m.empty = FALSE; + + if (shape->_fill.paint.type() == NRArenaShape::Paint::NONE) { + // do not render fill in any way + } else if (shape->_fill.paint.type() == NRArenaShape::Paint::COLOR) { + if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(), + shape->_fill.opacity * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&shape->_fill.paint.color(), + shape->_fill.opacity); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (shape->_fill.paint.type() == NRArenaShape::Paint::SERVER) { + if (shape->fill_painter) { + nr_arena_render_paintserver_fill (pb, area, shape->fill_painter, shape->_fill.opacity, &m); + } + } + + nr_pixblock_release (&m); + } + + if ( shape->stroke_shp ) { + NRPixBlock m; + guint32 rgba; + + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m, shape->stroke_shp); + m.empty = FALSE; + + if (shape->_stroke.paint.type() == NRArenaShape::Paint::COLOR || + NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + if ( NR_ARENA_ITEM(shape)->arena->rendermode == RENDERMODE_OUTLINE) { + rgba = NR_ARENA_ITEM(shape)->arena->outlinecolor; + } else if ( item->render_opacity ) { + rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(), + shape->_stroke.opacity * + SP_SCALE24_TO_FLOAT (style->opacity.value)); + } else { + rgba = sp_color_get_rgba32_falpha (&shape->_stroke.paint.color(), + shape->_stroke.opacity); + } + nr_blit_pixblock_mask_rgba32 (pb, &m, rgba); + pb->empty = FALSE; + } else if (shape->_stroke.paint.type() == NRArenaShape::Paint::SERVER) { + if (shape->stroke_painter) { + nr_arena_render_paintserver_fill (pb, area, shape->stroke_painter, shape->_stroke.opacity, &m); + } + } + + nr_pixblock_release (&m); + } + + /* Just compose children into parent buffer */ + for (NRArenaItem *child = shape->markers; child != NULL; child = child->next) { + unsigned int ret; + ret = nr_arena_item_invoke_render (child, area, pb, flags); + if (ret & NR_ARENA_ITEM_STATE_INVALID) return ret; + } + + return item->state; +} + +static guint +nr_arena_shape_clip (NRArenaItem *item, NRRectL *area, NRPixBlock *pb) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return item->state; + + if ( shape->delayed_shp ) { + if ( nr_rect_l_test_intersect (area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); + /* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + if ( shape->fill_shp ) { + NRPixBlock m; + + /* fixme: We can OR in one step (Lauris) */ + nr_pixblock_setup_fast (&m, NR_PIXBLOCK_MODE_A8, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_render_shape_mask_or (m,shape->fill_shp); + + for (int y = area->y0; y < area->y1; y++) { + unsigned char *s, *d; + s = NR_PIXBLOCK_PX (&m) + (y - area->y0) * m.rs; + d = NR_PIXBLOCK_PX (pb) + (y - area->y0) * pb->rs; + for (int x = area->x0; x < area->x1; x++) { + *d = NR_A7_NORMALIZED(*s,*d); + d ++; + s ++; + } + } + nr_pixblock_release (&m); + pb->empty = FALSE; + } + + return item->state; +} + +static NRArenaItem * +nr_arena_shape_pick (NRArenaItem *item, NR::Point p, double delta, unsigned int /*sticky*/) +{ + NRArenaShape *shape = NR_ARENA_SHAPE (item); + + if (!shape->curve) return NULL; + if (!shape->style) return NULL; + if ( shape->delayed_shp ) { + NRRectL area; + area.x0=(int)floor(p[NR::X]); + area.x1=(int)ceil(p[NR::X]); + area.y0=(int)floor(p[NR::Y]); + area.y1=(int)ceil(p[NR::Y]); + int idelta = (int)ceil(delta) + 1; + // njh: inset rect + area.x0-=idelta; + area.x1+=idelta; + area.y0-=idelta; + area.y1+=idelta; + if ( nr_rect_l_test_intersect (&area, &item->bbox) ) { + NRGC tempGC(NULL); + tempGC.transform=shape->ctm; + nr_arena_shape_update_stroke(shape,&tempGC); + nr_arena_shape_update_fill(shape,&tempGC); + /* NRRect bbox; + bbox.x0 = bbox.y0 = bbox.x1 = bbox.y1 = 0.0; + nr_arena_shape_add_bboxes(shape,bbox); + item->bbox.x0 = (gint32)(bbox.x0 - 1.0F); + item->bbox.y0 = (gint32)(bbox.y0 - 1.0F); + item->bbox.x1 = (gint32)(bbox.x1 + 1.0F); + item->bbox.y1 = (gint32)(bbox.y1 + 1.0F); + shape->approx_bbox=item->bbox;*/ + } + } + + if (item->state & NR_ARENA_ITEM_STATE_RENDER) { + if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) { + if (shape->fill_shp->PtWinding(p) > 0 ) return item; + } + if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) { + if (shape->stroke_shp->PtWinding(p) > 0 ) return item; + } + if (delta > 1e-3) { + if (shape->fill_shp && (shape->_fill.paint.type() != NRArenaShape::Paint::NONE)) { + if (distanceLessThanOrEqual(shape->fill_shp, p, delta)) return item; + } + if (shape->stroke_shp && (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE)) { + if (distanceLessThanOrEqual(shape->stroke_shp, p, delta)) return item; + } + } + } else { + NRBPath bp; + bp.path = shape->curve->bpath; + double dist = NR_HUGE; + int wind = 0; + nr_path_matrix_point_bbox_wind_distance(&bp, shape->ctm, p, NULL, &wind, &dist, NR_EPSILON); + if (shape->_fill.paint.type() != NRArenaShape::Paint::NONE) { + if (!shape->style->fill_rule.computed) { + if (wind != 0) return item; + } else { + if (wind & 0x1) return item; + } + } + if (shape->_stroke.paint.type() != NRArenaShape::Paint::NONE) { + /* fixme: We do not take stroke width into account here (Lauris) */ + if (dist < delta) return item; + } + } + + return NULL; +} + +/** + * + * Requests a render of the shape, then if the shape is already a curve it + * unrefs the old curve; if the new curve is valid it creates a copy of the + * curve and adds it to the shape. Finally, it requests an update of the + * arena for the shape. + */ +void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve,bool justTrans) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + + if ( justTrans == false ) { + // dirty cached versions + if ( shape->cached_fill ) { + delete shape->cached_fill; + shape->cached_fill=NULL; + } + if ( shape->cached_stroke ) { + delete shape->cached_stroke; + shape->cached_stroke=NULL; + } + } + + nr_arena_item_request_render (NR_ARENA_ITEM (shape)); + + if (shape->curve) { + sp_curve_unref (shape->curve); + shape->curve = NULL; + } + + if (curve) { + shape->curve = curve; + sp_curve_ref (curve); + } + + nr_arena_item_request_update (NR_ARENA_ITEM (shape), NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void NRArenaShape::setFill(SPPaintServer *server) { + _fill.paint.set(server); + _invalidateCachedFill(); +} + +void NRArenaShape::setFill(SPColor const &color) { + _fill.paint.set(color); + _invalidateCachedFill(); +} + +void NRArenaShape::setFillOpacity(double opacity) { + _fill.opacity = opacity; + _invalidateCachedFill(); +} + +void NRArenaShape::setFillRule(NRArenaShape::FillRule rule) { + _fill.rule = rule; + _invalidateCachedFill(); +} + +void NRArenaShape::setStroke(SPPaintServer *server) { + _stroke.paint.set(server); + _invalidateCachedStroke(); +} + +void NRArenaShape::setStroke(SPColor const &color) { + _stroke.paint.set(color); + _invalidateCachedStroke(); +} + +void NRArenaShape::setStrokeOpacity(double opacity) { + _stroke.opacity = opacity; + _invalidateCachedStroke(); +} + +void NRArenaShape::setStrokeWidth(double width) { + _stroke.width = width; + _invalidateCachedStroke(); +} + +void NRArenaShape::setMitreLimit(double limit) { + _stroke.mitre_limit = limit; + _invalidateCachedStroke(); +} + +void NRArenaShape::setLineCap(NRArenaShape::CapType cap) { + _stroke.cap = cap; + _invalidateCachedStroke(); +} + +void NRArenaShape::setLineJoin(NRArenaShape::JoinType join) { + _stroke.join = join; + _invalidateCachedStroke(); +} + +/** nr_arena_shape_set_style + * + * Unrefs any existing style and ref's to the given one, then requests an update of the arena + */ +void +nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + + if (style) sp_style_ref (style); + if (shape->style) sp_style_unref (shape->style); + shape->style = style; + + switch (style->fill.type) { + case SP_PAINT_TYPE_NONE: { + shape->setFill(NULL); + break; + } + case SP_PAINT_TYPE_COLOR: { + shape->setFill(style->fill.value.color); + break; + } + case SP_PAINT_TYPE_PAINTSERVER: { + shape->setFill(style->fill.value.paint.server); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setFillOpacity(SP_SCALE24_TO_FLOAT(style->fill_opacity.value)); + switch (style->fill_rule.computed) { + case SP_WIND_RULE_EVENODD: { + shape->setFillRule(NRArenaShape::EVEN_ODD); + break; + } + case SP_WIND_RULE_NONZERO: { + shape->setFillRule(NRArenaShape::NONZERO); + break; + } + default: { + g_assert_not_reached(); + } + } + + switch (style->stroke.type) { + case SP_PAINT_TYPE_NONE: { + shape->setStroke(NULL); + break; + } + case SP_PAINT_TYPE_COLOR: { + shape->setStroke(style->stroke.value.color); + break; + } + case SP_PAINT_TYPE_PAINTSERVER: { + shape->setStroke(style->stroke.value.paint.server); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setStrokeWidth(style->stroke_width.computed); + shape->setStrokeOpacity(SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)); + switch (style->stroke_linecap.computed) { + case SP_STROKE_LINECAP_ROUND: { + shape->setLineCap(NRArenaShape::ROUND_CAP); + break; + } + case SP_STROKE_LINECAP_SQUARE: { + shape->setLineCap(NRArenaShape::SQUARE_CAP); + break; + } + case SP_STROKE_LINECAP_BUTT: { + shape->setLineCap(NRArenaShape::BUTT_CAP); + break; + } + default: { + g_assert_not_reached(); + } + } + switch (style->stroke_linejoin.computed) { + case SP_STROKE_LINEJOIN_ROUND: { + shape->setLineJoin(NRArenaShape::ROUND_JOIN); + break; + } + case SP_STROKE_LINEJOIN_BEVEL: { + shape->setLineJoin(NRArenaShape::BEVEL_JOIN); + break; + } + case SP_STROKE_LINEJOIN_MITER: { + shape->setLineJoin(NRArenaShape::MITRE_JOIN); + break; + } + default: { + g_assert_not_reached(); + } + } + shape->setMitreLimit(style->stroke_miterlimit.value); + + nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void +nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox) +{ + g_return_if_fail (shape != NULL); + g_return_if_fail (NR_IS_ARENA_SHAPE (shape)); + g_return_if_fail (pbox != NULL); + + if ((pbox->x0 < pbox->x1) && (pbox->y0 < pbox->y1)) { + shape->paintbox = *pbox; + } else { + /* fixme: We kill warning, although not sure what to do here (Lauris) */ + shape->paintbox.x0 = shape->paintbox.y0 = 0.0F; + shape->paintbox.x1 = shape->paintbox.y1 = 256.0F; + } + + nr_arena_item_request_update(shape, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +void NRArenaShape::setPaintBox(NR::Rect const &pbox) +{ + paintbox.x0 = pbox.min()[NR::X]; + paintbox.y0 = pbox.min()[NR::Y]; + paintbox.x1 = pbox.max()[NR::X]; + paintbox.y1 = pbox.max()[NR::Y]; + + nr_arena_item_request_update(this, NR_ARENA_ITEM_STATE_ALL, FALSE); +} + +static void +shape_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven) +{ + if ( st >= en ) return; + if ( vst < 0 ) vst=0; + if ( vst > 1 ) vst=1; + if ( ven < 0 ) ven=0; + if ( ven > 1 ) ven=1; + float sv=vst; + float dv=ven-vst; + int len=en-st; + unsigned char* d=(unsigned char*)dest.buffer; + d+=(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( vst > 0.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = 255; + d += 1; + len -= 1; + } + } else { + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + unsigned int da; + /* Draw */ + da = NR_A7(c0_24,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + d += 1; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + sv*=256; + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + unsigned int da; + /* Draw */ + da = NR_A7(c0_24,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=16777216; + dv*=16777216; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca, da; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + da = NR_A7(ca,d[0]); + d[0] = NR_PREMUL_SINGLE(da); + d += 1; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_shape_mask_or (NRPixBlock &m,Shape* theS) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + + if ( il >= m.area.x1 || ir <= m.area.x0 || it >= m.area.y1 || ib <= m.area.y0 ) return; + if ( il < m.area.x0 ) il=m.area.x0; + if ( it < m.area.y0 ) it=m.area.y0; + if ( ir > m.area.x1 ) ir=m.area.x1; + if ( ib > m.area.y1 ) ib=m.area.y1; + + // version par FloatLigne + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,1.0); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + theS->QuickScan(curY,curPt,((float)(y+1)),theI,1.0); + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndQuickRaster(); + delete theI; + delete theIL; + + /* // version par BitLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),0.25); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25); + theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25); + theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25); + theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25); + } else { + theS->Scan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25); + theS->Scan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25); + theS->Scan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25); + theS->Scan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25); + } + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL;*/ + +/* // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY,curPt,1.0); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->DirectQuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)m.data.px; + if ( m.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)m.data.p; + uint32_t* ligStart=((uint32_t*)(mdata+((il-m.area.x0)+m.rs*(it-m.area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],false,0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],false,0.25); + } else { + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],true,0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],true,0.25); + } + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,NULL,shape_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+m.rs)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL;*/ +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-arena-shape.h b/src/display/nr-arena-shape.h new file mode 100644 index 000000000..f7991bf4d --- /dev/null +++ b/src/display/nr-arena-shape.h @@ -0,0 +1,213 @@ +#ifndef __NR_ARENA_SHAPE_H__ +#define __NR_ARENA_SHAPE_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA_SHAPE (nr_arena_shape_get_type ()) +#define NR_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_CAST ((obj), NR_TYPE_ARENA_SHAPE, NRArenaShape)) +#define NR_IS_ARENA_SHAPE(obj) (NR_CHECK_INSTANCE_TYPE ((obj), NR_TYPE_ARENA_SHAPE)) + +//#include + +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "forward.h" +#include "sp-paint-server.h" +#include "nr-arena-item.h" + +#include "../color.h" + +#include "../livarot/Shape.h" + +NRType nr_arena_shape_get_type (void); + +struct NRArenaShape : public NRArenaItem { + class Paint { + public: + enum Type { + NONE, + COLOR, + SERVER + }; + + Paint() : _type(NONE), _server(NULL) { + sp_color_set_rgb_rgba32(&_color, 0); + } + Paint(Paint const &p) { _assign(p); } + ~Paint() { clear(); } + + Type type() const { return _type; } + SPPaintServer *server() const { return _server; } + SPColor const &color() const { return _color; } + + Paint &operator=(Paint const &p) { + set(p); + return *this; + } + + void set(Paint const &p) { + clear(); + _assign(p); + } + void set(SPColor const &color) { + clear(); + _type = COLOR; + sp_color_copy(&_color, &color); + } + void set(SPPaintServer *server) { + clear(); + if (server) { + _type = SERVER; + _server = server; + sp_object_ref(_server, NULL); + } + } + void clear() { + if ( _type == SERVER ) { + sp_object_unref(_server, NULL); + _server = NULL; + } + _type = NONE; + } + + private: + Type _type; + SPColor _color; + SPPaintServer *_server; + + void _assign(Paint const &p) { + _type = p._type; + _server = p._server; + sp_color_copy(&_color, &p._color); + if (_server) { + sp_object_ref(_server, NULL); + } + } + }; + + enum FillRule { + EVEN_ODD, + NONZERO + }; + + enum CapType { + ROUND_CAP, + SQUARE_CAP, + BUTT_CAP + }; + + enum JoinType { + ROUND_JOIN, + BEVEL_JOIN, + MITRE_JOIN + }; + + /* Shape data */ + SPCurve *curve; + SPStyle *style; + NRRect paintbox; + /* State data */ + NR::Matrix ctm; + + SPPainter *fill_painter; + SPPainter *stroke_painter; + // the 2 cached polygons, for rasterizations uses + Shape *fill_shp; + Shape *stroke_shp; + // delayed_shp=true means the *_shp polygons are not computed yet + // they'll be computed on demand in *_render(), *_pick() or *_clip() + // the goal is to not uncross polygons that are outside the viewing region + bool delayed_shp; + // approximate bounding box, for the case when the polygons have been delayed + NRRectL approx_bbox; + // cache for transformations: cached_fill and cached_stroke are + // polygons computed for the cached_fctm and cache_sctm respectively + // when the transformation changes interactively (tracked by the + // SP_OBJECT_USER_MODIFIED_FLAG_B), we check if it's an isometry wrt + // the cached ctm. if it's an isometry, just apply it to the cached + // polygon to get the *_shp polygon. Otherwise, recompute so this + // works fine for translation and rotation, but not scaling and + // skewing + NR::Matrix cached_fctm; + NR::Matrix cached_sctm; + + Shape *cached_fill; + Shape *cached_stroke; + /* Markers */ + NRArenaItem *markers; + + static NRArenaShape *create(NRArena *arena) { + NRArenaShape *obj=reinterpret_cast(nr_object_new(NR_TYPE_ARENA_SHAPE)); + obj->init(arena); + return obj; + } + + void setFill(SPPaintServer *server); + void setFill(SPColor const &color); + void setFillOpacity(double opacity); + void setFillRule(FillRule rule); + + void setStroke(SPPaintServer *server); + void setStroke(SPColor const &color); + void setStrokeOpacity(double opacity); + void setStrokeWidth(double width); + void setLineCap(CapType cap); + void setLineJoin(JoinType join); + void setMitreLimit(double limit); + + void setPaintBox(NR::Rect const &pbox); + + void _invalidateCachedFill() { + if (cached_fill) { + delete cached_fill; + cached_fill = NULL; + } + } + void _invalidateCachedStroke() { + if (cached_stroke) { + delete cached_stroke; + cached_stroke = NULL; + } + } + + struct Style { + Style() : opacity(0.0) {} + Paint paint; + double opacity; + }; + struct FillStyle : public Style { + FillStyle() : rule(EVEN_ODD) {} + FillRule rule; + } _fill; + struct StrokeStyle : public Style { + StrokeStyle() + : cap(ROUND_CAP), join(ROUND_JOIN), + width(0.0), mitre_limit(0.0) + {} + + CapType cap; + JoinType join; + double width; + double mitre_limit; + } _stroke; +}; + +struct NRArenaShapeClass { + NRArenaItemClass parent_class; +}; + +void nr_arena_shape_set_path(NRArenaShape *shape, SPCurve *curve, bool justTrans); +void nr_arena_shape_set_style (NRArenaShape *shape, SPStyle *style); +void nr_arena_shape_set_paintbox (NRArenaShape *shape, const NRRect *pbox); + +#endif diff --git a/src/display/nr-arena.cpp b/src/display/nr-arena.cpp new file mode 100644 index 000000000..f54bfbb90 --- /dev/null +++ b/src/display/nr-arena.cpp @@ -0,0 +1,131 @@ +#define __NR_ARENA_C__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "nr-arena-item.h" +#include "nr-arena.h" +#include + +static void nr_arena_class_init (NRArenaClass *klass); +static void nr_arena_init (NRArena *arena); +static void nr_arena_finalize (NRObject *object); + +static NRActiveObjectClass *parent_class; + +NRType +nr_arena_get_type (void) +{ + static NRType type = 0; + if (!type) { + type = nr_object_register_type (NR_TYPE_ACTIVE_OBJECT, + "NRArena", + sizeof (NRArenaClass), + sizeof (NRArena), + (void (*) (NRObjectClass *)) nr_arena_class_init, + (void (*) (NRObject *)) nr_arena_init); + } + return type; +} + +static void +nr_arena_class_init (NRArenaClass *klass) +{ + NRObjectClass *object_class = (NRObjectClass *) klass; + + parent_class = (NRActiveObjectClass *) (((NRObjectClass *) klass)->parent); + + object_class->finalize = nr_arena_finalize; + object_class->cpp_ctor = NRObject::invoke_ctor; +} + +static void +nr_arena_init (NRArena *arena) +{ + arena->delta = 0; // to be set by desktop from prefs + arena->rendermode = RENDERMODE_NORMAL; // default is normal render + arena->outlinecolor = 0xff; // black; to be set by desktop from bg color +} + +static void +nr_arena_finalize (NRObject *object) +{ + ((NRObjectClass *) (parent_class))->finalize (object); +} + +void +nr_arena_request_update (NRArena *arena, NRArenaItem *item) +{ + NRActiveObject *aobject = (NRActiveObject *) arena; + + nr_return_if_fail (arena != NULL); + nr_return_if_fail (NR_IS_ARENA (arena)); + nr_return_if_fail (item != NULL); + nr_return_if_fail (NR_IS_ARENA_ITEM (item)); + + if (aobject->callbacks) { + for (unsigned int i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener = aobject->callbacks->listeners + i; + NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector; + if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_update) { + avector->request_update (arena, item, listener->data); + } + } + } +} + +void +nr_arena_request_render_rect (NRArena *arena, NRRectL *area) +{ + NRActiveObject *aobject = (NRActiveObject *) arena; + + nr_return_if_fail (arena != NULL); + nr_return_if_fail (NR_IS_ARENA (arena)); + nr_return_if_fail (area != NULL); + + if (aobject->callbacks && area && !nr_rect_l_test_empty (area)) { + for (unsigned int i = 0; i < aobject->callbacks->length; i++) { + NRObjectListener *listener = aobject->callbacks->listeners + i; + NRArenaEventVector *avector = (NRArenaEventVector *) listener->vector; + if ((listener->size >= sizeof (NRArenaEventVector)) && avector->request_render) { + avector->request_render (arena, area, listener->data); + } + } + } +} + +void +nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask) +{ + NRPixBlock cb, cb_opa; + nr_pixblock_setup_fast (&cb, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE); + nr_pixblock_setup_fast (&cb_opa, NR_PIXBLOCK_MODE_R8G8B8A8N, area->x0, area->y0, area->x1, area->y1, TRUE); + + /* Need separate gradient buffer (lauris)*/ + // do the filling + painter->fill (painter, &cb); + cb.empty = FALSE; + + // do the fill-opacity and mask composite + if (opacity < 1.0) { + nr_blit_pixblock_pixblock_alpha (&cb_opa, &cb, (int) floor (255 * opacity)); + cb_opa.empty = FALSE; + nr_blit_pixblock_pixblock_mask (pb, &cb_opa, mask); + } else { + nr_blit_pixblock_pixblock_mask (pb, &cb, mask); + } + + pb->empty = FALSE; + + nr_pixblock_release (&cb); + nr_pixblock_release (&cb_opa); +} diff --git a/src/display/nr-arena.h b/src/display/nr-arena.h new file mode 100644 index 000000000..245ce14db --- /dev/null +++ b/src/display/nr-arena.h @@ -0,0 +1,57 @@ +#ifndef __NR_ARENA_H__ +#define __NR_ARENA_H__ + +/* + * RGBA display list system for inkscape + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define NR_TYPE_ARENA (nr_arena_get_type ()) +#define NR_ARENA(o) (NR_CHECK_INSTANCE_CAST ((o), NR_TYPE_ARENA, NRArena)) +#define NR_IS_ARENA(o) (NR_CHECK_INSTANCE_TYPE ((o), NR_TYPE_ARENA)) + +#include +#include +#include "nr-arena-forward.h" +#include "sp-paint-server.h" + +NRType nr_arena_get_type (void); + +struct NRArenaEventVector { + NRObjectEventVector parent; + void (* request_update) (NRArena *arena, NRArenaItem *item, void *data); + void (* request_render) (NRArena *arena, NRRectL *area, void *data); +}; + +enum { + RENDERMODE_NORMAL, + RENDERMODE_NOAA, + RENDERMODE_OUTLINE +}; + +struct NRArena : public NRActiveObject { + static NRArena *create() { + return reinterpret_cast(nr_object_new(NR_TYPE_ARENA)); + } + + double delta; + int rendermode; + guint32 outlinecolor; +}; + +struct NRArenaClass : public NRActiveObjectClass { +}; + +void nr_arena_request_update (NRArena *arena, NRArenaItem *item); +void nr_arena_request_render_rect (NRArena *arena, NRRectL *area); + +void nr_arena_render_paintserver_fill (NRPixBlock *pb, NRRectL *area, SPPainter *painter, float opacity, NRPixBlock *mask); + +#endif diff --git a/src/display/nr-gradient-gpl.cpp b/src/display/nr-gradient-gpl.cpp new file mode 100644 index 000000000..54a33cfdb --- /dev/null +++ b/src/display/nr-gradient-gpl.cpp @@ -0,0 +1,306 @@ +#define __NR_GRADIENT_C__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "nr-gradient-gpl.h" + +#define noNR_USE_GENERIC_RENDERER + +#define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1) +#define NRG_2MASK ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1) + +static void nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m); +static void nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs); +static void nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb); + +NRRenderer * +nr_lgradient_renderer_setup (NRLGradientRenderer *lgr, + const unsigned char *cv, + unsigned int spread, + const NRMatrix *gs2px, + float x0, float y0, + float x1, float y1) +{ + NRMatrix n2gs, n2px, px2n; + + lgr->renderer.render = nr_lgradient_render_block; + + lgr->vector = cv; + lgr->spread = spread; + + n2gs.c[0] = x1 - x0; + n2gs.c[1] = y1 - y0; + n2gs.c[2] = y1 - y0; + n2gs.c[3] = x0 - x1; + n2gs.c[4] = x0; + n2gs.c[5] = y0; + + nr_matrix_multiply (&n2px, &n2gs, gs2px); + nr_matrix_invert (&px2n, &n2px); + + lgr->x0 = n2px.c[4] - 0.5; + lgr->y0 = n2px.c[5] - 0.5; + lgr->dx = px2n.c[0] * NR_GRADIENT_VECTOR_LENGTH; + lgr->dy = px2n.c[2] * NR_GRADIENT_VECTOR_LENGTH; + + return (NRRenderer *) lgr; +} + +static void +nr_lgradient_render_block (NRRenderer *r, NRPixBlock *pb, NRPixBlock *m) +{ + NRLGradientRenderer *lgr; + int width, height; + + lgr = (NRLGradientRenderer *) r; + + width = pb->area.x1 - pb->area.x0; + height = pb->area.y1 - pb->area.y0; + +#ifdef NR_USE_GENERIC_RENDERER + nr_lgradient_render_generic (lgr, pb); +#else + if (pb->empty) { + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + nr_lgradient_render_R8G8B8A8N_EMPTY (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + nr_lgradient_render_generic (lgr, pb); + break; + default: + break; + } + } else { + switch (pb->mode) { + case NR_PIXBLOCK_MODE_A8: + nr_lgradient_render_generic (lgr, pb); + break; + case NR_PIXBLOCK_MODE_R8G8B8: + nr_lgradient_render_R8G8B8 (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8N: + nr_lgradient_render_R8G8B8A8N (lgr, NR_PIXBLOCK_PX (pb), pb->area.x0, pb->area.y0, width, height, pb->rs); + break; + case NR_PIXBLOCK_MODE_R8G8B8A8P: + nr_lgradient_render_generic (lgr, pb); + break; + default: + break; + } + } +#endif +} + +static void +nr_lgradient_render_R8G8B8A8N_EMPTY (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + double pos; + + for (y = 0; y < height; y++) { + const unsigned char *s; + unsigned char *d; + int idx; + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + if (lgr->spread == NR_GRADIENT_SPREAD_PAD) { + for (x = 0; x < width; x++) { + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } else if (lgr->spread == NR_GRADIENT_SPREAD_REFLECT) { + for (x = 0; x < width; x++) { + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } else { + for (x = 0; x < width; x++) { + idx = (int) ((long long) pos & NRG_MASK); + s = lgr->vector + 4 * idx; + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + d += 4; + pos += lgr->dx; + } + } + } +} + +static void +nr_lgradient_render_R8G8B8A8N (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + unsigned char *d; + double pos; + + for (y = 0; y < height; y++) { + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + unsigned int ca; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + /* Full composition */ + s = lgr->vector + 4 * idx; + if (s[3] == 255) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + } else if (s[3] != 0) { + ca = NR_A7(s[3],d[3]); + d[0] = NR_COMPOSENNN_A7 (s[0], s[3], d[0], d[3], ca); + d[1] = NR_COMPOSENNN_A7 (s[1], s[3], d[1], d[3], ca); + d[2] = NR_COMPOSENNN_A7 (s[2], s[3], d[2], d[3], ca); + d[3] = NR_PREMUL_SINGLE(ca); + } + d += 4; + pos += lgr->dx; + } + } +} + +static void +nr_lgradient_render_R8G8B8 (NRLGradientRenderer *lgr, unsigned char *px, int x0, int y0, int width, int height, int rs) +{ + int x, y; + unsigned char *d; + double pos; + + for (y = 0; y < height; y++) { + d = px + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + /* Full composition */ + s = lgr->vector + 4 * idx; + d[0] = NR_COMPOSEN11 (s[0], s[3], d[0]); + d[1] = NR_COMPOSEN11 (s[1], s[3], d[1]); + d[2] = NR_COMPOSEN11 (s[2], s[3], d[2]); + d += 3; + pos += lgr->dx; + } + } +} + +static void +nr_lgradient_render_generic (NRLGradientRenderer *lgr, NRPixBlock *pb) +{ + int x, y; + unsigned char *d; + double pos; + int bpp; + NRPixBlock spb; + int x0, y0, width, height, rs; + + x0 = pb->area.x0; + y0 = pb->area.y0; + width = pb->area.x1 - pb->area.x0; + height = pb->area.y1 - pb->area.y0; + rs = pb->rs; + + nr_pixblock_setup_extern (&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1, + (unsigned char *) lgr->vector, + 4 * NR_GRADIENT_VECTOR_LENGTH, + 0, 0); + bpp = (pb->mode == NR_PIXBLOCK_MODE_A8) ? 1 : (pb->mode == NR_PIXBLOCK_MODE_R8G8B8) ? 3 : 4; + + for (y = 0; y < height; y++) { + d = NR_PIXBLOCK_PX (pb) + y * rs; + pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx; + for (x = 0; x < width; x++) { + int idx; + const unsigned char *s; + switch (lgr->spread) { + case NR_GRADIENT_SPREAD_PAD: + idx = (int) CLAMP (pos, 0, (double) NRG_MASK); + break; + case NR_GRADIENT_SPREAD_REFLECT: + idx = (int) ((long long) pos & NRG_2MASK); + if (idx > NRG_MASK) idx = NRG_2MASK - idx; + break; + case NR_GRADIENT_SPREAD_REPEAT: + idx = (int) ((long long) pos & NRG_MASK); + break; + default: + idx = 0; + break; + } + s = lgr->vector + 4 * idx; + nr_compose_pixblock_pixblock_pixel (pb, d, &spb, s); + d += bpp; + pos += lgr->dx; + } + } + + nr_pixblock_release (&spb); +} + diff --git a/src/display/nr-gradient-gpl.h b/src/display/nr-gradient-gpl.h new file mode 100644 index 000000000..db7d9bb54 --- /dev/null +++ b/src/display/nr-gradient-gpl.h @@ -0,0 +1,41 @@ +#ifndef __NR_GRADIENT_GPL_H__ +#define __NR_GRADIENT_GPL_H__ + +/* + * Pixel buffer rendering library + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 2001-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* + * Here is GPL code, unlike other libnr wihich is public domain + */ + +#include + +/* Linear */ + +struct NRLGradientRenderer { + NRRenderer renderer; + const unsigned char *vector; + unsigned int spread; + double x0, y0; + double dx, dy; +}; + +NRRenderer *nr_lgradient_renderer_setup (NRLGradientRenderer *lgr, + const unsigned char *cv, + unsigned int spread, + const NRMatrix *gs2px, + float x0, float y0, + float x1, float y1); + + + +#endif diff --git a/src/display/nr-plain-stuff-gdk.cpp b/src/display/nr-plain-stuff-gdk.cpp new file mode 100644 index 000000000..d5b43f4ea --- /dev/null +++ b/src/display/nr-plain-stuff-gdk.cpp @@ -0,0 +1,46 @@ +#define __NR_PLAIN_STUFF_GDK_C__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include "nr-plain-stuff.h" +#include "nr-plain-stuff-gdk.h" + +void +nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba) +{ + NRPixBlock pb; + + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, w, h, FALSE); + + nr_render_rgba32_rgb (NR_PIXBLOCK_PX (&pb), w, h, pb.rs, x, y, rgba); + gdk_draw_rgb_image (drawable, gc, x, y, w, h, GDK_RGB_DITHER_MAX, NR_PIXBLOCK_PX (&pb), pb.rs); + + nr_pixblock_release (&pb); +} + +void +nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h) +{ + for (gint yy = y; yy < y + h; yy += 64) { + for (gint xx = x; xx < x + w; xx += 64) { + NRPixBlock pb; + gint ex = MIN (xx + 64, x + w); + gint ey = MIN (yy + 64, y + h); + nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8, xx, yy, ex, ey, FALSE); + nr_pixblock_render_gray_noise (&pb, NULL); + gdk_draw_rgb_image (drawable, gc, xx, yy, ex - xx, ey - yy, GDK_RGB_DITHER_NONE, NR_PIXBLOCK_PX (&pb), pb.rs); + nr_pixblock_release (&pb); + } + } +} + diff --git a/src/display/nr-plain-stuff-gdk.h b/src/display/nr-plain-stuff-gdk.h new file mode 100644 index 000000000..7c83792a8 --- /dev/null +++ b/src/display/nr-plain-stuff-gdk.h @@ -0,0 +1,32 @@ +#ifndef __NR_PLAIN_STUFF_GDK_H__ +#define __NR_PLAIN_STUFF_GDK_H__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include + +void nr_gdk_draw_rgba32_solid (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h, guint32 rgba); + +void nr_gdk_draw_gray_garbage (GdkDrawable *drawable, GdkGC *gc, gint x, gint y, gint w, gint h); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-plain-stuff.cpp b/src/display/nr-plain-stuff.cpp new file mode 100644 index 000000000..af6e002ec --- /dev/null +++ b/src/display/nr-plain-stuff.cpp @@ -0,0 +1,94 @@ +#define __NR_PLAIN_STUFF_C__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include +#include +#include "nr-plain-stuff.h" + +#define NR_DEFAULT_CHECKERSIZEP2 2 +#define NR_DEFAULT_CHECKERCOLOR0 0xbfbfbfff +#define NR_DEFAULT_CHECKERCOLOR1 0x808080ff + +void +nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff) +{ + g_return_if_fail (px != NULL); + + nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, NR_DEFAULT_CHECKERCOLOR0, NR_DEFAULT_CHECKERCOLOR1, NR_DEFAULT_CHECKERSIZEP2); +} + +void +nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2) +{ + gint x, y, m; + guint r0, g0, b0; + guint r1, g1, b1; + + g_return_if_fail (px != NULL); + g_return_if_fail (sizep2 >= 0); + g_return_if_fail (sizep2 <= 8); + + xoff &= 0x1ff; + yoff &= 0x1ff; + m = 0x1 << sizep2; + r0 = NR_RGBA32_R (c0); + g0 = NR_RGBA32_G (c0); + b0 = NR_RGBA32_B (c0); + r1 = NR_RGBA32_R (c1); + g1 = NR_RGBA32_G (c1); + b1 = NR_RGBA32_B (c1); + + for (y = 0; y < h; y++) { + guchar *p; + p = px; + for (x = 0; x < w; x++) { + if (((x + xoff) ^ (y + yoff)) & m) { + *p++ = r0; + *p++ = g0; + *p++ = b0; + } else { + *p++ = r1; + *p++ = g1; + *p++ = b1; + } + } + px += rs; + } +} + +void +nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c) +{ + guint32 c0, c1; + gint a, r, g, b, cr, cg, cb; + + g_return_if_fail (px != NULL); + + r = NR_RGBA32_R (c); + g = NR_RGBA32_G (c); + b = NR_RGBA32_B (c); + a = NR_RGBA32_A (c); + + cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR0)); + cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR0)); + cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR0)); + c0 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff; + + cr = NR_COMPOSEN11 (r, a, NR_RGBA32_R (NR_DEFAULT_CHECKERCOLOR1)); + cg = NR_COMPOSEN11 (g, a, NR_RGBA32_G (NR_DEFAULT_CHECKERCOLOR1)); + cb = NR_COMPOSEN11 (b, a, NR_RGBA32_B (NR_DEFAULT_CHECKERCOLOR1)); + c1 = (cr << 24) | (cg << 16) | (cb << 8) | 0xff; + + nr_render_checkerboard_rgb_custom (px, w, h, rs, xoff, yoff, c0, c1, NR_DEFAULT_CHECKERSIZEP2); +} + diff --git a/src/display/nr-plain-stuff.h b/src/display/nr-plain-stuff.h new file mode 100644 index 000000000..c568f38a6 --- /dev/null +++ b/src/display/nr-plain-stuff.h @@ -0,0 +1,33 @@ +#ifndef __NR_PLAIN_STUFF_H__ +#define __NR_PLAIN_STUFF_H__ + +/* + * Miscellaneous simple rendering utilities + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * Released under GNU GPL + */ + +#include + +void nr_render_checkerboard_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff); +void nr_render_checkerboard_rgb_custom (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c0, guint32 c1, gint sizep2); + +void nr_render_rgba32_rgb (guchar *px, gint w, gint h, gint rs, gint xoff, gint yoff, guint32 c); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sodipodi-ctrl.cpp b/src/display/sodipodi-ctrl.cpp new file mode 100644 index 000000000..71786fd96 --- /dev/null +++ b/src/display/sodipodi-ctrl.cpp @@ -0,0 +1,494 @@ +#define INKSCAPE_CTRL_C + +/* + * SPCtrl + * + * We render it by hand to reduce allocing/freeing svps & to get clean + * (non-aa) images + * + */ + +#include "sp-canvas-util.h" +#include "display-forward.h" +#include "sodipodi-ctrl.h" + +enum { + ARG_0, + ARG_SHAPE, + ARG_MODE, + ARG_ANCHOR, + ARG_SIZE, + ARG_FILLED, + ARG_FILL_COLOR, + ARG_STROKED, + ARG_STROKE_COLOR, + ARG_PIXBUF +}; + + +static void sp_ctrl_class_init (SPCtrlClass *klass); +static void sp_ctrl_init (SPCtrl *ctrl); +static void sp_ctrl_destroy (GtkObject *object); +static void sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); + +static void sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static double sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrl_get_type (void) +{ + static GtkType ctrl_type = 0; + if (!ctrl_type) { + static const GTypeInfo ctrl_info = { + sizeof (SPCtrlClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_ctrl_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPCtrl), + 0, /* n_preallocs */ + (GInstanceInitFunc) sp_ctrl_init, + NULL + }; + ctrl_type = g_type_register_static (SP_TYPE_CANVAS_ITEM, "SPCtrl", &ctrl_info, (GTypeFlags)0); + } + return ctrl_type; +} + +static void +sp_ctrl_class_init (SPCtrlClass *klass) +{ + GtkObjectClass *object_class; + SPCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) klass; + item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass *)gtk_type_class (sp_canvas_item_get_type ()); + + gtk_object_add_arg_type ("SPCtrl::shape", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_SHAPE); + gtk_object_add_arg_type ("SPCtrl::mode", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_MODE); + gtk_object_add_arg_type ("SPCtrl::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR); + gtk_object_add_arg_type ("SPCtrl::size", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_SIZE); + gtk_object_add_arg_type ("SPCtrl::pixbuf", GTK_TYPE_POINTER, GTK_ARG_READWRITE, ARG_PIXBUF); + gtk_object_add_arg_type ("SPCtrl::filled", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_FILLED); + gtk_object_add_arg_type ("SPCtrl::fill_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_FILL_COLOR); + gtk_object_add_arg_type ("SPCtrl::stroked", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_STROKED); + gtk_object_add_arg_type ("SPCtrl::stroke_color", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_STROKE_COLOR); + + object_class->destroy = sp_ctrl_destroy; + object_class->set_arg = sp_ctrl_set_arg; + + item_class->update = sp_ctrl_update; + item_class->render = sp_ctrl_render; + item_class->point = sp_ctrl_point; +} + +static void +sp_ctrl_init (SPCtrl *ctrl) +{ + ctrl->shape = SP_CTRL_SHAPE_SQUARE; + ctrl->mode = SP_CTRL_MODE_COLOR; + ctrl->anchor = GTK_ANCHOR_CENTER; + ctrl->span = 3; + ctrl->defined = TRUE; + ctrl->shown = FALSE; + ctrl->build = FALSE; + ctrl->filled = 1; + ctrl->stroked = 0; + ctrl->fill_color = 0x000000ff; + ctrl->stroke_color = 0x000000ff; + + ctrl->box.x0 = ctrl->box.y0 = ctrl->box.x1 = ctrl->box.y1 = 0; + ctrl->cache = NULL; + ctrl->pixbuf = NULL; +} + +static void +sp_ctrl_destroy (GtkObject *object) +{ + SPCtrl *ctrl; + + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRL (object)); + + ctrl = SP_CTRL (object); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrl_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + SPCanvasItem *item; + SPCtrl *ctrl; + GdkPixbuf * pixbuf = NULL; + + item = SP_CANVAS_ITEM (object); + ctrl = SP_CTRL (object); + + switch (arg_id) { + case ARG_SHAPE: + ctrl->shape = (SPCtrlShapeType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_MODE: + ctrl->mode = (SPCtrlModeType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_ANCHOR: + ctrl->anchor = (GtkAnchorType)(GTK_VALUE_INT (*arg)); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_SIZE: + ctrl->span = (gint) ((GTK_VALUE_DOUBLE (*arg) - 1.0) / 2.0 + 0.5); + ctrl->defined = (ctrl->span > 0); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_FILLED: + ctrl->filled = GTK_VALUE_BOOL (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_FILL_COLOR: + ctrl->fill_color = GTK_VALUE_INT (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_STROKED: + ctrl->stroked = GTK_VALUE_BOOL (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_STROKE_COLOR: + ctrl->stroke_color = GTK_VALUE_INT (*arg); + ctrl->build = FALSE; + sp_canvas_item_request_update (item); + break; + case ARG_PIXBUF: + pixbuf = (GdkPixbuf*)(GTK_VALUE_POINTER (*arg)); + if (gdk_pixbuf_get_has_alpha (pixbuf)) { + ctrl->pixbuf = pixbuf; + } else { + ctrl->pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + gdk_pixbuf_unref (pixbuf); + } + ctrl->build = FALSE; + break; + default: + break; + } +} + +static void +sp_ctrl_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SPCtrl *ctrl; + gint x, y; + + ctrl = SP_CTRL (item); + + if (((SPCanvasItemClass *) parent_class)->update) + (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + if (ctrl->shown) { + sp_canvas_request_redraw (item->canvas, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1); + } + + if (!ctrl->defined) return; + + x = (gint) ((affine[4] > 0) ? (affine[4] + 0.5) : (affine[4] - 0.5)) - ctrl->span; + y = (gint) ((affine[5] > 0) ? (affine[5] + 0.5) : (affine[5] - 0.5)) - ctrl->span; + + switch (ctrl->anchor) { + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + break; + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + x += ctrl->span; + break; + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + x -= (ctrl->span + 1); + break; + } + + switch (ctrl->anchor) { + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + break; + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + y += ctrl->span; + break; + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + y -= (ctrl->span + 1); + break; + } + + ctrl->box.x0 = x; + ctrl->box.y0 = y; + ctrl->box.x1 = ctrl->box.x0 + 2 * ctrl->span; + ctrl->box.y1 = ctrl->box.y0 + 2 * ctrl->span; + + sp_canvas_update_bbox (item, ctrl->box.x0, ctrl->box.y0, ctrl->box.x1 + 1, ctrl->box.y1 + 1); + +} + +static double +sp_ctrl_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + SPCtrl *ctrl = SP_CTRL (item); + + *actual_item = item; + + const double x = p[NR::X]; + const double y = p[NR::Y]; + + if ((x >= ctrl->box.x0) && (x <= ctrl->box.x1) && (y >= ctrl->box.y0) && (y <= ctrl->box.y1)) return 0.0; + + return 1e18; +} + +static void +sp_ctrl_build_cache (SPCtrl *ctrl) +{ + guchar * p, *q; + gint size, x, y, z, s, a, side, c; + guint8 fr, fg, fb, fa, sr, sg, sb, sa; + + if (ctrl->filled) { + fr = (ctrl->fill_color >> 24) & 0xff; + fg = (ctrl->fill_color >> 16) & 0xff; + fb = (ctrl->fill_color >> 8) & 0xff; + fa = (ctrl->fill_color) & 0xff; + } else { fr = 0x00; fg = 0x00; fb = 0x00; fa = 0x00; } + if (ctrl->stroked) { + sr = (ctrl->stroke_color >> 24) & 0xff; + sg = (ctrl->stroke_color >> 16) & 0xff; + sb = (ctrl->stroke_color >> 8) & 0xff; + sa = (ctrl->stroke_color) & 0xff; + } else { sr = fr; sg = fg; sb = fb; sa = fa; } + + + side = (ctrl->span * 2 +1); + c = ctrl->span ; + g_free (ctrl->cache); + size = (side) * (side) * 4; + ctrl->cache = (guchar*)g_malloc (size); + if (side < 2) return; + + switch (ctrl->shape) { + case SP_CTRL_SHAPE_SQUARE: + p = ctrl->cache; + for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; } + for (y = 2; y < side; y++) { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + for (x=2; x < side; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; } + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + } + for (x=0; x < side; x++) { *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_DIAMOND: + p = ctrl->cache; + for (y = 0; y < side; y++) { + z = abs (c - y); + for (x = 0; x < z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++; + for (; x < side - z -1; x++) { *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; } + if (z != c) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;} + for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + } + break; + case SP_CTRL_SHAPE_CIRCLE: + p = ctrl->cache; + q = p + size -1; + s = -1; + for (y = 0; y <= c ; y++) { + a = abs (c - y); + z = (gint)(0.0 + sqrt ((c+.4)*(c+.4) - a*a)); + x = 0; + while (x < c-z) { + *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; + *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; + x++; + } + do { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr; + x++; + } while (x < c-s); + while (x < MIN(c+s+1, c+z)) { + *p++ = fr; *p++ = fg; *p++ = fb; *p++ = fa; + *q-- = fa; *q-- = fb; *q-- = fg; *q-- = fr; + x++; + } + do { + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; + *q-- = sa; *q-- = sb; *q-- = sg; *q-- = sr; + x++; + } while (x <= c+z); + while (x < side) { + *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; + *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; *q-- = 0x00; + x++; + } + s = z; + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_CROSS: + p = ctrl->cache; + for (y = 0; y < side; y++) { + z = abs (c - y); + for (x = 0; x < c-z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + *p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++; + for (; x < c + z; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + if (z != 0) {*p++ = sr; *p++ = sg; *p++ = sb; *p++ = sa; x++;} + for (; x < side; x++) {*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;} + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_BITMAP: + if (ctrl->pixbuf) { + unsigned char *px; + unsigned int rs; + px = gdk_pixbuf_get_pixels (ctrl->pixbuf); + rs = gdk_pixbuf_get_rowstride (ctrl->pixbuf); + for (y = 0; y < side; y++){ + unsigned char *s, *d; + s = px + y * rs; + d = ctrl->cache + 4 * side * y; + for (x = 0; x < side; x++) { + if (s[3] < 0x80) { + d[0] = 0x00; + d[1] = 0x00; + d[2] = 0x00; + d[3] = 0x00; + } else if (s[0] < 0x80) { + d[0] = sr; + d[1] = sg; + d[2] = sb; + d[3] = sa; + } else { + d[0] = fr; + d[1] = fg; + d[2] = fb; + d[3] = fa; + } + s += 4; + d += 4; + } + } + } else { + g_print ("control has no pixmap\n"); + } + ctrl->build = TRUE; + break; + case SP_CTRL_SHAPE_IMAGE: + if (ctrl->pixbuf) { + guint r = gdk_pixbuf_get_rowstride (ctrl->pixbuf); + guchar * pix; + q = gdk_pixbuf_get_pixels (ctrl->pixbuf); + p = ctrl->cache; + for (y = 0; y < side; y++){ + pix = q + (y * r); + for (x = 0; x < side; x++) { + *p++ = *pix++; + *p++ = *pix++; + *p++ = *pix++; + *p++ = *pix++; + } + } + } else { g_print ("control has no pixmap\n"); } + ctrl->build = TRUE; + break; + default: + break; + } + +} + +// composite background, foreground, alpha for xor mode +#define COMPOSE_X(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff ) +// composite background, foreground, alpha for color mode +#define COMPOSE_N(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) f) * ((guchar) a) ) / 0xff ) + +static void +sp_ctrl_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + gint y0, y1, y, x0,x1,x; + guchar * p, * q, a; + + SPCtrl *ctrl = SP_CTRL (item); + + if (!ctrl->defined) return; + if ((!ctrl->filled) && (!ctrl->stroked)) return; + + sp_canvas_prepare_buffer (buf); + + // the control-image is rendered into ctrl->cache + if (!ctrl->build) sp_ctrl_build_cache (ctrl); + + // then we render from ctrl->cache + y0 = MAX (ctrl->box.y0, buf->rect.y0); + y1 = MIN (ctrl->box.y1, buf->rect.y1 - 1); + x0 = MAX (ctrl->box.x0, buf->rect.x0); + x1 = MIN (ctrl->box.x1, buf->rect.x1 - 1); + + bool colormode; + + for (y = y0; y <= y1; y++) { + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + q = ctrl->cache + ((y - ctrl->box.y0) * (ctrl->span*2+1) + (x0 - ctrl->box.x0)) * 4; + for (x = x0; x <= x1; x++) { + a = *(q + 3); + // 00000000 is the only way to get invisible; all other colors with alpha 00 are treated as mode_color with alpha ff + colormode = false; + if (a == 0x00 && !(q[0] == 0x00 && q[1] == 0x00 && q[2] == 0x00)) { + a = 0xff; + colormode = true; + } + if (ctrl->mode == SP_CTRL_MODE_COLOR || colormode) { + p[0] = COMPOSE_N (p[0], q[0], a); + p[1] = COMPOSE_N (p[1], q[1], a); + p[2] = COMPOSE_N (p[2], q[2], a); + q += 4; + p += 3; + } else if (ctrl->mode == SP_CTRL_MODE_XOR) { + p[0] = COMPOSE_X (p[0], q[0], a); + p[1] = COMPOSE_X (p[1], q[1], a); + p[2] = COMPOSE_X (p[2], q[2], a); + q += 4; + p += 3; + } + } + } + ctrl->shown = TRUE; +} + +void SPCtrl::moveto (NR::Point const p) { + sp_canvas_item_affine_absolute (SP_CANVAS_ITEM (this), NR::Matrix(NR::translate (p))); +} diff --git a/src/display/sodipodi-ctrl.h b/src/display/sodipodi-ctrl.h new file mode 100644 index 000000000..c0e584ce2 --- /dev/null +++ b/src/display/sodipodi-ctrl.h @@ -0,0 +1,65 @@ +#ifndef INKSCAPE_CTRL_H +#define INKSCAPE_CTRL_H + +/* sodipodi-ctrl + * + * It is simply small square, which does not scale nor rotate + * + */ + +#include +#include "sp-canvas.h" +#include +#include + + + +#define SP_TYPE_CTRL (sp_ctrl_get_type ()) +#define SP_CTRL(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRL, SPCtrl)) +#define SP_CTRL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_CTRL, SPCtrlClass)) +#define SP_IS_CTRL(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRL)) +#define SP_IS_CTRL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRL)) + +typedef enum { + SP_CTRL_SHAPE_SQUARE, + SP_CTRL_SHAPE_DIAMOND, + SP_CTRL_SHAPE_CIRCLE, + SP_CTRL_SHAPE_CROSS, + SP_CTRL_SHAPE_BITMAP, + SP_CTRL_SHAPE_IMAGE +} SPCtrlShapeType; + + +typedef enum { + SP_CTRL_MODE_COLOR, + SP_CTRL_MODE_XOR +} SPCtrlModeType; + +struct SPCtrl : public SPCanvasItem{ + SPCtrlShapeType shape; + SPCtrlModeType mode; + GtkAnchorType anchor; + gint span; + guint defined : 1; + guint shown : 1; + guint build : 1; + guint filled : 1; + guint stroked : 1; + guint32 fill_color; + guint32 stroke_color; + + NRRectL box; /* NB! x1 & y1 are included */ + guchar *cache; + GdkPixbuf * pixbuf; + + void moveto(NR::Point const p); +}; + +struct SPCtrlClass : public SPCanvasItemClass{ +}; + + + +/* Standard Gtk function */ +GtkType sp_ctrl_get_type (void); +#endif diff --git a/src/display/sodipodi-ctrlrect.cpp b/src/display/sodipodi-ctrlrect.cpp new file mode 100644 index 000000000..05449f2bb --- /dev/null +++ b/src/display/sodipodi-ctrlrect.cpp @@ -0,0 +1,330 @@ +#define __INKSCAPE_CTRLRECT_C__ + +/* + * Simple non-transformed rectangle, usable for rubberband + * + * Author: + * Lauris Kaplinski + * bulia byak + * Carl Hetherington + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sodipodi-ctrlrect.h" + +/* + * Currently we do not have point method, as it should always be painted + * during some transformation, which takes care of events... + * + * Corner coords can be in any order - i.e. x1 < x0 is allowed + */ + +static void sp_ctrlrect_class_init(SPCtrlRectClass *c); +static void sp_ctrlrect_init(CtrlRect *ctrlrect); +static void sp_ctrlrect_destroy(GtkObject *object); + +static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +static const guint DASH_LENGTH = 4; + +GtkType sp_ctrlrect_get_type() +{ + static GtkType ctrlrect_type = 0; + + if (!ctrlrect_type) { + GtkTypeInfo ctrlrect_info = { + "SPCtrlRect", + sizeof(CtrlRect), + sizeof(SPCtrlRectClass), + (GtkClassInitFunc) sp_ctrlrect_class_init, + (GtkObjectInitFunc) sp_ctrlrect_init, + NULL, NULL, NULL + }; + ctrlrect_type = gtk_type_unique(SP_TYPE_CANVAS_ITEM, &ctrlrect_info); + } + return ctrlrect_type; +} + +static void sp_ctrlrect_class_init(SPCtrlRectClass *c) +{ + GtkObjectClass *object_class = (GtkObjectClass *) c; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) c; + + parent_class = (SPCanvasItemClass*) gtk_type_class(sp_canvas_item_get_type()); + + object_class->destroy = sp_ctrlrect_destroy; + + item_class->update = sp_ctrlrect_update; + item_class->render = sp_ctrlrect_render; +} + +static void sp_ctrlrect_init(CtrlRect *cr) +{ + cr->init(); +} + +static void sp_ctrlrect_destroy(GtkObject *object) +{ + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (* GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +/* FIXME: use definitions from somewhere else */ +#define RGBA_R(v) ((v) >> 24) +#define RGBA_G(v) (((v) >> 16) & 0xff) +#define RGBA_B(v) (((v) >> 8) & 0xff) +#define RGBA_A(v) ((v) & 0xff) +#define COMPOSE(b,f,a) ( ( ((guchar) b) * ((guchar) (0xff - a)) + ((guchar) ((b ^ ~f) + b/4 - (b>127? 63 : 0))) * ((guchar) a) ) / 0xff ) + +static void sp_ctrlrect_hline(SPCanvasBuf *buf, gint y, gint xs, gint xe, guint32 rgba, guint dashed) +{ + if (y >= buf->rect.y0 && y < buf->rect.y1) { + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const x0 = MAX(buf->rect.x0, xs); + gint const x1 = MIN(buf->rect.x1, xe + 1); + guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (gint x = x0; x < x1; x++) { + if (!dashed || ((x / DASH_LENGTH) % 2)) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + } + p += 3; + } + } +} + +static void sp_ctrlrect_vline(SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba, guint dashed) +{ + if (x >= buf->rect.x0 && x < buf->rect.x1) { + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const y0 = MAX(buf->rect.y0, ys); + gint const y1 = MIN(buf->rect.y1, ye + 1); + guchar *p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3; + for (gint y = y0; y < y1; y++) { + if (!dashed || ((y / DASH_LENGTH) % 2)) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + } + p += buf->buf_rowstride; + } + } +} + +/** Fills the pixels in [xs, xe)*[ys,ye) clipped to the tile with rgb * a. */ +static void sp_ctrlrect_area(SPCanvasBuf *buf, gint xs, gint ys, gint xe, gint ye, guint32 rgba) +{ + guint const r = RGBA_R(rgba); + guint const g = RGBA_G(rgba); + guint const b = RGBA_B(rgba); + guint const a = RGBA_A(rgba); + gint const x0 = MAX(buf->rect.x0, xs); + gint const x1 = MIN(buf->rect.x1, xe + 1); + gint const y0 = MAX(buf->rect.y0, ys); + gint const y1 = MIN(buf->rect.y1, ye + 1); + for (gint y = y0; y < y1; y++) { + guchar *p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x0 - buf->rect.x0) * 3; + for (gint x = x0; x < x1; x++) { + p[0] = COMPOSE(p[0], r, a); + p[1] = COMPOSE(p[1], g, a); + p[2] = COMPOSE(p[2], b, a); + p += 3; + } + } +} + +static void sp_ctrlrect_render(SPCanvasItem *item, SPCanvasBuf *buf) +{ + SP_CTRLRECT(item)->render(buf); +} + + +static void sp_ctrlrect_update(SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + SP_CTRLRECT(item)->update(affine, flags); +} + + + +void CtrlRect::init() +{ + _has_fill = false; + _dashed = false; + _shadow = 0; + + _area.x0 = _area.y0 = 0; + _area.x1 = _area.y1 = -1; + + _shadow_size = 0; + + _border_color = 0x000000ff; + _fill_color = 0xffffffff; + _shadow_color = 0x000000ff; +} + + +void CtrlRect::render(SPCanvasBuf *buf) +{ + if ((_area.x0 < buf->rect.x1) && + (_area.y0 < buf->rect.y1) && + ((_area.x1 + _shadow_size) >= buf->rect.x0) && + ((_area.y1 + _shadow_size) >= buf->rect.y0)) { + sp_canvas_prepare_buffer(buf); + + /* Top */ + sp_ctrlrect_hline(buf, _area.y0, _area.x0, _area.x1, _border_color, _dashed); + /* Bottom */ + sp_ctrlrect_hline(buf, _area.y1, _area.x0, _area.x1, _border_color, _dashed); + /* Left */ + sp_ctrlrect_vline(buf, _area.x0, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed); + /* Right */ + sp_ctrlrect_vline(buf, _area.x1, _area.y0 + 1, _area.y1 - 1, _border_color, _dashed); + if (_shadow_size > 0) { + /* Right shadow */ + sp_ctrlrect_area(buf, _area.x1 + 1, _area.y0 + _shadow_size, + _area.x1 + _shadow_size, _area.y1 + _shadow_size, _shadow_color); + /* Bottom shadow */ + sp_ctrlrect_area(buf, _area.x0 + _shadow_size, _area.y1 + 1, + _area.x1, _area.y1 + _shadow_size, _shadow_color); + } + if (_has_fill) { + /* Fill */ + sp_ctrlrect_area(buf, _area.x0 + 1, _area.y0 + 1, + _area.x1 - 1, _area.y1 - 1, _fill_color); + } + } +} + + +void CtrlRect::update(NR::Matrix const &affine, unsigned int flags) +{ + if (((SPCanvasItemClass *) parent_class)->update) { + ((SPCanvasItemClass *) parent_class)->update(this, affine, flags); + } + + sp_canvas_item_reset_bounds(this); + + /* Request redraw old */ + if (!_has_fill) { + /* Top */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + 1, _area.y0 + 1); + /* Left */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x0 + 1, _area.y1 + 1); + /* Right */ + sp_canvas_request_redraw(canvas, + _area.x1 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + /* Bottom */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y1 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } else { + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } + + NR::Rect bbox(_rect.min() * affine, _rect.max() * affine); + + _area.x0 = (int) floor(bbox.min()[NR::X] + 0.5); + _area.y0 = (int) floor(bbox.min()[NR::Y] + 0.5); + _area.x1 = (int) floor(bbox.max()[NR::X] + 0.5); + _area.y1 = (int) floor(bbox.max()[NR::Y] + 0.5); + + _shadow_size = _shadow; + + /* Request redraw new */ + if (!_has_fill) { + /* Top */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + 1, _area.y0 + 1); + /* Left */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x0 + 1, _area.y1 + 1); + /* Right */ + sp_canvas_request_redraw(canvas, + _area.x1 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + /* Bottom */ + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y1 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } else { + sp_canvas_request_redraw(canvas, + _area.x0 - 1, _area.y0 - 1, + _area.x1 + _shadow_size + 1, _area.y1 + _shadow_size + 1); + } + + x1 = _area.x0 - 1; + y1 = _area.y0 - 1; + x2 = _area.x1 + _shadow_size + 1; + y2 = _area.y1 + _shadow_size + 1; +} + + +void CtrlRect::setColor(guint32 b, bool h, guint f) +{ + _border_color = b; + _has_fill = h; + _fill_color = f; + _requestUpdate(); +} + +void CtrlRect::setShadow(int s, guint c) +{ + _shadow = s; + _shadow_color = c; + _requestUpdate(); +} + +void CtrlRect::setRectangle(NR::Rect const &r) +{ + _rect = r; + _requestUpdate(); +} + +void CtrlRect::setDashed(bool d) +{ + _dashed = d; + _requestUpdate(); +} + +void CtrlRect::_requestUpdate() +{ + sp_canvas_item_request_update(SP_CANVAS_ITEM(this)); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sodipodi-ctrlrect.h b/src/display/sodipodi-ctrlrect.h new file mode 100644 index 000000000..dc931b7dc --- /dev/null +++ b/src/display/sodipodi-ctrlrect.h @@ -0,0 +1,70 @@ +#ifndef __INKSCAPE_CTRLRECT_H__ +#define __INKSCAPE_CTRLRECT_H__ + +/** + * \file sodipodi-ctrlrect.h + * \brief Simple non-transformed rectangle, usable for rubberband + * + * Authors: + * Lauris Kaplinski + * Carl Hetherington + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL + * + */ + +#include +#include "sp-canvas.h" + +#define SP_TYPE_CTRLRECT (sp_ctrlrect_get_type ()) +#define SP_CTRLRECT(obj) (GTK_CHECK_CAST((obj), SP_TYPE_CTRLRECT, CtrlRect)) +#define SP_CTRLRECT_CLASS(c) (GTK_CHECK_CLASS_CAST((c), SP_TYPE_CTRLRECT, SPCtrlRectClass)) +#define SP_IS_CTRLRECT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLRECT)) +#define SP_IS_CTRLRECT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_CTRLRECT)) + +class CtrlRect : public SPCanvasItem +{ +public: + + void init(); + void setColor(guint32 b, bool h, guint f); + void setShadow(int s, guint c); + void setRectangle(NR::Rect const &r); + void setDashed(bool d); + + void render(SPCanvasBuf *buf); + void update(NR::Matrix const &affine, unsigned int flags); + +private: + void _requestUpdate(); + + NR::Rect _rect; + bool _has_fill; + bool _dashed; + NRRectL _area; + gint _shadow_size; + guint32 _border_color; + guint32 _fill_color; + guint32 _shadow_color; + int _shadow; +}; + +struct SPCtrlRectClass : public SPCanvasItemClass {}; + +GtkType sp_ctrlrect_get_type(); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp new file mode 100644 index 000000000..e4f2c7b75 --- /dev/null +++ b/src/display/sp-canvas-util.cpp @@ -0,0 +1,307 @@ +#define __SP_CANVAS_UTILS_C__ + +/* + * Helper stuff for SPCanvas + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "libnr/nr-matrix-div.h" +#include "libnr/nr-pixops.h" +#include "sp-canvas-util.h" + +#include +#include +#include + +void +sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2) +{ + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + item->x1 = x1; + item->y1 = y1; + item->x2 = x2; + item->y2 = y2; + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_canvas_item_reset_bounds (SPCanvasItem *item) +{ + item->x1 = 0.0; + item->y1 = 0.0; + item->x2 = 0.0; + item->y2 = 0.0; +} + +void +sp_canvas_prepare_buffer (SPCanvasBuf *buf) +{ + if (buf->is_empty) { + sp_canvas_clear_buffer(buf); + buf->is_empty = false; + } +} + +void +sp_canvas_clear_buffer (SPCanvasBuf *buf) +{ + unsigned char r, g, b; + + r = (buf->bg_color >> 16) & 0xff; + g = (buf->bg_color >> 8) & 0xff; + b = buf->bg_color & 0xff; + + if ((r != g) || (r != b)) { + int x, y; + for (y = buf->rect.y0; y < buf->rect.y1; y++) { + unsigned char *p; + p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride; + for (x = buf->rect.x0; x < buf->rect.x1; x++) { + *p++ = r; + *p++ = g; + *p++ = b; + } + } + } else { + int y; + for (y = buf->rect.y0; y < buf->rect.y1; y++) { + memset (buf->buf + (y - buf->rect.y0) * buf->buf_rowstride, r, 3 * (buf->rect.x1 - buf->rect.x0)); + } + } +} + +NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item) +{ + g_assert (item != NULL); // this may be overly zealous - it is + // plausible that this gets called + // with item == 0 + + return item->xform; +} + +NR::Matrix sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to) +{ + g_assert (from != NULL); + g_assert (to != NULL); + + return sp_canvas_item_i2w_affine(from) / sp_canvas_item_i2w_affine(to); +} + +void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, NR::Matrix const &i2w) +{ + g_assert (item != NULL); + + sp_canvas_item_affine_absolute(item, i2w / sp_canvas_item_i2w_affine(item->parent)); +} + +void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z) +{ + g_assert (item != NULL); + + gint current_z = sp_canvas_item_order (item); + + if (current_z == -1) // not found in its parent + return; + + if (z == current_z) + return; + + if (z > current_z) + sp_canvas_item_raise (item, z - current_z); + + sp_canvas_item_lower (item, current_z - z); +} + +gint +sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b) +{ + const gint o_a = sp_canvas_item_order (a); + const gint o_b = sp_canvas_item_order (b); + + if (o_a > o_b) return -1; + if (o_a < o_b) return 1; + + return 0; +} + +// These two functions are used by canvasitems that use livarot (currently ctrlline and ctrlquadr) + +void +ctrl_run_A8_OR (raster_info &dest,void *data,int st,float vst,int en,float ven) +{ + union { + uint8_t comp[4]; + uint32_t col; + } tempCol; + if ( st >= en ) return; + tempCol.col=*(uint32_t*)data; + + unsigned int r, g, b, a; + r = NR_RGBA32_R (tempCol.col); + g = NR_RGBA32_G (tempCol.col); + b = NR_RGBA32_B (tempCol.col); + a = NR_RGBA32_A (tempCol.col); + if (a == 0) return; + + vst*=a; + ven*=a; + + if ( vst < 0 ) vst=0; + if ( vst > 255 ) vst=255; + if ( ven < 0 ) ven=0; + if ( ven > 255 ) ven=255; + float sv=vst; + float dv=ven-vst; + int len=en-st; + uint8_t* d=(uint8_t*)dest.buffer; + + d+=3*(st-dest.startPix); + if ( fabs(dv) < 0.001 ) { + if ( sv > 249.999 ) { + /* Simple copy */ + while (len > 0) { + d[0] = INK_COMPOSE (r, 255, d[0]); + d[1] = INK_COMPOSE (g, 255, d[1]); + d[2] = INK_COMPOSE (b, 255, d[2]); + d += 3; + len -= 1; + } + } else { + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + while (len > 0) { + d[0] = INK_COMPOSE (r, c0_24, d[0]); + d[1] = INK_COMPOSE (g, c0_24, d[1]); + d[2] = INK_COMPOSE (b, c0_24, d[2]); + d += 3; + len -= 1; + } + } + } else { + if ( en <= st+1 ) { + sv=0.5*(vst+ven); + unsigned int c0_24=(int)sv; + c0_24&=0xFF; + d[0] = INK_COMPOSE (r, c0_24, d[0]); + d[1] = INK_COMPOSE (g, c0_24, d[1]); + d[2] = INK_COMPOSE (b, c0_24, d[2]); + } else { + dv/=len; + sv+=0.5*dv; // correction trapezoidale + sv*=65536; + dv*=65536; + int c0_24 = static_cast(CLAMP(sv, 0, 16777216)); + int s0_24 = static_cast(dv); + while (len > 0) { + unsigned int ca; + /* Draw */ + ca = c0_24 >> 16; + if ( ca > 255 ) ca=255; + d[0] = INK_COMPOSE (r, ca, d[0]); + d[1] = INK_COMPOSE (g, ca, d[1]); + d[2] = INK_COMPOSE (b, ca, d[2]); + d += 3; + c0_24 += s0_24; + c0_24 = CLAMP (c0_24, 0, 16777216); + len -= 1; + } + } + } +} + +void nr_pixblock_render_ctrl_rgba (Shape* theS,uint32_t color,NRRectL &area,char* destBuf,int stride) +{ + + theS->CalcBBox(); + float l=theS->leftX,r=theS->rightX,t=theS->topY,b=theS->bottomY; + int il,ir,it,ib; + il=(int)floor(l); + ir=(int)ceil(r); + it=(int)floor(t); + ib=(int)ceil(b); + +// printf("bbox %i %i %i %i render %i %i %i %i\n",il,it,ir,ib,area.x0,area.y0,area.x1,area.y1); + + if ( il >= area.x1 || ir <= area.x0 || it >= area.y1 || ib <= area.y0 ) return; + if ( il < area.x0 ) il=area.x0; + if ( it < area.y0 ) it=area.y0; + if ( ir > area.x1 ) ir=area.x1; + if ( ib > area.y1 ) ib=area.y1; + +/* // version par FloatLigne + int curPt; + float curY; + theS->BeginRaster(curY,curPt,1.0); + + FloatLigne* theI=new FloatLigne(); + IntLigne* theIL=new IntLigne(); + + theS->Scan(curY,curPt,(float)(it),1.0); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + if ( y&0x00000003 ) { + theS->Scan(curY,curPt,((float)(y+1)),theI,false,1.0); + } else { + theS->Scan(curY,curPt,((float)(y+1)),theI,true,1.0); + } + theI->Flatten(); + theIL->Copy(theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,bpath_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndRaster(); + delete theI; + delete theIL; */ + + // version par BitLigne directe + int curPt; + float curY; + theS->BeginQuickRaster(curY, curPt); + + BitLigne* theI[4]; + for (int i=0;i<4;i++) theI[i]=new BitLigne(il,ir); + IntLigne* theIL=new IntLigne(); + + theS->QuickScan(curY,curPt,(float)(it),true,0.25); + + char* mdata=(char*)destBuf; + uint32_t* ligStart=((uint32_t*)(mdata+(3*(il-area.x0)+stride*(it-area.y0)))); + for (int y=it;yReset(); + theS->QuickScan(curY,curPt,((float)(y+0.25)),fill_oddEven,theI[0],0.25); + theS->QuickScan(curY,curPt,((float)(y+0.5)),fill_oddEven,theI[1],0.25); + theS->QuickScan(curY,curPt,((float)(y+0.75)),fill_oddEven,theI[2],0.25); + theS->QuickScan(curY,curPt,((float)(y+1.0)),fill_oddEven,theI[3],0.25); + theIL->Copy(4,theI); + + raster_info dest; + dest.startPix=il; + dest.endPix=ir; + dest.sth=il; + dest.stv=y; + dest.buffer=ligStart; + theIL->Raster(dest,&color,ctrl_run_A8_OR); + ligStart=((uint32_t*)(((char*)ligStart)+stride)); + } + theS->EndQuickRaster(); + for (int i=0;i<4;i++) delete theI[i]; + delete theIL; +} diff --git a/src/display/sp-canvas-util.h b/src/display/sp-canvas-util.h new file mode 100644 index 000000000..b592ba1d0 --- /dev/null +++ b/src/display/sp-canvas-util.h @@ -0,0 +1,61 @@ +#ifndef __SP_CANVAS_UTILS_H__ +#define __SP_CANVAS_UTILS_H__ + +/* + * Helper stuff for SPCanvas + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-canvas.h" + +/* Miscellaneous utility & convenience functions for general canvas objects */ + +void sp_canvas_update_bbox (SPCanvasItem *item, int x1, int y1, int x2, int y2); +void sp_canvas_item_reset_bounds (SPCanvasItem *item); +void sp_canvas_prepare_buffer (SPCanvasBuf *buf); + +/* fill buffer with background color */ + +void +sp_canvas_clear_buffer (SPCanvasBuf * buf); + +/* get i2p (item to parent) affine transformation as general 6-element array */ + +NR::Matrix sp_canvas_item_i2p_affine (SPCanvasItem * item); + +/* get i2i (item to item) affine transformation as general 6-element array */ + +NR::Matrix sp_canvas_item_i2i_affine (SPCanvasItem * from, SPCanvasItem * to); + +/* set item affine matrix to achieve given i2w matrix */ + +void sp_canvas_item_set_i2w_affine (SPCanvasItem * item, NR::Matrix const & aff); + +void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z); + +gint sp_canvas_item_compare_z (SPCanvasItem * a, SPCanvasItem * b); + +class Shape; +class raster_info; +void ctrl_run_A8_OR (raster_info &dest, void *data, int st, float vst, int en, float ven); +void nr_pixblock_render_ctrl_rgba (Shape* theS, uint32_t color, NRRectL &area, char* destBuf, int stride); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp new file mode 100644 index 000000000..d1d7221f0 --- /dev/null +++ b/src/display/sp-canvas.cpp @@ -0,0 +1,2074 @@ +#define __SP_CANVAS_C__ + +/** \file + * Port of GnomeCanvas for Inkscape needs + * + * Authors: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * fred + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include + +#include +#include +#include "display-forward.h" +#include +#include +#include + +enum { + RENDERMODE_NORMAL, + RENDERMODE_NOAA, + RENDERMODE_OUTLINE +}; + +const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE; + +#define SP_CANVAS_WINDOW(c) (((GtkWidget *) (c))->window) + +enum { + SP_CANVAS_ITEM_VISIBLE = 1 << 7, + SP_CANVAS_ITEM_NEED_UPDATE = 1 << 8, + SP_CANVAS_ITEM_NEED_AFFINE = 1 << 9 +}; + +/** + * A group of Items. + */ +struct SPCanvasGroup { + SPCanvasItem item; + + GList *items, *last; +}; + +/** + * The SPCanvasGroup vtable. + */ +struct SPCanvasGroupClass { + SPCanvasItemClass parent_class; +}; + +/** + * The SPCanvas vtable. + */ +struct SPCanvasClass { + GtkWidgetClass parent_class; +}; + +static void group_add (SPCanvasGroup *group, SPCanvasItem *item); +static void group_remove (SPCanvasGroup *group, SPCanvasItem *item); + +/* SPCanvasItem */ + +enum {ITEM_EVENT, ITEM_LAST_SIGNAL}; + + +static void sp_canvas_request_update (SPCanvas *canvas); + +static void sp_canvas_item_class_init (SPCanvasItemClass *klass); +static void sp_canvas_item_init (SPCanvasItem *item); +static void sp_canvas_item_dispose (GObject *object); +static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args); + +static int emit_event (SPCanvas *canvas, GdkEvent *event); + +static guint item_signals[ITEM_LAST_SIGNAL] = { 0 }; + +static GtkObjectClass *item_parent_class; + +/** + * Registers the SPCanvasItem class with Glib and returns its type number. + */ +GType +sp_canvas_item_get_type (void) +{ + static GType type = 0; + if (!type) { + static const GTypeInfo info = { + sizeof (SPCanvasItemClass), + NULL, NULL, + (GClassInitFunc) sp_canvas_item_class_init, + NULL, NULL, + sizeof (SPCanvasItem), + 0, + (GInstanceInitFunc) sp_canvas_item_init, + NULL + }; + type = g_type_register_static (GTK_TYPE_OBJECT, "SPCanvasItem", &info, (GTypeFlags)0); + } + + return type; +} + +/** + * Initializes the SPCanvasItem vtable and the "event" signal. + */ +static void +sp_canvas_item_class_init (SPCanvasItemClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + /* fixme: Derive from GObject */ + item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT); + + item_signals[ITEM_EVENT] = g_signal_new ("event", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SPCanvasItemClass, event), + NULL, NULL, + sp_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT); + + object_class->dispose = sp_canvas_item_dispose; +} + +/** + * Callback for initialization of SPCanvasItem. + */ +static void +sp_canvas_item_init (SPCanvasItem *item) +{ + item->flags |= SP_CANVAS_ITEM_VISIBLE; + item->xform = NR::Matrix(NR::identity()); +} + +/** + * Constructs new SPCanvasItem on SPCanvasGroup. + */ +SPCanvasItem * +sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...) +{ + va_list args; + + g_return_val_if_fail (parent != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS_GROUP (parent), NULL); + g_return_val_if_fail (gtk_type_is_a (type, sp_canvas_item_get_type ()), NULL); + + SPCanvasItem *item = SP_CANVAS_ITEM (gtk_type_new (type)); + + va_start (args, first_arg_name); + sp_canvas_item_construct (item, parent, first_arg_name, args); + va_end (args); + + return item; +} + +/** + * Sets up the newly created SPCanvasItem. + * + * We make it static for encapsulation reasons since it was nowhere used. + */ +static void +sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args) +{ + g_return_if_fail (SP_IS_CANVAS_GROUP (parent)); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + item->parent = SP_CANVAS_ITEM (parent); + item->canvas = item->parent->canvas; + + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + + group_add (SP_CANVAS_GROUP (item->parent), item); + + sp_canvas_item_request_update (item); + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Helper function that requests redraw only if item's visible flag is set. + */ +static void +redraw_if_visible (SPCanvasItem *item) +{ + if (item->flags & SP_CANVAS_ITEM_VISIBLE) { + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + } +} + +/** + * Callback that removes item from all referers and destroys it. + */ +static void +sp_canvas_item_dispose (GObject *object) +{ + SPCanvasItem *item = SP_CANVAS_ITEM (object); + + redraw_if_visible (item); + item->flags &= ~SP_CANVAS_ITEM_VISIBLE; + + if (item == item->canvas->current_item) { + item->canvas->current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item == item->canvas->new_current_item) { + item->canvas->new_current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item == item->canvas->grabbed_item) { + item->canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + if (item == item->canvas->focused_item) + item->canvas->focused_item = NULL; + + if (item->parent) { + group_remove (SP_CANVAS_GROUP (item->parent), item); + } + + G_OBJECT_CLASS (item_parent_class)->dispose (object); +} + +/** + * Helper function to update item and its children. + * + * NB! affine is parent2canvas. + */ +static void +sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + /* Apply the child item's transform */ + NR::Matrix child_affine = item->xform * affine; + + /* apply object flags to child flags */ + int child_flags = flags & ~SP_CANVAS_UPDATE_REQUESTED; + + if (item->flags & SP_CANVAS_ITEM_NEED_UPDATE) + child_flags |= SP_CANVAS_UPDATE_REQUESTED; + + if (item->flags & SP_CANVAS_ITEM_NEED_AFFINE) + child_flags |= SP_CANVAS_UPDATE_AFFINE; + + if (child_flags & (SP_CANVAS_UPDATE_REQUESTED | SP_CANVAS_UPDATE_AFFINE)) { + if (SP_CANVAS_ITEM_GET_CLASS (item)->update) + SP_CANVAS_ITEM_GET_CLASS (item)->update (item, child_affine, child_flags); + } + + GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_UPDATE); + GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_AFFINE); +} + +/** + * Helper function to invoke the point method of the item. + * + * The argument x, y should be in the parent's item-relative coordinate + * system. This routine applies the inverse of the item's transform, + * maintaining the affine invariant. + */ +static double +sp_canvas_item_invoke_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + if (SP_CANVAS_ITEM_GET_CLASS (item)->point) + return SP_CANVAS_ITEM_GET_CLASS (item)->point (item, p, actual_item); + + return NR_HUGE; +} + +/** + * Makes the item's affine transformation matrix be equal to the specified + * matrix. + * + * @item: A canvas item. + * @affine: An affine transformation matrix. + */ +void +sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine) +{ + item->xform = affine; + + if (!(item->flags & SP_CANVAS_ITEM_NEED_AFFINE)) { + item->flags |= SP_CANVAS_ITEM_NEED_AFFINE; + if (item->parent != NULL) { + sp_canvas_item_request_update (item->parent); + } else { + sp_canvas_request_update (item->canvas); + } + } + + item->canvas->need_repick = TRUE; +} + +/** + * Convenience function to reorder items in a group's child list. + * + * This puts the specified link after the "before" link. + */ +static void +put_item_after (GList *link, GList *before) +{ + if (link == before) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (SP_CANVAS_ITEM (link->data)->parent); + + if (before == NULL) { + if (link == parent->items) return; + + link->prev->next = link->next; + + if (link->next) { + link->next->prev = link->prev; + } else { + parent->last = link->prev; + } + + link->prev = before; + link->next = parent->items; + link->next->prev = link; + parent->items = link; + } else { + if ((link == parent->last) && (before == parent->last->prev)) + return; + + if (link->next) + link->next->prev = link->prev; + + if (link->prev) + link->prev->next = link->next; + else { + parent->items = link->next; + parent->items->prev = NULL; + } + + link->prev = before; + link->next = before->next; + + link->prev->next = link; + + if (link->next) + link->next->prev = link; + else + parent->last = link; + } +} + + +/** + * Raises the item in its parent's stack by the specified number of positions. + * + * \param item A canvas item. + * \param positions Number of steps to raise the item. + * + * If the number of positions is greater than the distance to the top of the + * stack, then the item is put at the top. + */ +void +sp_canvas_item_raise (SPCanvasItem *item, int positions) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 0); + + if (!item->parent || positions == 0) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent); + GList *link = g_list_find (parent->items, item); + g_assert (link != NULL); + + GList *before; + for (before = link; positions && before; positions--) + before = before->next; + + if (!before) + before = parent->last; + + put_item_after (link, before); + + redraw_if_visible (item); + item->canvas->need_repick = TRUE; +} + + +/** + * Lowers the item in its parent's stack by the specified number of positions. + * + * \param item A canvas item. + * \param positions Number of steps to lower the item. + * + * If the number of positions is greater than the distance to the bottom of the + * stack, then the item is put at the bottom. + **/ +void +sp_canvas_item_lower (SPCanvasItem *item, int positions) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 1); + + if (!item->parent || positions == 0) + return; + + SPCanvasGroup *parent = SP_CANVAS_GROUP (item->parent); + GList *link = g_list_find (parent->items, item); + g_assert (link != NULL); + + GList *before; + if (link->prev) + for (before = link->prev; positions && before; positions--) + before = before->prev; + else + before = NULL; + + put_item_after (link, before); + + redraw_if_visible (item); + item->canvas->need_repick = TRUE; +} + +/** + * Sets visible flag on item and requests a redraw. + */ +void +sp_canvas_item_show (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (item->flags & SP_CANVAS_ITEM_VISIBLE) + return; + + item->flags |= SP_CANVAS_ITEM_VISIBLE; + + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Clears visible flag on item and requests a redraw. + */ +void +sp_canvas_item_hide (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) + return; + + item->flags &= ~SP_CANVAS_ITEM_VISIBLE; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; +} + +/** + * Grab item under cursor. + * + * \pre !canvas->grabbed_item && item->flags & SP_CANVAS_ITEM_VISIBLE + */ +int +sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, guint32 etime) +{ + g_return_val_if_fail (item != NULL, -1); + g_return_val_if_fail (SP_IS_CANVAS_ITEM (item), -1); + g_return_val_if_fail (GTK_WIDGET_MAPPED (item->canvas), -1); + + if (item->canvas->grabbed_item) + return -1; + + if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) + return -1; + + /* fixme: Top hack (Lauris) */ + /* fixme: If we add key masks to event mask, Gdk will abort (Lauris) */ + /* fixme: But Canvas actualle does get key events, so all we need is routing these here */ + gdk_pointer_grab (SP_CANVAS_WINDOW (item->canvas), FALSE, + (GdkEventMask)(event_mask & (~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK))), + NULL, cursor, etime); + + item->canvas->grabbed_item = item; + item->canvas->grabbed_event_mask = event_mask; + item->canvas->current_item = item; /* So that events go to the grabbed item */ + + return 0; +} + +/** + * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the + * mouse. + * + * \param item A canvas item that holds a grab. + * \param etime The timestamp for ungrabbing the mouse. + */ +void +sp_canvas_item_ungrab (SPCanvasItem *item, guint32 etime) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + + if (item->canvas->grabbed_item != item) + return; + + item->canvas->grabbed_item = NULL; + + gdk_pointer_ungrab (etime); +} + +/** + * Returns the product of all transformation matrices from the root item down + * to the item. + */ +NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item) +{ + g_assert (SP_IS_CANVAS_ITEM (item)); // should we get this? + + NR::Matrix affine = NR::identity(); + + while (item) { + affine *= item->xform; + item = item->parent; + } + return affine; +} + +/** + * Helper that returns true iff item is descendant of parent. + */ +static bool is_descendant(SPCanvasItem const *item, SPCanvasItem const *parent) +{ + while (item) { + if (item == parent) + return true; + item = item->parent; + } + + return false; +} + +/** + * Focus canvas, and item under cursor if it is not already focussed. + */ +void +sp_canvas_item_grab_focus (SPCanvasItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (SP_IS_CANVAS_ITEM (item)); + g_return_if_fail (GTK_WIDGET_CAN_FOCUS (GTK_WIDGET (item->canvas))); + + SPCanvasItem *focused_item = item->canvas->focused_item; + + if (focused_item) { + GdkEvent ev; + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = FALSE; + + emit_event (item->canvas, &ev); + } + + item->canvas->focused_item = item; + gtk_widget_grab_focus (GTK_WIDGET (item->canvas)); + + if (focused_item) { + GdkEvent ev; + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = SP_CANVAS_WINDOW (item->canvas); + ev.focus_change.send_event = FALSE; + ev.focus_change.in = TRUE; + + emit_event (item->canvas, &ev); + } +} + +/** + * Requests that the canvas queue an update for the specified item. + * + * To be used only by item implementations. + */ +void +sp_canvas_item_request_update (SPCanvasItem *item) +{ + if (item->flags & SP_CANVAS_ITEM_NEED_UPDATE) + return; + + item->flags |= SP_CANVAS_ITEM_NEED_UPDATE; + + if (item->parent != NULL) { + /* Recurse up the tree */ + sp_canvas_item_request_update (item->parent); + } else { + /* Have reached the top of the tree, make sure the update call gets scheduled. */ + sp_canvas_request_update (item->canvas); + } +} + +/** + * Returns position of item in group. + */ +gint sp_canvas_item_order (SPCanvasItem * item) +{ + return g_list_index (SP_CANVAS_GROUP (item->parent)->items, item); +} + +/* SPCanvasGroup */ + +static void sp_canvas_group_class_init (SPCanvasGroupClass *klass); +static void sp_canvas_group_init (SPCanvasGroup *group); +static void sp_canvas_group_destroy (GtkObject *object); + +static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static double sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); +static void sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *group_parent_class; + +/** + * Registers SPCanvasGroup class with Gtk and returns its type number. + */ +GtkType +sp_canvas_group_get_type (void) +{ + static GtkType group_type = 0; + + if (!group_type) { + static const GtkTypeInfo group_info = { + "SPCanvasGroup", + sizeof (SPCanvasGroup), + sizeof (SPCanvasGroupClass), + (GtkClassInitFunc) sp_canvas_group_class_init, + (GtkObjectInitFunc) sp_canvas_group_init, + NULL, NULL, NULL + }; + + group_type = gtk_type_unique (sp_canvas_item_get_type (), &group_info); + } + + return group_type; +} + +/** + * Class initialization function for SPCanvasGroupClass + */ +static void +sp_canvas_group_class_init (SPCanvasGroupClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + group_parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ()); + + object_class->destroy = sp_canvas_group_destroy; + + item_class->update = sp_canvas_group_update; + item_class->render = sp_canvas_group_render; + item_class->point = sp_canvas_group_point; +} + +/** + * Callback. Empty. + */ +static void +sp_canvas_group_init (SPCanvasGroup */*group*/) +{ + /* Nothing here */ +} + +/** + * Callback that destroys all items in group and calls group's virtual + * destroy() function. + */ +static void +sp_canvas_group_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CANVAS_GROUP (object)); + + const SPCanvasGroup *group = SP_CANVAS_GROUP (object); + + GList *list = group->items; + while (list) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + list = list->next; + + gtk_object_destroy (GTK_OBJECT (child)); + } + + if (GTK_OBJECT_CLASS (group_parent_class)->destroy) + (* GTK_OBJECT_CLASS (group_parent_class)->destroy) (object); +} + +/** + * Update handler for canvas groups + */ +static void +sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + NR::ConvexHull corners(NR::Point(0, 0)); + bool empty=true; + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *i = (SPCanvasItem *)list->data; + + sp_canvas_item_invoke_update (i, affine, flags); + + if ( i->x2 > i->x1 && i->y2 > i->y1 ) { + if (empty) { + corners = NR::ConvexHull(NR::Point(i->x1, i->y1)); + empty = false; + } else { + corners.add(NR::Point(i->x1, i->y1)); + } + corners.add(NR::Point(i->x2, i->y2)); + } + } + + NR::Rect const &bounds = corners.bounds(); + item->x1 = bounds.min()[NR::X]; + item->y1 = bounds.min()[NR::Y]; + item->x2 = bounds.max()[NR::X]; + item->y2 = bounds.max()[NR::Y]; +} + +/** + * Point handler for canvas groups. + */ +static double +sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + const double x = p[NR::X]; + const double y = p[NR::Y]; + int x1 = (int)(x - item->canvas->close_enough); + int y1 = (int)(y - item->canvas->close_enough); + int x2 = (int)(x + item->canvas->close_enough); + int y2 = (int)(y + item->canvas->close_enough); + + double best = 0.0; + *actual_item = NULL; + + double dist = 0.0; + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + + if ((child->x1 <= x2) && (child->y1 <= y2) && (child->x2 >= x1) && (child->y2 >= y1)) { + SPCanvasItem *point_item = NULL; /* cater for incomplete item implementations */ + + int has_point; + if ((child->flags & SP_CANVAS_ITEM_VISIBLE) && SP_CANVAS_ITEM_GET_CLASS (child)->point) { + dist = sp_canvas_item_invoke_point (child, p, &point_item); + has_point = TRUE; + } else + has_point = FALSE; + + if (has_point && point_item && ((int) (dist + 0.5) <= item->canvas->close_enough)) { + best = dist; + *actual_item = point_item; + } + } + } + + return best; +} + +/** + * Renders all visible canvas group items in buf rectangle. + */ +static void +sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + + for (GList *list = group->items; list; list = list->next) { + SPCanvasItem *child = (SPCanvasItem *)list->data; + if (child->flags & SP_CANVAS_ITEM_VISIBLE) { + if ((child->x1 < buf->rect.x1) && + (child->y1 < buf->rect.y1) && + (child->x2 > buf->rect.x0) && + (child->y2 > buf->rect.y0)) { + if (SP_CANVAS_ITEM_GET_CLASS (child)->render) + SP_CANVAS_ITEM_GET_CLASS (child)->render (child, buf); + } + } + } +} + +/** + * Adds an item to a canvas group. + */ +static void +group_add (SPCanvasGroup *group, SPCanvasItem *item) +{ + gtk_object_ref (GTK_OBJECT (item)); + gtk_object_sink (GTK_OBJECT (item)); + + if (!group->items) { + group->items = g_list_append (group->items, item); + group->last = group->items; + } else { + group->last = g_list_append (group->last, item)->next; + } + + sp_canvas_item_request_update (item); +} + +/** + * Removes an item from a canvas group + */ +static void +group_remove (SPCanvasGroup *group, SPCanvasItem *item) +{ + g_return_if_fail (group != NULL); + g_return_if_fail (SP_IS_CANVAS_GROUP (group)); + g_return_if_fail (item != NULL); + + for (GList *children = group->items; children; children = children->next) { + if (children->data == item) { + + /* Unparent the child */ + + item->parent = NULL; + gtk_object_unref (GTK_OBJECT (item)); + + /* Remove it from the list */ + + if (children == group->last) group->last = children->prev; + + group->items = g_list_remove_link (group->items, children); + g_list_free (children); + break; + } + } +} + +/* SPCanvas */ + +static void sp_canvas_class_init (SPCanvasClass *klass); +static void sp_canvas_init (SPCanvas *canvas); +static void sp_canvas_destroy (GtkObject *object); + +static void sp_canvas_realize (GtkWidget *widget); +static void sp_canvas_unrealize (GtkWidget *widget); + +static void sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req); +static void sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation); + +static gint sp_canvas_button (GtkWidget *widget, GdkEventButton *event); +static gint sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event); +static gint sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event); +static gint sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event); +static gint sp_canvas_key (GtkWidget *widget, GdkEventKey *event); +static gint sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event); +static gint sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event); +static gint sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event); + +static GtkWidgetClass *canvas_parent_class; + +void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb); +void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb); + +/** + * Registers the SPCanvas class if necessary, and returns the type ID + * associated to it. + * + * \return The type ID of the SPCanvas class. + **/ +GtkType +sp_canvas_get_type (void) +{ + static GtkType canvas_type = 0; + + if (!canvas_type) { + static const GtkTypeInfo canvas_info = { + "SPCanvas", + sizeof (SPCanvas), + sizeof (SPCanvasClass), + (GtkClassInitFunc) sp_canvas_class_init, + (GtkObjectInitFunc) sp_canvas_init, + NULL, NULL, NULL + }; + + canvas_type = gtk_type_unique (GTK_TYPE_WIDGET, &canvas_info); + } + + return canvas_type; +} + +/** + * Class initialization function for SPCanvasClass. + */ +static void +sp_canvas_class_init (SPCanvasClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + canvas_parent_class = (GtkWidgetClass *)gtk_type_class (GTK_TYPE_WIDGET); + + object_class->destroy = sp_canvas_destroy; + + widget_class->realize = sp_canvas_realize; + widget_class->unrealize = sp_canvas_unrealize; + widget_class->size_request = sp_canvas_size_request; + widget_class->size_allocate = sp_canvas_size_allocate; + widget_class->button_press_event = sp_canvas_button; + widget_class->button_release_event = sp_canvas_button; + widget_class->motion_notify_event = sp_canvas_motion; + widget_class->scroll_event = sp_canvas_scroll; + widget_class->expose_event = sp_canvas_expose; + widget_class->key_press_event = sp_canvas_key; + widget_class->key_release_event = sp_canvas_key; + widget_class->enter_notify_event = sp_canvas_crossing; + widget_class->leave_notify_event = sp_canvas_crossing; + widget_class->focus_in_event = sp_canvas_focus_in; + widget_class->focus_out_event = sp_canvas_focus_out; +} + +/** + * Callback: object initialization for SPCanvas. + */ +static void +sp_canvas_init (SPCanvas *canvas) +{ + GTK_WIDGET_UNSET_FLAGS (canvas, GTK_NO_WINDOW); + GTK_WIDGET_UNSET_FLAGS (canvas, GTK_DOUBLE_BUFFERED); + GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS); + + canvas->pick_event.type = GDK_LEAVE_NOTIFY; + canvas->pick_event.crossing.x = 0; + canvas->pick_event.crossing.y = 0; + + /* Create the root item as a special case */ + canvas->root = SP_CANVAS_ITEM (gtk_type_new (sp_canvas_group_get_type ())); + canvas->root->canvas = canvas; + + gtk_object_ref (GTK_OBJECT (canvas->root)); + gtk_object_sink (GTK_OBJECT (canvas->root)); + + canvas->need_repick = TRUE; + + canvas->tiles=NULL; + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; +} + +/** + * Convenience function to remove the idle handler of a canvas. + */ +static void +remove_idle (SPCanvas *canvas) +{ + if (canvas->idle_id) { + gtk_idle_remove (canvas->idle_id); + canvas->idle_id = 0; + } +} + +/* + * Removes the transient state of the canvas (idle handler, grabs). + */ +static void +shutdown_transients (SPCanvas *canvas) +{ + /* We turn off the need_redraw flag, since if the canvas is mapped again + * it will request a redraw anyways. We do not turn off the need_update + * flag, though, because updates are not queued when the canvas remaps + * itself. + */ + if (canvas->need_redraw) { + canvas->need_redraw = FALSE; + } + if ( canvas->tiles ) free(canvas->tiles); + canvas->tiles=NULL; + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; + + if (canvas->grabbed_item) { + canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + remove_idle (canvas); +} + +/** + * Destroy handler for SPCanvas. + */ +static void +sp_canvas_destroy (GtkObject *object) +{ + SPCanvas *canvas = SP_CANVAS (object); + + if (canvas->root) { + gtk_object_unref (GTK_OBJECT (canvas->root)); + canvas->root = NULL; + } + + shutdown_transients (canvas); + + if (GTK_OBJECT_CLASS (canvas_parent_class)->destroy) + (* GTK_OBJECT_CLASS (canvas_parent_class)->destroy) (object); +} + +/** + * Returns new canvas as widget. + */ +GtkWidget * +sp_canvas_new_aa (void) +{ + SPCanvas *canvas = (SPCanvas *)gtk_type_new (sp_canvas_get_type ()); + + return (GtkWidget *) canvas; +} + +/** + * The canvas widget's realize callback. + */ +static void +sp_canvas_realize (GtkWidget *widget) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + GdkWindowAttr attributes; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gdk_rgb_get_visual (); + attributes.colormap = gdk_rgb_get_cmap (); + attributes.event_mask = (gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_PROXIMITY_IN_MASK | + GDK_PROXIMITY_OUT_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_FOCUS_CHANGE_MASK); + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + gtk_widget_set_events(widget, attributes.event_mask); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + canvas->pixmap_gc = gdk_gc_new (SP_CANVAS_WINDOW (canvas)); +} + +/** + * The canvas widget's unrealize callback. + */ +static void +sp_canvas_unrealize (GtkWidget *widget) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + shutdown_transients (canvas); + + gdk_gc_destroy (canvas->pixmap_gc); + canvas->pixmap_gc = NULL; + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) + (* GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) (widget); +} + +/** + * The canvas widget's size_request callback. + */ +static void +sp_canvas_size_request (GtkWidget *widget, GtkRequisition *req) +{ + static_cast(SP_CANVAS (widget)); + + req->width = 256; + req->height = 256; +} + +/** + * The canvas widget's size_allocate callback. + */ +static void +sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + /* Schedule redraw of new region */ + sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+allocation->width,canvas->y0+allocation->height); + if (allocation->width > widget->allocation.width) { + sp_canvas_request_redraw (canvas, + canvas->x0 + widget->allocation.width, + 0, + canvas->x0 + allocation->width, + canvas->y0 + allocation->height); + } + if (allocation->height > widget->allocation.height) { + sp_canvas_request_redraw (canvas, + 0, + canvas->y0 + widget->allocation.height, + canvas->x0 + allocation->width, + canvas->y0 + allocation->height); + } + + widget->allocation = *allocation; + + if (GTK_WIDGET_REALIZED (widget)) { + gdk_window_move_resize (widget->window, + widget->allocation.x, widget->allocation.y, + widget->allocation.width, widget->allocation.height); + } +} + +/** + * Helper that emits an event for an item in the canvas, be it the current + * item, grabbed item, or focused item, as appropriate. + */ +static int +emit_event (SPCanvas *canvas, GdkEvent *event) +{ + guint mask; + + if (canvas->grabbed_item) { + switch (event->type) { + case GDK_ENTER_NOTIFY: + mask = GDK_ENTER_NOTIFY_MASK; + break; + case GDK_LEAVE_NOTIFY: + mask = GDK_LEAVE_NOTIFY_MASK; + break; + case GDK_MOTION_NOTIFY: + mask = GDK_POINTER_MOTION_MASK; + break; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + mask = GDK_BUTTON_PRESS_MASK; + break; + case GDK_BUTTON_RELEASE: + mask = GDK_BUTTON_RELEASE_MASK; + break; + case GDK_KEY_PRESS: + mask = GDK_KEY_PRESS_MASK; + break; + case GDK_KEY_RELEASE: + mask = GDK_KEY_RELEASE_MASK; + break; + case GDK_SCROLL: + mask = GDK_SCROLL; + break; + default: + mask = 0; + break; + } + + if (!(mask & canvas->grabbed_event_mask)) return FALSE; + } + + /* Convert to world coordinates -- we have two cases because of diferent + * offsets of the fields in the event structures. + */ + + GdkEvent ev = *event; + + switch (ev.type) { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + ev.crossing.x += canvas->x0; + ev.crossing.y += canvas->y0; + break; + case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + ev.motion.x += canvas->x0; + ev.motion.y += canvas->y0; + break; + default: + break; + } + + /* Choose where we send the event */ + + /* canvas->current_item becomes NULL in some cases under Win32 + ** (e.g. if the pointer leaves the window). So this is a hack that + ** Lauris applied to SP to get around the problem. + */ + SPCanvasItem* item = NULL; + if (canvas->grabbed_item && !is_descendant (canvas->current_item, canvas->grabbed_item)) { + item = canvas->grabbed_item; + } else { + item = canvas->current_item; + } + + if (canvas->focused_item && + ((event->type == GDK_KEY_PRESS) || + (event->type == GDK_KEY_RELEASE) || + (event->type == GDK_FOCUS_CHANGE))) { + item = canvas->focused_item; + } + + /* The event is propagated up the hierarchy (for if someone connected to + * a group instead of a leaf event), and emission is stopped if a + * handler returns TRUE, just like for GtkWidget events. + */ + + gint finished = FALSE; + + while (item && !finished) { + gtk_object_ref (GTK_OBJECT (item)); + gtk_signal_emit (GTK_OBJECT (item), item_signals[ITEM_EVENT], &ev, &finished); + SPCanvasItem *parent = item->parent; + gtk_object_unref (GTK_OBJECT (item)); + item = parent; + } + + return finished; +} + +/** + * Helper that re-picks the current item in the canvas, based on the event's + * coordinates and emits enter/leave events for items as appropriate. + */ +static int +pick_current_item (SPCanvas *canvas, GdkEvent *event) +{ + double x, y; + + int retval = FALSE; + + /* Save the event in the canvas. This is used to synthesize enter and + * leave events in case the current item changes. It is also used to + * re-pick the current item if the current one gets deleted. Also, + * synthesize an enter event. + */ + if (event != &canvas->pick_event) { + if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) { + /* these fields have the same offsets in both types of events */ + + canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY; + canvas->pick_event.crossing.window = event->motion.window; + canvas->pick_event.crossing.send_event = event->motion.send_event; + canvas->pick_event.crossing.subwindow = NULL; + canvas->pick_event.crossing.x = event->motion.x; + canvas->pick_event.crossing.y = event->motion.y; + canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL; + canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; + canvas->pick_event.crossing.focus = FALSE; + canvas->pick_event.crossing.state = event->motion.state; + + /* these fields don't have the same offsets in both types of events */ + + if (event->type == GDK_MOTION_NOTIFY) { + canvas->pick_event.crossing.x_root = event->motion.x_root; + canvas->pick_event.crossing.y_root = event->motion.y_root; + } else { + canvas->pick_event.crossing.x_root = event->button.x_root; + canvas->pick_event.crossing.y_root = event->button.y_root; + } + } else { + canvas->pick_event = *event; + } + } + + /* Don't do anything else if this is a recursive call */ + if (canvas->in_repick) return retval; + + /* LeaveNotify means that there is no current item, so we don't look for one */ + if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) { + /* these fields don't have the same offsets in both types of events */ + + if (canvas->pick_event.type == GDK_ENTER_NOTIFY) { + x = canvas->pick_event.crossing.x; + y = canvas->pick_event.crossing.y; + } else { + x = canvas->pick_event.motion.x; + y = canvas->pick_event.motion.y; + } + + /* world coords */ + x += canvas->x0; + y += canvas->y0; + + /* find the closest item */ + if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { + sp_canvas_item_invoke_point (canvas->root, NR::Point(x, y), &canvas->new_current_item); + } else { + canvas->new_current_item = NULL; + } + } else { + canvas->new_current_item = NULL; + } + + if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item) { + return retval; /* current item did not change */ + } + + /* Synthesize events for old and new current items */ + + if ((canvas->new_current_item != canvas->current_item) + && (canvas->current_item != NULL) + && !canvas->left_grabbed_item) { + GdkEvent new_event; + SPCanvasItem *item; + + item = canvas->current_item; + + new_event = canvas->pick_event; + new_event.type = GDK_LEAVE_NOTIFY; + + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + canvas->in_repick = TRUE; + retval = emit_event (canvas, &new_event); + canvas->in_repick = FALSE; + } + + /* Handle the rest of cases */ + + canvas->left_grabbed_item = FALSE; + canvas->current_item = canvas->new_current_item; + + if (canvas->current_item != NULL) { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_ENTER_NOTIFY; + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + retval = emit_event (canvas, &new_event); + } + + return retval; +} + +/** + * Button event handler for the canvas. + */ +static gint +sp_canvas_button (GtkWidget *widget, GdkEventButton *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + int retval = FALSE; + + /* dispatch normally regardless of the event's window if an item has + has a pointer grab in effect */ + if (!canvas->grabbed_item && + event->window != SP_CANVAS_WINDOW (canvas)) + return retval; + + int mask; + switch (event->button) { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + case 4: + mask = GDK_BUTTON4_MASK; + break; + case 5: + mask = GDK_BUTTON5_MASK; + break; + default: + mask = 0; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + /* Pick the current item as if the button were not pressed, and + * then process the event. + */ + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + canvas->state ^= mask; + retval = emit_event (canvas, (GdkEvent *) event); + break; + + case GDK_BUTTON_RELEASE: + /* Process the event as if the button were pressed, then repick + * after the button has been released + */ + canvas->state = event->state; + retval = emit_event (canvas, (GdkEvent *) event); + event->state ^= mask; + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + event->state ^= mask; + break; + + default: + g_assert_not_reached (); + } + + return retval; +} + +/** + * Scroll event handler for the canvas. + * + * \todo FIXME: generate motion events to re-select items. + */ +static gint +sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event) +{ + return emit_event (SP_CANVAS (widget), (GdkEvent *) event); +} + +/** + * Motion event handler for the canvas. + */ +static int +sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (event->window != SP_CANVAS_WINDOW (canvas)) + return FALSE; + + if (canvas->grabbed_event_mask & GDK_POINTER_MOTION_HINT_MASK) { + gint x, y; + gdk_window_get_pointer (widget->window, &x, &y, NULL); + event->x = x; + event->y = y; + } + + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + + return emit_event (canvas, (GdkEvent *) event); +} + +/** + * Helper that draws a specific rectangular part of the canvas. + */ +static void +sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) +{ + g_return_if_fail (!canvas->need_update); + + GtkWidget *widget = GTK_WIDGET (canvas); + + int draw_x1 = MAX (xx0, canvas->x0); + int draw_y1 = MAX (yy0, canvas->y0); + int draw_x2 = MIN (xx1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width); + int draw_y2 = MIN (yy1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height); + + int bw = draw_x2 - draw_x1; + int bh = draw_y2 - draw_y1; + if ((bw < 1) || (bh < 1)) + return; + + int sw, sh; + if (canvas->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients + /* 256K is the cached buffer and we need 3 channels */ + if (bw * bh < 87381) { // 256K/3 + // We can go with single buffer + sw = bw; + sh = bh; + } else if (bw <= (16 * 341)) { + // Go with row buffer + sw = bw; + sh = 87381 / bw; + } else if (bh <= (16 * 256)) { + // Go with column buffer + sw = 87381 / bh; + sh = bh; + } else { + sw = 341; + sh = 256; + } + } else { // paths only, so 1M works faster + /* 1M is the cached buffer and we need 3 channels */ + if (bw * bh < 349525) { // 1M/3 + // We can go with single buffer + sw = bw; + sh = bh; + } else if (bw <= (16 * 682)) { + // Go with row buffer + sw = bw; + sh = 349525 / bw; + } else if (bh <= (16 * 512)) { + // Go with column buffer + sw = 349525 / bh; + sh = bh; + } else { + sw = 682; + sh = 512; + } + } + + // As we can come from expose, we have to tile here + for (int y0 = draw_y1; y0 < draw_y2; y0 += sh) { + int y1 = MIN (y0 + sh, draw_y2); + for (int x0 = draw_x1; x0 < draw_x2; x0 += sw) { + int x1 = MIN (x0 + sw, draw_x2); + + SPCanvasBuf buf; + if (canvas->rendermode != RENDERMODE_OUTLINE) { + buf.buf = nr_pixelstore_256K_new (FALSE, 0); + } else { + buf.buf = nr_pixelstore_1M_new (FALSE, 0); + } + + buf.buf_rowstride = sw * 3; + buf.rect.x0 = x0; + buf.rect.y0 = y0; + buf.rect.x1 = x1; + buf.rect.y1 = y1; + GdkColor *color = &widget->style->bg[GTK_STATE_NORMAL]; + buf.bg_color = (((color->red & 0xff00) << 8) + | (color->green & 0xff00) + | (color->blue >> 8)); + buf.is_empty = true; + + if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { + SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf); + } + + if (buf.is_empty) { + gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color); + gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + TRUE, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0); + } else { + gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0, + GDK_RGB_DITHER_MAX, + buf.buf, + sw * 3, + x0 - canvas->x0, y0 - canvas->y0); + } + + if (canvas->rendermode != RENDERMODE_OUTLINE) { + nr_pixelstore_256K_free (buf.buf); + } else { + nr_pixelstore_1M_free (buf.buf); + } + + } + } +} + +/** + * The canvas widget's expose callback. + */ +static gint +sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (!GTK_WIDGET_DRAWABLE (widget) || + (event->window != SP_CANVAS_WINDOW (canvas))) + return FALSE; + + int n_rects; + GdkRectangle *rects; + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (int i = 0; i < n_rects; i++) { + NRRectL rect; + + rect.x0 = rects[i].x + canvas->x0; + rect.y0 = rects[i].y + canvas->y0; + rect.x1 = rect.x0 + rects[i].width; + rect.y1 = rect.y0 + rects[i].height; + + if (canvas->need_update || canvas->need_redraw) { + sp_canvas_request_redraw (canvas, rect.x0, rect.y0, rect.x1, rect.y1); + } else { + /* No pending updates, draw exposed area immediately */ + sp_canvas_paint_rect (canvas, rect.x0, rect.y0, rect.x1, rect.y1); + } + } + + if (n_rects > 0) + g_free (rects); + + return FALSE; +} + +/** + * The canvas widget's keypress callback. + */ +static gint +sp_canvas_key (GtkWidget *widget, GdkEventKey *event) +{ + return emit_event (SP_CANVAS (widget), (GdkEvent *) event); +} + +/** + * Crossing event handler for the canvas. + */ +static gint +sp_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event) +{ + SPCanvas *canvas = SP_CANVAS (widget); + + if (event->window != SP_CANVAS_WINDOW (canvas)) + return FALSE; + + canvas->state = event->state; + return pick_current_item (canvas, (GdkEvent *) event); +} + +/** + * Focus in handler for the canvas. + */ +static gint +sp_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event) +{ + GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); + + SPCanvas *canvas = SP_CANVAS (widget); + + if (canvas->focused_item) { + return emit_event (canvas, (GdkEvent *) event); + } else { + return FALSE; + } +} + +/** + * Focus out handler for the canvas. + */ +static gint +sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); + + SPCanvas *canvas = SP_CANVAS (widget); + + if (canvas->focused_item) + return emit_event (canvas, (GdkEvent *) event); + else + return FALSE; +} + +/** + * Helper that repaints the areas in the canvas that need it. + */ +static int +paint (SPCanvas *canvas) +{ + if (canvas->need_update) { + sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0); + canvas->need_update = FALSE; + } + + if (!canvas->need_redraw) + return TRUE; + + GtkWidget const *widget = GTK_WIDGET(canvas); + int const canvas_x1 = canvas->x0 + widget->allocation.width; + int const canvas_y1 = canvas->y0 + widget->allocation.height; + + NRRectL topaint; + topaint.x0 = topaint.y0 = topaint.x1 = topaint.y1 = 0; + + for (int j=canvas->tTop&(~3);jtBottom;j+=4) { + for (int i=canvas->tLeft&(~3);itRight;i+=4) { + int mode=0; + + int pl=i+1,pr=i,pt=j+4,pb=j; + for (int l=MAX(j,canvas->tTop);ltBottom);l++) { + for (int k=MAX(i,canvas->tLeft);ktRight);k++) { + if ( canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH] ) { + mode|=1<<((k-i)+(l-j)*4); + if ( k < pl ) pl=k; + if ( k+1 > pr ) pr=k+1; + if ( l < pt ) pt=l; + if ( l+1 > pb ) pb=l+1; + } + canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH]=0; + } + } + + if ( mode ) { + NRRectL tile; + tile.x0 = MAX (pl*32, canvas->x0); + tile.y0 = MAX (pt*32, canvas->y0); + tile.x1 = MIN (pr*32, canvas_x1); + tile.y1 = MIN (pb*32, canvas_y1); + if ((tile.x0 < tile.x1) && (tile.y0 < tile.y1)) { + nr_rect_l_union (&topaint, &topaint, &tile); + } + + } + } + } + + sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1); + + canvas->need_redraw = FALSE; + return TRUE; +} + +/** + * Helper that invokes update, paint, and repick on canvas. + */ +static int +do_update (SPCanvas *canvas) +{ + /* Cause the update if necessary */ + if (canvas->need_update) { + sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0); + canvas->need_update = FALSE; + } + + /* Paint if able to */ + if (GTK_WIDGET_DRAWABLE (canvas)) { + return paint (canvas); + } + + /* Pick new current item */ + while (canvas->need_repick) { + canvas->need_repick = FALSE; + pick_current_item (canvas, &canvas->pick_event); + } + + return TRUE; +} + +/** + * Idle handler for the canvas that deals with pending updates and redraws. + */ +static gint +idle_handler (gpointer data) +{ + GDK_THREADS_ENTER (); + + SPCanvas *canvas = SP_CANVAS (data); + + const int ret = do_update (canvas); + + if (ret) { + /* Reset idle id */ + canvas->idle_id = 0; + } + + GDK_THREADS_LEAVE (); + + return !ret; +} + +/** + * Convenience function to add an idle handler to a canvas. + */ +static void +add_idle (SPCanvas *canvas) +{ + if (canvas->idle_id != 0) + return; + + canvas->idle_id = gtk_idle_add_priority (sp_canvas_update_priority, idle_handler, canvas); +} + +/** + * Returns the root group of the specified canvas. + */ +SPCanvasGroup * +sp_canvas_root (SPCanvas *canvas) +{ + g_return_val_if_fail (canvas != NULL, NULL); + g_return_val_if_fail (SP_IS_CANVAS (canvas), NULL); + + return SP_CANVAS_GROUP (canvas->root); +} + +/** + * Scrolls canvas to specific position. + */ +void +sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + int ix = (int) (cx + 0.5); + int iy = (int) (cy + 0.5); + int dx = ix - canvas->x0; + int dy = iy - canvas->y0; + + canvas->dx0 = cx; + canvas->dy0 = cy; + canvas->x0 = ix; + canvas->y0 = iy; + + sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+canvas->widget.allocation.width,canvas->y0+canvas->widget.allocation.height); + + if (!clear) { + // scrolling without zoom; redraw only the newly exposed areas + if ((dx != 0) || (dy != 0)) { + int width, height; + width = canvas->widget.allocation.width; + height = canvas->widget.allocation.height; + if (GTK_WIDGET_REALIZED (canvas)) { + gdk_window_scroll (SP_CANVAS_WINDOW (canvas), -dx, -dy); + gdk_window_process_updates (SP_CANVAS_WINDOW (canvas), TRUE); + } + if (dx < 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix - dx, iy + height); + } else if (dx > 0) { + sp_canvas_request_redraw (canvas, ix + width - dx, iy + 0, ix + width, iy + height); + } + if (dy < 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix + width, iy - dy); + } else if (dy > 0) { + sp_canvas_request_redraw (canvas, ix + 0, iy + height - dy, ix + width, iy + height); + } + } + } else { + // scrolling as part of zoom; do nothing here - the next do_update will perform full redraw + } +} + +/** + * Updates canvas if necessary. + */ +void +sp_canvas_update_now (SPCanvas *canvas) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (!(canvas->need_update || + canvas->need_redraw)) + return; + + remove_idle (canvas); + do_update (canvas); +} + +/** + * Update callback for canvas widget. + */ +static void +sp_canvas_request_update (SPCanvas *canvas) +{ + canvas->need_update = TRUE; + add_idle (canvas); +} + +/** + * Forces redraw of rectangular canvas area. + */ +void +sp_canvas_request_redraw (SPCanvas *canvas, int x0, int y0, int x1, int y1) +{ + NRRectL bbox; + NRRectL visible; + NRRectL clip; + + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (!GTK_WIDGET_DRAWABLE (canvas)) return; + if ((x0 >= x1) || (y0 >= y1)) return; + + bbox.x0 = x0; + bbox.y0 = y0; + bbox.x1 = x1; + bbox.y1 = y1; + + visible.x0 = canvas->x0; + visible.y0 = canvas->y0; + visible.x1 = visible.x0 + GTK_WIDGET (canvas)->allocation.width; + visible.y1 = visible.y0 + GTK_WIDGET (canvas)->allocation.height; + + nr_rect_l_intersect (&clip, &bbox, &visible); + + sp_canvas_dirty_rect(canvas,x0,y0,x1,y1); + add_idle (canvas); +} + +/** + * Sets world coordinates from win and canvas. + */ +void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (worldx) *worldx = canvas->x0 + winx; + if (worldy) *worldy = canvas->y0 + winy; +} + +/** + * Sets win coordinates from world and canvas. + */ +void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy) +{ + g_return_if_fail (canvas != NULL); + g_return_if_fail (SP_IS_CANVAS (canvas)); + + if (winx) *winx = worldx - canvas->x0; + if (winy) *winy = worldy - canvas->y0; +} + +/** + * Converts point from win to world coordinates. + */ +NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win) +{ + g_assert (canvas != NULL); + g_assert (SP_IS_CANVAS (canvas)); + + return NR::Point(canvas->x0 + win[0], canvas->y0 + win[1]); +} + +/** + * Converts point from world to win coordinates. + */ +NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world) +{ + g_assert (canvas != NULL); + g_assert (SP_IS_CANVAS (canvas)); + + return NR::Point(world[0] - canvas->x0, world[1] - canvas->y0); +} + +/** + * Returns true if point given in world coordinates is inside window. + */ +bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world) +{ + g_assert( canvas != NULL ); + g_assert(SP_IS_CANVAS(canvas)); + + using NR::X; + using NR::Y; + GtkWidget const &w = *GTK_WIDGET(canvas); + return ( ( canvas->x0 <= world[X] ) && + ( canvas->y0 <= world[Y] ) && + ( world[X] < canvas->x0 + w.allocation.width ) && + ( world[Y] < canvas->y0 + w.allocation.height ) ); +} + +/** + * Return canvas window coordinates as NRRect. + */ +NR::Rect SPCanvas::getViewbox() const +{ + GtkWidget const *w = GTK_WIDGET(this); + + return NR::Rect(NR::Point(dx0, dy0), + NR::Point(dx0 + w->allocation.width, dy0 + w->allocation.height)); +} + +inline int sp_canvas_tile_floor(int x) +{ + return (x&(~31))/32; +} + +inline int sp_canvas_tile_ceil(int x) +{ + return ((x+31)&(~31))/32; +} + +/** + * Helper that changes tile size for canvas redraw. + */ +void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) +{ + if ( nl >= nr || nt >= nb ) { + if ( canvas->tiles ) free(canvas->tiles); + canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; + canvas->tileH=canvas->tileV=0; + canvas->tiles=NULL; + return; + } + int tl=sp_canvas_tile_floor(nl); + int tt=sp_canvas_tile_floor(nt); + int tr=sp_canvas_tile_ceil(nr); + int tb=sp_canvas_tile_ceil(nb); + + int nh=tr-tl,nv=tb-tt; + uint8_t* ntiles=(uint8_t*)malloc(nh*nv*sizeof(uint8_t)); + for (int i=tl;i= canvas->tLeft && i < canvas->tRight && j >= canvas->tTop && j < canvas->tBottom ) { + ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]; + } else { + ntiles[ind]=0; + } + } + } + if ( canvas->tiles ) free(canvas->tiles); + canvas->tiles=ntiles; + canvas->tLeft=tl; + canvas->tTop=tt; + canvas->tRight=tr; + canvas->tBottom=tb; + canvas->tileH=nh; + canvas->tileV=nv; +} + +/** + * Helper that marks specific canvas rectangle for redraw. + */ +void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) +{ + if ( nl >= nr || nt >= nb ) { + return; + } + int tl=sp_canvas_tile_floor(nl); + int tt=sp_canvas_tile_floor(nt); + int tr=sp_canvas_tile_ceil(nr); + int tb=sp_canvas_tile_ceil(nb); + if ( tl >= canvas->tRight || tr <= canvas->tLeft || tt >= canvas->tBottom || tb <= canvas->tTop ) return; + if ( tl < canvas->tLeft ) tl=canvas->tLeft; + if ( tr > canvas->tRight ) tr=canvas->tRight; + if ( tt < canvas->tTop ) tt=canvas->tTop; + if ( tb > canvas->tBottom ) tb=canvas->tBottom; + + canvas->need_redraw = TRUE; + + for (int i=tl;itiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=1; + } + } +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/sp-canvas.h b/src/display/sp-canvas.h new file mode 100644 index 000000000..905c9272f --- /dev/null +++ b/src/display/sp-canvas.h @@ -0,0 +1,186 @@ +#ifndef __SP_CANVAS_H__ +#define __SP_CANVAS_H__ + +/** \file + * SPCanvas, SPCanvasBuf, and SPCanvasItem. + * + * Authors: + * Federico Mena + * Raph Levien + * Lauris Kaplinski + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct SPCanvas; +struct SPCanvasGroup; + +enum { + SP_CANVAS_UPDATE_REQUESTED = 1 << 0, + SP_CANVAS_UPDATE_AFFINE = 1 << 1 +}; + +/** + * The canvas buf contains the actual pixels. + */ +struct SPCanvasBuf{ + guchar *buf; + int buf_rowstride; + NRRectL rect; + /// Background color, given as 0xrrggbb + guint32 bg_color; + // If empty, ignore contents of buffer and use a solid area of bg_color + bool is_empty; +}; + +/** + * An SPCanvasItem refers to a SPCanvas and to its parent item; it has + * four coordinates, a bounding rectangle, and a transformation matrix. + */ +struct SPCanvasItem : public GtkObject { + SPCanvas *canvas; + SPCanvasItem *parent; + + double x1, y1, x2, y2; + NR::Rect bounds; + NR::Matrix xform; +}; + +/** + * The vtable of an SPCanvasItem. + */ +struct SPCanvasItemClass : public GtkObjectClass { + void (* update) (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); + + void (* render) (SPCanvasItem *item, SPCanvasBuf *buf); + double (* point) (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item); + + int (* event) (SPCanvasItem *item, GdkEvent *event); +}; + +SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...); + +#define sp_canvas_item_set gtk_object_set + +void sp_canvas_item_affine_absolute(SPCanvasItem *item, NR::Matrix const &aff); + +void sp_canvas_item_raise(SPCanvasItem *item, int positions); +void sp_canvas_item_lower(SPCanvasItem *item, int positions); +void sp_canvas_item_show(SPCanvasItem *item); +void sp_canvas_item_hide(SPCanvasItem *item); +int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime); +void sp_canvas_item_ungrab(SPCanvasItem *item, guint32 etime); + +NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item); + +void sp_canvas_item_grab_focus(SPCanvasItem *item); + +void sp_canvas_item_request_update(SPCanvasItem *item); + +/* get item z-order in parent group */ + +gint sp_canvas_item_order(SPCanvasItem * item); + + +// SPCanvas ------------------------------------------------- +/** + * Port of GnomeCanvas for inkscape needs. + */ +struct SPCanvas { + GtkWidget widget; + + guint idle_id; + + SPCanvasItem *root; + + double dx0, dy0; + int x0, y0; + + /* Area that needs redrawing, stored as a microtile array */ + int tLeft,tTop,tRight,tBottom; + int tileH,tileV; + uint8_t *tiles; + + /* Last known modifier state, for deferred repick when a button is down */ + int state; + + /* The item containing the mouse pointer, or NULL if none */ + SPCanvasItem *current_item; + + /* Item that is about to become current (used to track deletions and such) */ + SPCanvasItem *new_current_item; + + /* Item that holds a pointer grab, or NULL if none */ + SPCanvasItem *grabbed_item; + + /* Event mask specified when grabbing an item */ + guint grabbed_event_mask; + + /* If non-NULL, the currently focused item */ + SPCanvasItem *focused_item; + + /* Event on which selection of current item is based */ + GdkEvent pick_event; + + int close_enough; + + /* GC for temporary draw pixmap */ + GdkGC *pixmap_gc; + + unsigned int need_update : 1; + unsigned int need_redraw : 1; + unsigned int need_repick : 1; + + /* For use by internal pick_current_item() function */ + unsigned int left_grabbed_item : 1; + /* For use by internal pick_current_item() function */ + unsigned int in_repick : 1; + + int rendermode; + + NR::Rect getViewbox() const; +}; + +GtkWidget *sp_canvas_new_aa(); + +SPCanvasGroup *sp_canvas_root(SPCanvas *canvas); + +void sp_canvas_scroll_to(SPCanvas *canvas, double cx, double cy, unsigned int clear); +void sp_canvas_update_now(SPCanvas *canvas); + +void sp_canvas_request_redraw(SPCanvas *canvas, int x1, int y1, int x2, int y2); + +void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy); +void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy); + +NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win); +NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world); + +bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/display/sp-ctrlline.cpp b/src/display/sp-ctrlline.cpp new file mode 100644 index 000000000..0a278bb69 --- /dev/null +++ b/src/display/sp-ctrlline.cpp @@ -0,0 +1,217 @@ +#define __INKSCAPE_CTRLLINE_C__ + +/* + * Simple straight line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +/* + * TODO: + * Draw it by hand - we really do not need aa stuff for it + * + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sp-ctrlline.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include + +struct SPCtrlLine : public SPCanvasItem{ + guint32 rgba; + NRPoint s, e; + Shape* shp; +}; + +struct SPCtrlLineClass : public SPCanvasItemClass{}; + +static void sp_ctrlline_class_init (SPCtrlLineClass *klass); +static void sp_ctrlline_init (SPCtrlLine *ctrlline); +static void sp_ctrlline_destroy (GtkObject *object); + +static void sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrlline_get_type (void) +{ + static GtkType type = 0; + + if (!type) { + GtkTypeInfo info = { + "SPCtrlLine", + sizeof (SPCtrlLine), + sizeof (SPCtrlLineClass), + (GtkClassInitFunc) sp_ctrlline_class_init, + (GtkObjectInitFunc) sp_ctrlline_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_ctrlline_class_init (SPCtrlLineClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_ctrlline_destroy; + + item_class->update = sp_ctrlline_update; + item_class->render = sp_ctrlline_render; +} + +static void +sp_ctrlline_init (SPCtrlLine *ctrlline) +{ + ctrlline->rgba = 0x0000ff7f; + ctrlline->s.x = ctrlline->s.y = ctrlline->e.x = ctrlline->e.y = 0.0; + ctrlline->shp=NULL; +} + +static void +sp_ctrlline_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRLLINE (object)); + + SPCtrlLine *ctrlline = SP_CTRLLINE (object); + + if (ctrlline->shp) { + delete ctrlline->shp; + ctrlline->shp = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrlline_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCtrlLine *ctrlline = SP_CTRLLINE (item); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + + if (ctrlline->shp) { + sp_canvas_prepare_buffer (buf); + nr_pixblock_render_ctrl_rgba (ctrlline->shp,ctrlline->rgba,area,(char*)buf->buf, buf->buf_rowstride); + } +} + +static void +sp_ctrlline_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + NRRect dbox; + + SPCtrlLine *cl = SP_CTRLLINE (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + dbox.x0=dbox.x1=dbox.y0=dbox.y1=0; + if (cl->shp) { + delete cl->shp; + cl->shp = NULL; + } + Path* thePath = new Path; + thePath->MoveTo(NR::Point(cl->s.x, cl->s.y) * affine); + thePath->LineTo(NR::Point(cl->e.x, cl->e.y) * affine); + + thePath->Convert(1.0); + if ( cl->shp == NULL ) cl->shp=new Shape; + thePath->Stroke(cl->shp,false,0.5,join_straight,butt_straight,20.0,false); + cl->shp->CalcBBox(); + if ( cl->shp->leftX < cl->shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX; + dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY; + } else { + if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX; + if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX; + if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY; + if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY; + } + } + delete thePath; + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLLINE (cl)); + + if (rgba != cl->rgba) { + SPCanvasItem *item; + cl->rgba = rgba; + item = SP_CANVAS_ITEM (cl); + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + } +} + +#define EPSILON 1e-6 +#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON) + +void +sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLLINE (cl)); + + if (DIFFER (x0, cl->s.x) || DIFFER (y0, cl->s.y) || DIFFER (x1, cl->e.x) || DIFFER (y1, cl->e.y)) { + cl->s.x = x0; + cl->s.y = y0; + cl->e.x = x1; + cl->e.y = y1; + sp_canvas_item_request_update (SP_CANVAS_ITEM (cl)); + } +} + +void +sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end) +{ + sp_ctrlline_set_coords(cl, start[0], start[1], end[0], end[1]); +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-ctrlline.h b/src/display/sp-ctrlline.h new file mode 100644 index 000000000..500fbf08f --- /dev/null +++ b/src/display/sp-ctrlline.h @@ -0,0 +1,45 @@ +#ifndef __INKSCAPE_CTRLLINE_H__ +#define __INKSCAPE_CTRLLINE_H__ + +/* + * Simple straight line + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include "sp-canvas.h" + + + +#define SP_TYPE_CTRLLINE (sp_ctrlline_get_type ()) +#define SP_CTRLLINE(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLLINE, SPCtrlLine)) +#define SP_IS_CTRLLINE(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLLINE)) + +struct SPCtrlLine; +struct SPCtrlLineClass; + +GtkType sp_ctrlline_get_type (void); + +void sp_ctrlline_set_rgba32 (SPCtrlLine *cl, guint32 rgba); +void sp_ctrlline_set_coords (SPCtrlLine *cl, gdouble x0, gdouble y0, gdouble x1, gdouble y1); +void sp_ctrlline_set_coords (SPCtrlLine *cl, const NR::Point start, const NR::Point end); + + + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-ctrlquadr.cpp b/src/display/sp-ctrlquadr.cpp new file mode 100644 index 000000000..e9488cdb5 --- /dev/null +++ b/src/display/sp-ctrlquadr.cpp @@ -0,0 +1,210 @@ +#define __INKSCAPE_CTRLQUADR_C__ + +/* + * Quadrilateral + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 authors + * + * Released under GNU GPL + */ + +#include "display-forward.h" +#include "sp-canvas-util.h" +#include "sp-ctrlquadr.h" + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include + +struct SPCtrlQuadr : public SPCanvasItem{ + guint32 rgba; + NR::Point p1, p2, p3, p4; + Shape* shp; +}; + +struct SPCtrlQuadrClass : public SPCanvasItemClass{}; + +static void sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass); +static void sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr); +static void sp_ctrlquadr_destroy (GtkObject *object); + +static void sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags); +static void sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf); + +static SPCanvasItemClass *parent_class; + +GtkType +sp_ctrlquadr_get_type (void) +{ + static GtkType type = 0; + + if (!type) { + GtkTypeInfo info = { + "SPCtrlQuadr", + sizeof (SPCtrlQuadr), + sizeof (SPCtrlQuadrClass), + (GtkClassInitFunc) sp_ctrlquadr_class_init, + (GtkObjectInitFunc) sp_ctrlquadr_init, + NULL, NULL, NULL + }; + type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info); + } + return type; +} + +static void +sp_ctrlquadr_class_init (SPCtrlQuadrClass *klass) +{ + GtkObjectClass *object_class = (GtkObjectClass *) klass; + SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass; + + parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM); + + object_class->destroy = sp_ctrlquadr_destroy; + + item_class->update = sp_ctrlquadr_update; + item_class->render = sp_ctrlquadr_render; +} + +static void +sp_ctrlquadr_init (SPCtrlQuadr *ctrlquadr) +{ + ctrlquadr->rgba = 0x000000ff; + ctrlquadr->p1 = NR::Point(0, 0); + ctrlquadr->p2 = NR::Point(0, 0); + ctrlquadr->p3 = NR::Point(0, 0); + ctrlquadr->p4 = NR::Point(0, 0); + ctrlquadr->shp=NULL; +} + +static void +sp_ctrlquadr_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (object)); + + SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (object); + + if (ctrlquadr->shp) { + delete ctrlquadr->shp; + ctrlquadr->shp = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +sp_ctrlquadr_render (SPCanvasItem *item, SPCanvasBuf *buf) +{ + SPCtrlQuadr *ctrlquadr = SP_CTRLQUADR (item); + + NRRectL area; + area.x0=buf->rect.x0; + area.x1=buf->rect.x1; + area.y0=buf->rect.y0; + area.y1=buf->rect.y1; + + if (ctrlquadr->shp) { + sp_canvas_prepare_buffer (buf); + nr_pixblock_render_ctrl_rgba (ctrlquadr->shp,ctrlquadr->rgba,area,(char*)buf->buf, buf->buf_rowstride); + } +} + +static void +sp_ctrlquadr_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) +{ + NRRect dbox; + + SPCtrlQuadr *cl = SP_CTRLQUADR (item); + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + + if (parent_class->update) + (* parent_class->update) (item, affine, flags); + + sp_canvas_item_reset_bounds (item); + + dbox.x0=dbox.x1=dbox.y0=dbox.y1=0; + if (cl->shp) { + delete cl->shp; + cl->shp = NULL; + } + Path* thePath = new Path; + thePath->MoveTo(cl->p1 * affine); + thePath->LineTo(cl->p2 * affine); + thePath->LineTo(cl->p3 * affine); + thePath->LineTo(cl->p4 * affine); + thePath->LineTo(cl->p1 * affine); + + thePath->Convert(1.0); + + if ( cl->shp == NULL ) cl->shp=new Shape; + thePath->Fill(cl->shp, 0); + + cl->shp->CalcBBox(); + if ( cl->shp->leftX < cl->shp->rightX ) { + if ( dbox.x0 >= dbox.x1 ) { + dbox.x0=cl->shp->leftX;dbox.x1=cl->shp->rightX; + dbox.y0=cl->shp->topY;dbox.y1=cl->shp->bottomY; + } else { + if ( cl->shp->leftX < dbox.x0 ) dbox.x0=cl->shp->leftX; + if ( cl->shp->rightX > dbox.x1 ) dbox.x1=cl->shp->rightX; + if ( cl->shp->topY < dbox.y0 ) dbox.y0=cl->shp->topY; + if ( cl->shp->bottomY > dbox.y1 ) dbox.y1=cl->shp->bottomY; + } + } + delete thePath; + + item->x1 = (int)dbox.x0; + item->y1 = (int)dbox.y0; + item->x2 = (int)dbox.x1; + item->y2 = (int)dbox.y1; + + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); +} + +void +sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (cl)); + + if (rgba != cl->rgba) { + SPCanvasItem *item; + cl->rgba = rgba; + item = SP_CANVAS_ITEM (cl); + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2); + } +} + +void +sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, NR::Point p1, NR::Point p2, NR::Point p3, NR::Point p4) +{ + g_return_if_fail (cl != NULL); + g_return_if_fail (SP_IS_CTRLQUADR (cl)); + + if (p1 != cl->p1 || p2 != cl->p2 || p3 != cl->p3 || p4 != cl->p4) { + cl->p1 = p1; + cl->p2 = p2; + cl->p3 = p3; + cl->p4 = p4; + sp_canvas_item_request_update (SP_CANVAS_ITEM (cl)); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/sp-ctrlquadr.h b/src/display/sp-ctrlquadr.h new file mode 100644 index 000000000..38951448c --- /dev/null +++ b/src/display/sp-ctrlquadr.h @@ -0,0 +1,43 @@ +#ifndef __INKSCAPE_CTRLQUADR_H__ +#define __INKSCAPE_CTRLQUADR_H__ + +/* + * Quadrilateral + * + * Authors: + * bulia byak + * + * Copyright (C) 2005 authors + * + * Released under GNU GPL + */ + +#include "sp-canvas.h" + + + +#define SP_TYPE_CTRLQUADR (sp_ctrlquadr_get_type ()) +#define SP_CTRLQUADR(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_CTRLQUADR, SPCtrlQuadr)) +#define SP_IS_CTRLQUADR(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_CTRLQUADR)) + +struct SPCtrlQuadr; +struct SPCtrlQuadrClass; + +GtkType sp_ctrlquadr_get_type (void); + +void sp_ctrlquadr_set_rgba32 (SPCtrlQuadr *cl, guint32 rgba); +void sp_ctrlquadr_set_coords (SPCtrlQuadr *cl, const NR::Point p1, const NR::Point p2, const NR::Point p3, const NR::Point p4); + + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/display/testnr.cpp b/src/display/testnr.cpp new file mode 100644 index 000000000..3a3478d28 --- /dev/null +++ b/src/display/testnr.cpp @@ -0,0 +1,24 @@ +#include +#include "sp-arena.h" + +int +main (int argc, char ** argv) +{ + GtkWidget * w, * c; + + gtk_init (&argc, &argv); + + w = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + c = sp_arena_new (); + gtk_widget_show (c); + + gtk_container_add (GTK_CONTAINER (w), c); + + gtk_widget_show (w); + + gtk_main (); + + return 0; +} + -- cgit v1.2.3