agg/examples/flash_rasterizer2.cpp

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