536 lines
16 KiB
C++
536 lines
16 KiB
C++
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <limits>
|
|
#include "agg_rendering_buffer.h"
|
|
#include "agg_trans_viewport.h"
|
|
#include "agg_path_storage.h"
|
|
#include "agg_conv_transform.h"
|
|
#include "agg_conv_curve.h"
|
|
#include "agg_conv_stroke.h"
|
|
#include "agg_gsv_text.h"
|
|
#include "agg_scanline_u.h"
|
|
#include "agg_scanline_bin.h"
|
|
#include "agg_renderer_scanline.h"
|
|
#include "agg_rasterizer_outline_aa.h"
|
|
#include "agg_rasterizer_scanline_aa.h"
|
|
#include "agg_span_allocator.h"
|
|
#include "agg_pixfmt_rgba.h"
|
|
#include "agg_bounding_rect.h"
|
|
#include "platform/agg_platform_support.h"
|
|
|
|
#define AGG_BGRA32
|
|
//#define AGG_BGRA128
|
|
#include "pixel_formats.h"
|
|
|
|
enum { flip_y = false };
|
|
|
|
|
|
|
|
namespace agg
|
|
{
|
|
struct path_style
|
|
{
|
|
unsigned path_id;
|
|
int left_fill;
|
|
int right_fill;
|
|
int line;
|
|
};
|
|
|
|
class compound_shape
|
|
{
|
|
public:
|
|
~compound_shape()
|
|
{
|
|
if(m_fd)
|
|
{
|
|
fclose(m_fd);
|
|
}
|
|
}
|
|
|
|
compound_shape() :
|
|
m_path(),
|
|
m_affine(),
|
|
m_curve(m_path),
|
|
m_trans(m_curve, m_affine),
|
|
m_styles(),
|
|
m_min_style(std::numeric_limits<int>::max()),
|
|
m_max_style(std::numeric_limits<int>::min())
|
|
{}
|
|
|
|
bool open(const char* fname)
|
|
{
|
|
m_fd = fopen(fname, "r");
|
|
return m_fd != 0;
|
|
}
|
|
|
|
bool read_next()
|
|
{
|
|
m_path.remove_all();
|
|
m_styles.remove_all();
|
|
m_min_style = std::numeric_limits<int>::max();
|
|
m_max_style = std::numeric_limits<int>::min();
|
|
|
|
const char space[] = " \t\n\r";
|
|
double ax, ay, cx, cy;
|
|
if(m_fd)
|
|
{
|
|
char buf[1024];
|
|
char* ts;
|
|
|
|
for(;;)
|
|
{
|
|
if(fgets(buf, 1022, m_fd) == 0) return false;
|
|
if(buf[0] == '=') break;
|
|
}
|
|
|
|
while(fgets(buf, 1022, m_fd))
|
|
{
|
|
if(buf[0] == '!') break;
|
|
if(buf[0] == 'P')
|
|
{
|
|
// BeginPath
|
|
path_style style;
|
|
style.path_id = m_path.start_new_path();
|
|
ts = strtok(buf, space); // Path;
|
|
ts = strtok(0, space); // left_style
|
|
style.left_fill = atoi(ts);
|
|
ts = strtok(0, space); // right_style
|
|
style.right_fill = atoi(ts);
|
|
ts = strtok(0, space); // line_style
|
|
style.line = atoi(ts);
|
|
ts = strtok(0, space); // ax
|
|
ax = atof(ts);
|
|
ts = strtok(0, space); // ay
|
|
ay = atof(ts);
|
|
m_path.move_to(ax, ay);
|
|
m_styles.add(style);
|
|
if(style.left_fill >= 0)
|
|
{
|
|
if(style.left_fill < m_min_style) m_min_style = style.left_fill;
|
|
if(style.left_fill > m_max_style) m_max_style = style.left_fill;
|
|
}
|
|
if(style.right_fill >= 0)
|
|
{
|
|
if(style.right_fill < m_min_style) m_min_style = style.right_fill;
|
|
if(style.right_fill > m_max_style) m_max_style = style.right_fill;
|
|
}
|
|
}
|
|
|
|
|
|
if(buf[0] == 'C')
|
|
{
|
|
ts = strtok(buf, space); // Curve;
|
|
ts = strtok(0, space); // cx
|
|
cx = atof(ts);
|
|
ts = strtok(0, space); // cy
|
|
cy = atof(ts);
|
|
ts = strtok(0, space); // ax
|
|
ax = atof(ts);
|
|
ts = strtok(0, space); // ay
|
|
ay = atof(ts);
|
|
m_path.curve3(cx, cy, ax, ay);
|
|
}
|
|
|
|
if(buf[0] == 'L')
|
|
{
|
|
ts = strtok(buf, space); // Line;
|
|
ts = strtok(0, space); // ax
|
|
ax = atof(ts);
|
|
ts = strtok(0, space); // ay
|
|
ay = atof(ts);
|
|
m_path.line_to(ax, ay);
|
|
}
|
|
|
|
if(buf[0] == '<')
|
|
{
|
|
// EndPath
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unsigned operator [] (unsigned i) const
|
|
{
|
|
return m_styles[i].path_id;
|
|
}
|
|
|
|
unsigned paths() const { return m_styles.size(); }
|
|
const path_style& style(unsigned i) const
|
|
{
|
|
return m_styles[i];
|
|
}
|
|
|
|
int min_style() const { return m_min_style; }
|
|
int max_style() const { return m_max_style; }
|
|
|
|
void rewind(unsigned path_id)
|
|
{
|
|
m_trans.rewind(path_id);
|
|
}
|
|
|
|
unsigned vertex(double* x, double* y)
|
|
{
|
|
return m_trans.vertex(x, y);
|
|
}
|
|
|
|
double scale() const
|
|
{
|
|
return m_affine.scale();
|
|
}
|
|
|
|
void scale(double w, double h)
|
|
{
|
|
m_affine.reset();
|
|
double x1, y1, x2, y2;
|
|
bounding_rect(m_path, *this, 0, m_styles.size(),
|
|
&x1, &y1, &x2, &y2);
|
|
if(x1 < x2 && y1 < y2)
|
|
{
|
|
trans_viewport vp;
|
|
vp.preserve_aspect_ratio(0.5, 0.5, aspect_ratio_meet);
|
|
vp.world_viewport(x1, y1, x2, y2);
|
|
vp.device_viewport(0, 0, w, h);
|
|
m_affine = vp.to_affine();
|
|
}
|
|
m_curve.approximation_scale(m_affine.scale());
|
|
}
|
|
|
|
void approximation_scale(double s)
|
|
{
|
|
m_curve.approximation_scale(m_affine.scale() * s);
|
|
}
|
|
|
|
int hit_test(double x, double y, double r)
|
|
{
|
|
m_affine.inverse_transform(&x, &y);
|
|
r /= m_affine.scale();
|
|
unsigned i;
|
|
for(i = 0; i < m_path.total_vertices(); i++)
|
|
{
|
|
double vx, vy;
|
|
unsigned cmd = m_path.vertex(i, &vx, &vy);
|
|
if(is_vertex(cmd))
|
|
{
|
|
if(calc_distance(x, y, vx, vy) <= r)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void modify_vertex(unsigned i, double x, double y)
|
|
{
|
|
m_affine.inverse_transform(&x, &y);
|
|
m_path.modify_vertex(i, x, y);
|
|
}
|
|
|
|
private:
|
|
path_storage m_path;
|
|
trans_affine m_affine;
|
|
conv_curve<path_storage> m_curve;
|
|
conv_transform<conv_curve<path_storage> > m_trans;
|
|
pod_bvector<path_style> m_styles;
|
|
double m_x1, m_y1, m_x2, m_y2;
|
|
int m_min_style;
|
|
int m_max_style;
|
|
|
|
FILE* m_fd;
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class the_application : public agg::platform_support
|
|
{
|
|
|
|
public:
|
|
agg::compound_shape m_shape;
|
|
color_type m_colors[100];
|
|
agg::trans_affine m_scale;
|
|
int m_point_idx;
|
|
|
|
|
|
the_application(agg::pix_format_e format, bool flip_y) :
|
|
agg::platform_support(format, flip_y),
|
|
m_point_idx(-1)
|
|
{
|
|
for(unsigned i = 0; i < 100; i++)
|
|
{
|
|
m_colors[i] = agg::srgba8(
|
|
(rand() & 0xFF),
|
|
(rand() & 0xFF),
|
|
(rand() & 0xFF),
|
|
230);
|
|
|
|
m_colors[i].premultiply();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool open(const char* fname)
|
|
{
|
|
return m_shape.open(full_file_name(fname));
|
|
}
|
|
|
|
void read_next()
|
|
{
|
|
m_shape.read_next();
|
|
m_shape.scale(width(), height());
|
|
}
|
|
|
|
virtual void on_draw()
|
|
{
|
|
typedef agg::renderer_base<pixfmt_pre> renderer_base;
|
|
typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_scanline;
|
|
typedef agg::scanline_u8 scanline;
|
|
|
|
pixfmt_pre pixf(rbuf_window());
|
|
renderer_base ren_base(pixf);
|
|
ren_base.clear(agg::rgba(1.0, 1.0, 0.95));
|
|
renderer_scanline ren(ren_base);
|
|
|
|
agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl> ras;
|
|
agg::scanline_u8 sl;
|
|
agg::conv_transform<agg::compound_shape> shape(m_shape, m_scale);
|
|
agg::conv_stroke<agg::conv_transform<agg::compound_shape> > stroke(shape);
|
|
|
|
m_shape.approximation_scale(m_scale.scale());
|
|
|
|
unsigned i;
|
|
agg::path_storage tmp_path;
|
|
|
|
ras.clip_box(0, 0, width(), height());
|
|
|
|
// This is an alternative method of Flash rasterization.
|
|
// We decompose the compound shape into separate paths
|
|
// and select the ones that fit the given style (left or right).
|
|
// So that, we form a sub-shape and draw it as a whole.
|
|
//
|
|
// Here the regular scanline rasterizer is used, but it doesn't
|
|
// automatically close the polygons. So that, the rasterizer
|
|
// actually works with a set of polylines instead of polygons.
|
|
// Of course, the data integrity must be preserved, that is,
|
|
// the polylines must eventually form a closed contour
|
|
// (or a set of closed contours). So that, first we set
|
|
// auto_close(false);
|
|
//
|
|
// The second important thing is that one path can be rasterized
|
|
// twice, if it has both, left and right fill. Sometimes the
|
|
// path has equal left and right fill, so that, the same path
|
|
// will be added twice even for a single sub-shape. If the
|
|
// rasterizer can tolerate these degenerates you can add them,
|
|
// but it's also fine just to omit them.
|
|
//
|
|
// The third thing is that for one side (left or right)
|
|
// you should invert the direction of the paths.
|
|
//
|
|
// The main disadvantage of this method is imperfect stitching
|
|
// of the adjacent polygons. The problem can be solved if we use
|
|
// compositing operation "plus" instead of alpha-blend. But
|
|
// in this case we are forced to use an RGBA buffer, clean it with
|
|
// zero, rasterize using "plus" operation, and then alpha-blend
|
|
// the result over the final scene. It can be too expensive.
|
|
//------------------------------------------------------------
|
|
ras.auto_close(false);
|
|
//ras.filling_rule(agg::fill_even_odd);
|
|
start_timer();
|
|
for(int s = m_shape.min_style(); s <= m_shape.max_style(); s++)
|
|
{
|
|
ras.reset();
|
|
for(i = 0; i < m_shape.paths(); i++)
|
|
{
|
|
const agg::path_style& style = m_shape.style(i);
|
|
if(style.left_fill != style.right_fill)
|
|
{
|
|
if(style.left_fill == s)
|
|
{
|
|
ras.add_path(shape, style.path_id);
|
|
}
|
|
if(style.right_fill == s)
|
|
{
|
|
tmp_path.remove_all();
|
|
tmp_path.concat_path(shape, style.path_id);
|
|
tmp_path.invert_polygon(0);
|
|
ras.add_path(tmp_path);
|
|
}
|
|
}
|
|
}
|
|
agg::render_scanlines_aa_solid(ras, sl, ren_base, m_colors[s]);
|
|
}
|
|
double tfill = elapsed_time();
|
|
ras.auto_close(true);
|
|
|
|
// Draw strokes
|
|
//----------------------
|
|
start_timer();
|
|
stroke.width(sqrt(m_scale.scale()));
|
|
stroke.line_join(agg::round_join);
|
|
stroke.line_cap(agg::round_cap);
|
|
for(i = 0; i < m_shape.paths(); i++)
|
|
{
|
|
ras.reset();
|
|
if(m_shape.style(i).line >= 0)
|
|
{
|
|
ras.add_path(stroke, m_shape.style(i).path_id);
|
|
ren.color(agg::srgba8(0,0,0, 128));
|
|
agg::render_scanlines(ras, sl, ren);
|
|
}
|
|
}
|
|
double tstroke = elapsed_time();
|
|
|
|
|
|
char buf[256];
|
|
agg::gsv_text t;
|
|
t.size(8.0);
|
|
t.flip(true);
|
|
|
|
agg::conv_stroke<agg::gsv_text> ts(t);
|
|
ts.width(1.6);
|
|
ts.line_cap(agg::round_cap);
|
|
|
|
sprintf(buf, "Fill=%.2fms (%dFPS) Stroke=%.2fms (%dFPS) Total=%.2fms (%dFPS)\n\n"
|
|
"Space: Next Shape\n\n"
|
|
"+/- : ZoomIn/ZoomOut (with respect to the mouse pointer)",
|
|
tfill, int(1000.0 / tfill),
|
|
tstroke, int(1000.0 / tstroke),
|
|
tfill+tstroke, int(1000.0 / (tfill+tstroke)));
|
|
|
|
t.start_point(10.0, 20.0);
|
|
t.text(buf);
|
|
|
|
ras.add_path(ts);
|
|
ren.color(agg::rgba(0,0,0));
|
|
agg::render_scanlines(ras, sl, ren);
|
|
}
|
|
|
|
|
|
virtual void on_key(int x, int y, unsigned key, unsigned flags)
|
|
{
|
|
if(key == ' ')
|
|
{
|
|
m_shape.read_next();
|
|
m_shape.scale(width(), height());
|
|
force_redraw();
|
|
}
|
|
|
|
if(key == '+' || key == agg::key_kp_plus)
|
|
{
|
|
m_scale *= agg::trans_affine_translation(-x, -y);
|
|
m_scale *= agg::trans_affine_scaling(1.1);
|
|
m_scale *= agg::trans_affine_translation(x, y);
|
|
force_redraw();
|
|
}
|
|
|
|
if(key == '-' || key == agg::key_kp_minus)
|
|
{
|
|
m_scale *= agg::trans_affine_translation(-x, -y);
|
|
m_scale *= agg::trans_affine_scaling(1/1.1);
|
|
m_scale *= agg::trans_affine_translation(x, y);
|
|
force_redraw();
|
|
}
|
|
|
|
if(key == agg::key_left)
|
|
{
|
|
m_scale *= agg::trans_affine_translation(-x, -y);
|
|
m_scale *= agg::trans_affine_rotation(-agg::pi / 20.0);
|
|
m_scale *= agg::trans_affine_translation(x, y);
|
|
force_redraw();
|
|
}
|
|
|
|
if(key == agg::key_right)
|
|
{
|
|
m_scale *= agg::trans_affine_translation(-x, -y);
|
|
m_scale *= agg::trans_affine_rotation(agg::pi / 20.0);
|
|
m_scale *= agg::trans_affine_translation(x, y);
|
|
force_redraw();
|
|
}
|
|
}
|
|
|
|
void on_mouse_move(int x, int y, unsigned flags)
|
|
{
|
|
if((flags & 1) == 0)
|
|
{
|
|
on_mouse_button_up(x, y, flags);
|
|
}
|
|
else
|
|
{
|
|
if(m_point_idx >= 0)
|
|
{
|
|
double xd = x;
|
|
double yd = y;
|
|
m_scale.inverse_transform(&xd, &yd);
|
|
m_shape.modify_vertex(m_point_idx, xd, yd);
|
|
force_redraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
void on_mouse_button_down(int x, int y, unsigned flags)
|
|
{
|
|
if(flags & 1)
|
|
{
|
|
double xd = x;
|
|
double yd = y;
|
|
double r = 4.0 / m_scale.scale();
|
|
m_scale.inverse_transform(&xd, &yd);
|
|
m_point_idx = m_shape.hit_test(xd, yd, r);
|
|
force_redraw();
|
|
}
|
|
}
|
|
|
|
void on_mouse_button_up(int x, int y, unsigned flags)
|
|
{
|
|
m_point_idx = -1;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
int agg_main(int argc, char* argv[])
|
|
{
|
|
the_application app(pix_format, flip_y);
|
|
app.caption("AGG Example - Flash Rasterizer");
|
|
const char* fname = "shapes.txt";
|
|
if(argc > 1) fname = argv[1];
|
|
if(!app.open(fname))
|
|
{
|
|
char buf[256];
|
|
if(strcmp(fname, "shapes.txt") == 0)
|
|
{
|
|
sprintf(buf, "File not found: %s%s. Download http://www.antigrain.com/%s%s\n"
|
|
"or copy it from the ../art directory.",
|
|
fname, fname);
|
|
}
|
|
else
|
|
{
|
|
sprintf(buf, "File not found: %s", fname);
|
|
}
|
|
app.message(buf);
|
|
return 1;
|
|
}
|
|
|
|
if(app.init(655, 520, agg::window_resize))
|
|
{
|
|
app.read_next();
|
|
return app.run();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
|