anti-grain geometry - tips and tricks

38
Anti-Grain Geometry - Tips and Tricks http://www.antigrain.com/tips/index.html[2. 11. 2010 20:37:08] Home/ News Docs Download Mailing List CVS Tips and Tricks Some Interesting Issues, not Classified Simple Color Interpolation in a Square Image Parallelogram Transformations Using WinAPI to Render Text Compiling AGG under Microsoft eMbedded VC 4.0 Working with Gradients The Problem of Line Alignment Copyright © 2002-2006 Maxim Shemanarev Web Design and Programming Maxim Shemanarev

Upload: luciat3210

Post on 21-Apr-2015

217 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Tips and Tricks

http://www.antigrain.com/tips/index.html[2. 11. 2010 20:37:08]

Home/

News Docs Download Mailing List CVS

Tips and TricksSome Interesting Issues, not Classified

Simple Color Interpolation in a Square

Image Parallelogram Transformations

Using WinAPI to Render Text

Compiling AGG under Microsoft eMbedded VC 4.0

Working with Gradients

The Problem of Line Alignment

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 2: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Tips and Tricks

http://www.antigrain.com/tips/index.html[2. 11. 2010 20:37:08]

Page 3: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Color Interpolation

http://www.antigrain.com/tips/square_color_interpolation/square_color_interpolation.agdoc.html[2. 11. 2010 20:37:11]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

Color InterpolationSimple Color Interpolation in a Square

Anti-Grain Geometry has classes for linear, Bresenham-like interpolation with subpixel accuracy.They are dda_line_interpolator, dda2_line_interpolator, and line_bresenham_interpolator. And theycan be also used to interpolate colors in color selection controls like the one in Xara X:

Below is the class that interpolates colors of the rgba8 type, and the function to draw a square withinterpolation. It can be easily modified to draw a rectangle, but it's not really necessary to render thecolor editing control.

#include <stdio.h>#include <string.h>#include "agg_rendering_buffer.h"#include "agg_pixfmt_rgb24.h"#include "agg_renderer_base.h"#include "agg_dda_line.h"

enum{ square_size = 200};

// Writing the buffer to a .PPM file, assuming it has // RGB-structure, one byte per color component//--------------------------------------------------bool write_ppm(const unsigned char* buf, unsigned width, unsigned height, const char* file_name){ FILE* fd = fopen(file_name, "wb"); if(fd) { fprintf(fd, "P6 %d %d 255 ", width, height); fwrite(buf, 1, width * height * 3, fd); fclose(fd); return true; } return false;}

namespace agg{ class color_interpolator_rgba8 { public: color_interpolator_rgba8(agg::rgba8 c1, agg::rgba8 c2, unsigned len) : m_r(c1.r, c2.r, len), m_g(c1.g, c2.g, len), m_b(c1.b, c2.b, len), m_a(c1.a, c2.a, len) { }

void operator ++ () { ++m_r; ++m_g; ++m_b; ++m_a;

Page 4: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Color Interpolation

http://www.antigrain.com/tips/square_color_interpolation/square_color_interpolation.agdoc.html[2. 11. 2010 20:37:11]

}

rgba8 color() const { return rgba8(m_r.y(), m_g.y(), m_b.y(), m_a.y()); }

private: dda_line_interpolator<16> m_r; dda_line_interpolator<16> m_g; dda_line_interpolator<16> m_b; dda_line_interpolator<16> m_a; };

// Rendering a square with color interpolation between its corners // The colors of the corners are ordered CCW started from bottom-left, // assuming that the Y axis goes up. //------------------------------------------------------------------ template<class Renderer> void color_square_rgba8(Renderer& r, int x, int y, int size, rgba8 c1, rgba8 c2, rgba8 c3, rgba8 c4) { int i, j; color_interpolator_rgba8 cy1(c1, c4, size); color_interpolator_rgba8 cy2(c2, c3, size); for(i = 0; i < size; ++i) { color_interpolator_rgba8 cx(cy1.color(), cy2.color(), size); for(j = 0; j < size; ++j) { r.copy_pixel(x + j, y + i, cx.color()); ++cx; } ++cy1; ++cy2; } }}

int main(){ unsigned char* buffer = new unsigned char[square_size * square_size * 3];

agg::rendering_buffer rbuf(buffer, square_size, square_size, -square_size * 3); // Flip Y to go up

agg::pixfmt_rgb24 pf(rbuf); agg::renderer_base<agg::pixfmt_rgb24> rbase(pf);

agg::color_square_rgba8(rbase, 0, 0, square_size, agg::rgba8(0xc6, 0, 0), // Bottom-left agg::rgba8(0xc6, 0, 0xff), // Bottom-right agg::rgba8(0xc6, 0xff, 0xff), // Top-right agg::rgba8(0xc6, 0xfe, 0)); // Top-left

write_ppm(buffer, square_size, square_size, "agg_test.ppm");

delete [] buffer; return 0;}

Here is the result:

It's not included into the distribution package because it's rather a specific class. Besides, it dependson the rgba8 color type.

Copyright © 2002-2006 Maxim Shemanarev

Page 5: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Color Interpolation

http://www.antigrain.com/tips/square_color_interpolation/square_color_interpolation.agdoc.html[2. 11. 2010 20:37:11]

Web Design and Programming Maxim Shemanarev

Page 6: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Image Parallelogram Transformations

http://www.antigrain.com/tips/image_plg/image_plg.agdoc.html[2. 11. 2010 20:37:14]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

Image Parallelogram TransformationsUsing perspective transformations to simulate the functionality of

WinAPI PlgBlt()The declaration of PlgBlt() is:

BOOL PlgBlt( HDC hdcDest, // handle to destination device context CONST POINT *lpPoint, // vertices of destination parallelogram HDC hdcSrc, // handle to source device context int nXSrc, // x-coord. of upper-left corner of source rectangle. int nYSrc, // y-coord. of upper-left corner of source rectangle. int nWidth, // width of source rectangle int nHeight, // height of source rectangle HBITMAP hbmMask, // handle to bitmask int xMask, // x-coord. of upper-left corner of bitmask rectangle. int yMask // y-coord. of upper-left corner of bitmask rectangle.);

Here the most important argument is:

lpPointPointer to an array of three points in logical space that identify three corners of the destinationparallelogram. The upper-left corner of the source rectangle is mapped to the first point in thisarray, the upper-right corner to the second point in this array, and the lower-left corner to thethird point. The lower-right corner of the source rectangle is mapped to the implicit fourth pointin the parallelogram.

It means that this function can apply arbitrary affine transformations to the image. Anti-GrainGeometry can do that too, but there's a problem with proper calculating of the affine transformationmatrix. It really is tricky.

In AGG there are good news, bad news, and then good news again. The good news is that you can usethe perspective transformations that in general can transform an arbitrary convex qudrangle to anotherconvex quadrangle, particularly, a rectangle to an arbitrary parallelogram.

The bad news is that in general case, the perspective transformations work much slower than theaffine ones. It's because the image transformations use the “scanline” approach. You take yourdestination scanline (a row of pixels in the destination canvas), then apply the reverse transformationsto each pixel and pick up the source pixel possibly considering a filter (bilinear, bicubic, etc…). In caseof affine transformations we don't have to calculete every point directly. Instead, we can calculate onlytwo points for each scanline (begin and end) and use a bresenham-like linear interpolator that works ininteger coordinates, thus very fast. But the restriction is that the transformations must be linear andparellel. It means that any straight line must remain straight after applying the transformation, andany two parallel lines must remain parallel. In case of perspective transformations it is not so (they arenot parallel), and we cannot use linear interpolation.

The good news again is that the parallelogram case of the perspective transformations is linear andparallel, so, the the linear interpolation is perfectly applicable and it will work as fast as the imageaffine transformations.

To demonstrate it we modify the AGG example image_perspective.cpp (it can be found inagg2/examples/). Just replace the code of image_perspective.cpp to the following:

#include <stdlib.h>#include <ctype.h>#include <stdio.h>#include "agg_basics.h"#include "agg_rendering_buffer.h"#include "agg_rasterizer_scanline_aa.h"#include "agg_scanline_u.h"#include "agg_renderer_scanline.h"#include "agg_path_storage.h"

Page 7: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Image Parallelogram Transformations

http://www.antigrain.com/tips/image_plg/image_plg.agdoc.html[2. 11. 2010 20:37:14]

#include "agg_conv_transform.h"#include "agg_trans_bilinear.h"#include "agg_trans_perspective.h"#include "agg_span_interpolator_trans.h"#include "agg_span_interpolator_linear.h"#include "agg_pixfmt_rgb24.h"#include "agg_span_image_filter_rgb24.h"#include "ctrl/agg_rbox_ctrl.h"#include "platform/agg_platform_support.h"#include "interactive_polygon.h"

enum { flip_y = true };

agg::rasterizer_scanline_aa<> g_rasterizer;agg::scanline_u8 g_scanline;double g_x1 = 0;double g_y1 = 0;double g_x2 = 0;double g_y2 = 0;

class the_application : public agg::platform_support{public: typedef agg::pixfmt_bgr24 pixfmt; typedef agg::renderer_base<pixfmt> renderer_base; typedef agg::renderer_scanline_aa_solid<renderer_base> renderer_solid;

agg::interactive_polygon m_triangle;

the_application(agg::pix_format_e format, bool flip_y) : agg::platform_support(format, flip_y), m_triangle(4, 5.0) { }

virtual void on_init() { g_x1 = 0.0; g_y1 = 0.0; g_x2 = rbuf_img(0).width(); g_y2 = rbuf_img(0).height(); double dx = width() / 2.0 - (g_x2 - g_x1) / 2.0; double dy = height() / 2.0 - (g_y2 - g_y1) / 2.0; m_triangle.xn(0) = g_x1 + dx; m_triangle.yn(0) = g_y1 + dy; m_triangle.xn(1) = g_x2 + dx; m_triangle.yn(1) = g_y1 + dy; m_triangle.xn(2) = g_x2 + dx; m_triangle.yn(2) = g_y2 + dy; m_triangle.xn(3) = g_x1 + dx; m_triangle.yn(3) = g_y2 + dy; }

virtual void on_draw() { // Calculate the 4-th point of the parallelogram m_triangle.xn(3) = m_triangle.xn(0) + (m_triangle.xn(2) - m_triangle.xn(1));

m_triangle.yn(3) = m_triangle.yn(0) + (m_triangle.yn(2) - m_triangle.yn(1));

pixfmt pixf(rbuf_window()); renderer_base rb(pixf); renderer_solid r(rb); rb.clear(agg::rgba(1, 1, 1));

g_rasterizer.clip_box(0, 0, width(), height());

typedef agg::span_allocator<agg::rgba8> span_alloc_type; span_alloc_type sa;

agg::trans_perspective tr(m_triangle.polygon(), g_x1, g_y1, g_x2, g_y2);

if(tr.is_valid()) {

//=================== The trick with interpolator. // ------- Slow variant // span_interpolator_trans is a general purpose interpolator. // It calls the Transformer::transform() for each point of the // scanline, thus, it's slow. But it can be used with any // kind of transformations, linear or non-linear. //---------------------------- //typedef agg::span_interpolator_trans<agg::trans_perspective> // interpolator_type;

// ------- Fast variant // span_interpolator_linear is an accelerated version of the general // purpose one, span_interpolator_trans. It calculates // actual coordinates only for the beginning and the ending points

Page 8: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Image Parallelogram Transformations

http://www.antigrain.com/tips/image_plg/image_plg.agdoc.html[2. 11. 2010 20:37:14]

// of the span. But the transformations must be linear and parallel, // that is, any straight line must remain straight after applying the // transformation, and any two parallel lines must remain parallel. // It's not sutable for perspective transformations in general // (they are not parallel), but quite OK for this particular case, // i.e., parallelogram transformations. //---------------------------- typedef agg::span_interpolator_linear<agg::trans_perspective> interpolator_type;

//===================

interpolator_type interpolator(tr);

// "hardcoded" bilinear filter //------------------------------------------ typedef agg::span_image_filter_rgb24_bilinear<agg::order_bgr24, interpolator_type> span_gen_type;

typedef agg::renderer_scanline_aa<renderer_base, span_gen_type> renderer_type;

span_gen_type sg(sa, rbuf_img(0), agg::rgba(1, 1, 1, 0), interpolator);

renderer_type ri(rb, sg);

g_rasterizer.reset(); g_rasterizer.move_to_d(m_triangle.xn(0), m_triangle.yn(0)); g_rasterizer.line_to_d(m_triangle.xn(1), m_triangle.yn(1)); g_rasterizer.line_to_d(m_triangle.xn(2), m_triangle.yn(2)); g_rasterizer.line_to_d(m_triangle.xn(3), m_triangle.yn(3));

agg::render_scanlines(g_rasterizer, g_scanline, ri); }

//-------------------------- // Render the "quad" tool and controls g_rasterizer.add_path(m_triangle); r.color(agg::rgba(0, 0.3, 0.5, 0.6)); agg::render_scanlines(g_rasterizer, g_scanline, r); //-------------------------- }

virtual void on_mouse_button_down(int x, int y, unsigned flags) { if(flags & agg::mouse_left) { if(m_triangle.on_mouse_button_down(x, y)) { force_redraw(); } } }

virtual void on_mouse_move(int x, int y, unsigned flags) { if(flags & agg::mouse_left) { if(m_triangle.on_mouse_move(x, y)) { force_redraw(); } } if((flags & agg::mouse_left) == 0) { on_mouse_button_up(x, y, flags); } }

virtual void on_mouse_button_up(int x, int y, unsigned flags) { if(m_triangle.on_mouse_button_up(x, y)) { force_redraw(); } }

};

int agg_main(int argc, char* argv[]){ the_application app(agg::pix_format_bgr24, flip_y); app.caption("AGG Example. Image Perspective Transformations");

const char* img_name = "spheres"; if(argc >= 2) img_name = argv[1];

Page 9: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Image Parallelogram Transformations

http://www.antigrain.com/tips/image_plg/image_plg.agdoc.html[2. 11. 2010 20:37:14]

if(!app.load_img(0, img_name)) { char buf[256]; if(strcmp(img_name, "spheres") == 0) { sprintf(buf, "File not found: %s%s. Download http://www.antigrain.com/%s%s\n" "or copy it from another directory if available.", img_name, app.img_ext(), img_name, app.img_ext()); } else { sprintf(buf, "File not found: %s%s", img_name, app.img_ext()); } app.message(buf); return 1; } if(app.init(600, 600, agg::window_resize)) { return app.run(); } return 1;}

There is a screenshot:

NOTEThe arcticle is actually outdated. Now class trans_affine has methods to calculate an affine matrixthat transforms a parellelogram to another one, a rectangle to a parellelogram, and a parellelogramto a rectangle. See agg2/examples/image_perspective.cpp. However, the above materialis useful because it helps understand better the AGG concepts.

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 10: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Image Parallelogram Transformations

http://www.antigrain.com/tips/image_plg/image_plg.agdoc.html[2. 11. 2010 20:37:14]

Page 11: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

Using WinAPI to Render TextA simple class to extract True Type glyphs using WinAPI

GetGlyphOutline()There are many questions about drawing of high quality text. AGG provides a class that producesvector text of a fixed typeface (very primitive, ANSI 7-bit character set). I added this class just to havea simple mechanism to draw text in demo examples. It has a propriatory data format and isn't worthfurther developing.

The good news is that you can use any available library or API to extract glyphs and render them withAGG. One can say if the glyph format consists of line segments, conic and cubic bezier curves, it'spossible to render it with AGG. All available converters and transformers are applicable, as well as allthe renderers. For example, you can draw an outlined text with conv_stroke, or change the font weight(make it bolder or lighter) using conv_contour. You can also render it with gradients or fill the glyphswith images.

It's a long story how to integrate AGG with different font engines, like FreeType. This exampledemonstrates a simplest way to use Windows API to extract the glyphs and to render text with AGG. Itcalls GetGlyphOutline(), extracts native curved contours, applies conv_curve and renders the textwith any available scanline renderers.

It works relatively slow, not only because each glyph is being rasterized every time (no cachemechanism), but also because GetGlyphOutline() works terribly slow. More than a half of totaltime is spent in GetGlyphOutline(). It would be a good solution to cache the glyphs or even pre-rendered bitmaps; it could speed up rendering vastly, but it's out of this topic.

Below is a simple console application that creates agg_test.ppm file (the simplest possible RGBbitmap file). The file isn't natively supported by Microsoft Windows, but there are many viewers andconverters that can work with it, for example, IrfanView (www.irfanview.com).

Class tt_glyph is not included into AGG because it depends on the Windows API (#include<windows.h>), while the main part of AGG is supposed to be fully platform independent.

To build the example you need to indicate the AGG include directory and to add to the project filesagg_curves.cpp and agg_rasterizer_scanline_aa.cpp.

#include <stdio.h>#include <string.h>#include "agg_pixfmt_rgb24.h"#include "agg_renderer_base.h"#include "agg_renderer_scanline.h"#include "agg_rasterizer_scanline_aa.h"#include "agg_scanline_p.h"#include "agg_conv_curve.h"

#include <windows.h>

namespace agg{

//================================================================= class tt_glyph { enum { buf_size = 16384-32 }; public: ~tt_glyph(); tt_glyph();

// Set the created font and the "flip_y" flag. //------------------------ void font(HDC dc, HFONT f) { m_dc = dc; m_font = f; } void flip_y(bool flip) { m_flip_y = flip; } bool glyph(unsigned chr, bool hinted = true);

Page 12: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

// The following functions can be called after glyph() // and return the respective values of the // GLYPHMETRICS structure. //------------------------- int origin_x() const { return m_origin_x; } int origin_y() const { return m_origin_y; } unsigned width() const { return m_width; } unsigned height() const { return m_height; } int inc_x() const { return m_inc_x; } int inc_y() const { return m_inc_y; } // Set the starting point of the Glyph //------------------------- void start_point(double x, double y) { m_start_x = x; m_start_y = y; } // Vertex Source Interface //------------------------- void rewind(unsigned) { m_cur_vertex = m_vertices; m_cur_flag = m_flags; }

unsigned vertex(double* x, double* y) { *x = m_start_x + *m_cur_vertex++; *y = m_start_y + *m_cur_vertex++; return *m_cur_flag++; }

private: HDC m_dc; HFONT m_font; char* m_gbuf; int8u* m_flags; double* m_vertices; unsigned m_max_vertices; const int8u* m_cur_flag; const double* m_cur_vertex; double m_start_x; double m_start_y; MAT2 m_mat2; int m_origin_x; int m_origin_y; unsigned m_width; unsigned m_height; int m_inc_x; int m_inc_y; bool m_flip_y; };

tt_glyph::~tt_glyph() { delete [] m_vertices; delete [] m_flags; delete [] m_gbuf; } tt_glyph::tt_glyph() : m_dc(0), m_font(0), m_gbuf(new char [buf_size]), m_flags(new int8u [256]), m_vertices(new double[512]), m_max_vertices(256), m_cur_flag(m_flags), m_cur_vertex(m_vertices), m_start_x(0.0), m_start_y(0.0), m_flip_y(false) { m_vertices[0] = m_vertices[1] = 0.0; m_flags[0] = path_cmd_stop; memset(&m_mat2, 0, sizeof(m_mat2)); m_mat2.eM11.value = 1; m_mat2.eM22.value = 1; }

static inline double fx_to_dbl(const FIXED& p) { return double(p.value) + double(p.fract) * (1.0 / 65536.0); } static inline FIXED dbl_to_fx(double d) {

Page 13: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

int l; l = long(d * 65536.0); return *(FIXED*)&l; }

bool tt_glyph::glyph(unsigned chr, bool hinted) { m_vertices[0] = m_vertices[1] = 0.0; m_flags[0] = path_cmd_stop; rewind(0); if (m_font == 0) return false; #ifndef GGO_UNHINTED // For compatibility with old SDKs.#define GGO_UNHINTED 0x0100#endif int unhinted = hinted ? 0 : GGO_UNHINTED; GLYPHMETRICS gm; int total_size = GetGlyphOutline(m_dc, chr, GGO_NATIVE | unhinted, &gm, buf_size, (void*)m_gbuf, &m_mat2);

if (total_size < 0) return false; m_origin_x = gm.gmptGlyphOrigin.x; m_origin_y = gm.gmptGlyphOrigin.y; m_width = gm.gmBlackBoxX; m_height = gm.gmBlackBoxY; m_inc_x = gm.gmCellIncX; m_inc_y = gm.gmCellIncY; if (m_max_vertices <= total_size / sizeof(POINTFX)) { delete [] m_vertices; delete [] m_flags; m_max_vertices = total_size / sizeof(POINTFX) + 256; m_flags = new int8u [m_max_vertices]; m_vertices = new double [m_max_vertices * 2]; }

const char* cur_glyph = m_gbuf; const char* end_glyph = m_gbuf + total_size; double* vertex_ptr = m_vertices; int8u* flag_ptr = m_flags;

while(cur_glyph < end_glyph) { const TTPOLYGONHEADER* th = (TTPOLYGONHEADER*)cur_glyph; const char* end_poly = cur_glyph + th->cb; const char* cur_poly = cur_glyph + sizeof(TTPOLYGONHEADER); *vertex_ptr++ = fx_to_dbl(th->pfxStart.x); *vertex_ptr++ = m_flip_y ? -fx_to_dbl(th->pfxStart.y): fx_to_dbl(th->pfxStart.y); *flag_ptr++ = path_cmd_move_to; while(cur_poly < end_poly) { const TTPOLYCURVE* pc = (const TTPOLYCURVE*)cur_poly; if (pc->wType == TT_PRIM_LINE) { int i; for (i = 0; i < pc->cpfx; i++) { *vertex_ptr++ = fx_to_dbl(pc->apfx[i].x); *vertex_ptr++ = m_flip_y ? -fx_to_dbl(pc->apfx[i].y): fx_to_dbl(pc->apfx[i].y); *flag_ptr++ = path_cmd_line_to; } } if (pc->wType == TT_PRIM_QSPLINE) { int u; for (u = 0; u < pc->cpfx - 1; u++) // Walk through points in spline { POINTFX pnt_b = pc->apfx[u]; // B is always the current point POINTFX pnt_c = pc->apfx[u+1]; if (u < pc->cpfx - 2) // If not on last spline, compute C { // midpoint (x,y)

Page 14: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

*(int*)&pnt_c.x = (*(int*)&pnt_b.x + *(int*)&pnt_c.x) / 2; *(int*)&pnt_c.y = (*(int*)&pnt_b.y + *(int*)&pnt_c.y) / 2; } *vertex_ptr++ = fx_to_dbl(pnt_b.x); *vertex_ptr++ = m_flip_y ? -fx_to_dbl(pnt_b.y): fx_to_dbl(pnt_b.y); *flag_ptr++ = path_cmd_curve3; *vertex_ptr++ = fx_to_dbl(pnt_c.x); *vertex_ptr++ = m_flip_y ? -fx_to_dbl(pnt_c.y): fx_to_dbl(pnt_c.y); *flag_ptr++ = path_cmd_curve3; } } cur_poly += sizeof(WORD) * 2 + sizeof(POINTFX) * pc->cpfx; } cur_glyph += th->cb; *vertex_ptr++ = 0.0; *vertex_ptr++ = 0.0; *flag_ptr++ = path_cmd_end_poly | path_flags_close | path_flags_ccw; } *vertex_ptr++ = 0.0; *vertex_ptr++ = 0.0; *flag_ptr++ = path_cmd_stop; return true; } }

enum{ frame_width = 320, frame_height = 200};

// Writing the buffer to a .PPM file, assuming it has // RGB-structure, one byte per color component//--------------------------------------------------bool write_ppm(const unsigned char* buf, unsigned width, unsigned height, const char* file_name){ FILE* fd = fopen(file_name, "wb"); if(fd) { fprintf(fd, "P6 %d %d 255 ", width, height); fwrite(buf, 1, width * height * 3, fd); fclose(fd); return true; } return false;}

template<class Rasterizer, class Renderer, class Scanline, class CharT>void render_text(Rasterizer& ras, Renderer& ren, Scanline& sl, agg::tt_glyph& gl, double x, double y, const CharT* str, bool hinted = true){ // The minimal pipeline is the curve converter. Of course, there // any other transformations are applicapble, conv_stroke<>, for example. // If there are other thransformations, it probably makes sense to // turn off the hints (hinted=false), i.e., to use unhinted glyphs. //-------------------------- agg::conv_curve<agg::tt_glyph> curve(gl); while(*str) { gl.start_point(x, y); gl.glyph(*str++, hinted); ras.add_path(curve); agg::render_scanlines(ras, sl, ren); x += gl.inc_x(); y += gl.inc_y(); }}

int main(){ // Create the rendering buffer //------------------------

Page 15: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

unsigned char* buffer = new unsigned char[frame_width * frame_height * 3]; agg::rendering_buffer rbuf(buffer, frame_width, frame_height, -frame_width * 3);

// Create the renderers, the rasterizer, and the scanline container //------------------------ agg::pixfmt_rgb24 pixf(rbuf); agg::renderer_base<agg::pixfmt_rgb24> rbase(pixf); agg::renderer_scanline_aa_solid<agg::renderer_base<agg::pixfmt_rgb24> > ren(rbase); agg::rasterizer_scanline_aa<> ras; agg::scanline_p8 sl;

rbase.clear(agg::rgba8(255, 255, 255));

// Font parameters //------------------------ int fontHeight = 60; // in Pixels in this case int fontWidth = 0; int iAngle = 0; bool bold = true; bool italic = true; const char* typeFace = "Times New Roman";

// I'm not sure how to deal with those sneaky WinGDI functions correctly, // so, please correct me if there's something wrong. // I'm not sure if I need to call ReleaseDC() for the screen. //---------------------------------- HFONT font = ::CreateFont(fontHeight, // height of font fontWidth, // average character width iAngle, // angle of escapement iAngle, // base-line orientation angle bold ? 700 : 400, // font weight italic, // italic attribute option FALSE, // underline attribute option FALSE, // strikeout attribute option ANSI_CHARSET, // character set identifier OUT_DEFAULT_PRECIS, // output precision CLIP_DEFAULT_PRECIS, // clipping precision ANTIALIASED_QUALITY, // output quality FF_DONTCARE, // pitch and family typeFace); // typeface name if(font) { HDC dc = ::GetDC(0); if(dc) { HGDIOBJ old_font = ::SelectObject(dc, font); agg::tt_glyph gl; gl.font(dc, font); ren.color(agg::rgba8(0,0,128)); render_text(ras, ren, sl, gl, 10, 100, "Hello, World!"); write_ppm(buffer, frame_width, frame_height, "agg_test.ppm"); ::SelectObject(dc, old_font); ::ReleaseDC(0, dc); } ::DeleteObject(font); }

delete [] buffer; return 0;}

enum{ frame_width = 320, frame_height = 200};

// Writing the buffer to a .PPM file, assuming it has // RGB-structure, one byte per color component//--------------------------------------------------bool write_ppm(const unsigned char* buf, unsigned width, unsigned height, const char* file_name){ FILE* fd = fopen(file_name, "wb"); if(fd) { fprintf(fd, "P6 %d %d 255 ", width, height); fwrite(buf, 1, width * height * 3, fd);

Page 16: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

fclose(fd); return true; } return false;}

template<class Rasterizer, class Renderer, class Scanline, class CharT>void render_text(Rasterizer& ras, Renderer& ren, Scanline& sl, agg::tt_glyph& gl, double x, double y, const CharT* str, bool hinted = true){ // The minimal pipeline is the curve converter. Of course, there // any other transformations are applicapble, conv_stroke<>, for example. // If there are other thransformations, it probably makes sense to // turn off the hints (hinted=false), i.e., to use unhinted glyphs. //-------------------------- agg::conv_curve<agg::tt_glyph> curve(gl); while(*str) { gl.start_point(x, y); gl.glyph(*str++, hinted); ras.add_path(curve); agg::render_scanlines(ras, sl, ren); x += gl.inc_x(); y += gl.inc_y(); }}

int main(){ // Create the rendering buffer //------------------------ unsigned char* buffer = new unsigned char[frame_width * frame_height * 3]; agg::rendering_buffer rbuf(buffer, frame_width, frame_height, -frame_width * 3);

// Create the renderers, the rasterizer, and the scanline container //------------------------ agg::pixfmt_rgb24 pixf(rbuf); agg::renderer_base<agg::pixfmt_rgb24> rbase(pixf); agg::renderer_scanline_aa_solid<agg::renderer_base<agg::pixfmt_rgb24> > ren(rbase); agg::rasterizer_scanline_aa<> ras; agg::scanline_p8 sl;

rbase.clear(agg::rgba8(255, 255, 255));

// Font parameters //------------------------ int fontHeight = 60; // in Pixels in this case int fontWidth = 0; int iAngle = 0; bool bold = true; bool italic = true; const char* typeFace = "Times New Roman";

// I'm not sure how to deal with those sneaky WinGDI functions correctly, // so, please correct me if there's something wrong. // I'm not sure if I need to call ReleaseDC() for the screen. //---------------------------------- HFONT font = ::CreateFont(fontHeight, // height of font fontWidth, // average character width iAngle, // angle of escapement iAngle, // base-line orientation angle bold ? 700 : 400, // font weight italic, // italic attribute option FALSE, // underline attribute option FALSE, // strikeout attribute option ANSI_CHARSET, // character set identifier OUT_DEFAULT_PRECIS, // output precision CLIP_DEFAULT_PRECIS, // clipping precision ANTIALIASED_QUALITY, // output quality FF_DONTCARE, // pitch and family typeFace); // typeface name if(font) { HDC dc = ::GetDC(0); if(dc) { HGDIOBJ old_font = ::SelectObject(dc, font); agg::tt_glyph gl; gl.font(dc, font); ren.color(agg::rgba8(0,0,128)); render_text(ras, ren, sl, gl, 10, 100, "Hello, World!");

Page 17: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

write_ppm(buffer, frame_width, frame_height, "agg_test.ppm"); ::SelectObject(dc, old_font); ::ReleaseDC(0, dc); } ::DeleteObject(font); }

delete [] buffer; return 0;}

And the result:

Beautiful, isn't it? In fact, it looks much better than the native text rendered in Windows. To comparethe quality, run WordPad, type “Hello, World!” and change font to “Times New Roman”, set Bold, Italicand size of 39 points.

Here's an enlarged fragment of a glyph to compare the quality.

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 18: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Using WinAPI to Render Text

http://www.antigrain.com/tips/win_glyph/win_glyph.agdoc.html[2. 11. 2010 20:37:18]

Page 19: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

Compiling AGG under MicrosofteMbedded VC 4.0A simple step-by-step tutorial

If you have a Microsoft eMbedded Visual C++ 4.0 (eVC4.0) installed you can develop applicationsbased on AGG for PocketPCs that use Windows CE.

I didn't have any problems compiling AGG under eVC4.0, it looks pretty much the same as building ofWin32 API applications.

Below there is a spep-by-step instruction how to create a simple WinCE application with AGG.

1. Create a new WCE Application, for example in agg2/examples/win32_ce/agg_test.Choose "A typical Hello World Application".

2. Add path to the AGG include directory: Project/Settings, set "Setting For:" to "All configurations".Select tab "C/C++", category "Preprocessor".Type path to the AGG include directory in the "Additional Include Directories".If you put the project to agg2/examples/win32_ce/agg_test, type ../../../include

3. Add necessary AGG source files to the project from agg2/src. You can actually add all ofthem (except the ones in the sub-directories).

4. Select all the AGG source files in the project (agg_*.cpp, except agg_test.*).

5. Go to Project/Settings, set "Setting For:" to "All configurations".Select tab "C/C++", category "Precompiled Headers".Choose option "Not using precompiled headers".

6. Replace the content of agg_test.cpp to the following (see the source in agg_test.cpp).Basically you need to replace the WM_PAINT event handler, add the AGG includes, and add theagg_draw() function.

// agg_test.cpp : Defines the entry point for the application.//

#include "stdafx.h"#include "agg_test.h"#include <commctrl.h>

#include "agg_rendering_buffer.h"#include "agg_curves.h"#include "agg_conv_stroke.h"#include "agg_rasterizer_scanline_aa.h"#include "agg_scanline_p.h"#include "agg_renderer_scanline.h"#include "agg_pixfmt_rgb555.h"

#define MAX_LOADSTRING 100

// Global Variables:HINSTANCE hInst; // The current instanceHWND hwndCB; // The command bar handle

// Forward declarations of functions included in this code module:ATOM MyRegisterClass (HINSTANCE, LPTSTR);BOOL InitInstance (HINSTANCE, int);LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){ MSG msg; HACCEL hAccelTable;

// Perform application initialization:

Page 20: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }

hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_AGG_TEST);

// Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

return msg.wParam;}

//// FUNCTION: MyRegisterClass()//// PURPOSE: Registers the window class.//// COMMENTS://// It is important to call this function so that the application // will get 'well formed' small icons associated with it.//ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass){ WNDCLASS wc;

wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_AGG_TEST)); wc.hCursor = 0; wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = szWindowClass;

return RegisterClass(&wc);}

//// FUNCTION: InitInstance(HANDLE, int)//// PURPOSE: Saves instance handle and creates main window//// COMMENTS://// In this function, we save the instance handle in a global variable and// create and display the main program window.//BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){ HWND hWnd; TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // The window class name

hInst = hInstance; // Store instance handle in our global variable // Initialize global strings LoadString(hInstance, IDC_AGG_TEST, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance, szWindowClass);

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

if (!hWnd) { return FALSE; }

ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); if (hwndCB) CommandBar_Show(hwndCB, TRUE);

return TRUE;}

void agg_draw(unsigned char* buf, unsigned w, unsigned h, int stride){ typedef agg::pixfmt_rgb555 pixfmt;

//============================================================ // AGG lowest level code. agg::rendering_buffer rbuf;

Page 21: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

rbuf.attach((unsigned char*)buf, w, h, stride);

// Pixel format and basic primitives renderer pixfmt pixf(rbuf); agg::renderer_base<pixfmt> renb(pixf);

renb.clear(agg::rgba8(255, 255, 255, 255));

// Scanline renderer for solid filling. agg::renderer_scanline_aa_solid<agg::renderer_base<pixfmt> > ren(renb);

// Rasterizer & scanline agg::rasterizer_scanline_aa<> ras; agg::scanline_p8 sl;

agg::curve4 curve; agg::conv_stroke<agg::curve4> poly(curve); unsigned i;

srand(12365);

for(i = 0; i < 100; i++) { poly.width(double(rand() % 3500 + 500) / 500.0);

curve.init(rand() % w, rand() % h, rand() % w, rand() % h, rand() % w, rand() % h, rand() % w, rand() % h);

ren.color(agg::rgba8(rand() & 0xFF, rand() & 0xFF, rand() & 0xFF, rand() & 0xFF));

ras.add_path(poly, 0); agg::render_scanlines(ras, sl, ren); } //============================================================}

//// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)//// PURPOSE: Processes messages for the main window.//// WM_COMMAND - process the application menu// WM_PAINT - Paint the main window// WM_DESTROY - post a quit message and return////LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ HDC hdc; int wmId, wmEvent; PAINTSTRUCT ps;// TCHAR szHello[MAX_LOADSTRING];

switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_HELP_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_FILE_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_CREATE: hwndCB = CommandBar_Create(hInst, hWnd, 1); CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); break;

case WM_PAINT: { hdc = BeginPaint(hWnd, &ps); RECT rt; GetClientRect(hWnd, &rt);

int width = rt.right - rt.left; int height = rt.bottom - rt.top;

Page 22: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

//============================================================ //Creating compatible DC and a bitmap to render the image BITMAPINFO bmp_info; bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmp_info.bmiHeader.biWidth = width; bmp_info.bmiHeader.biHeight = height; bmp_info.bmiHeader.biPlanes = 1; bmp_info.bmiHeader.biBitCount = 16; bmp_info.bmiHeader.biCompression = BI_RGB; bmp_info.bmiHeader.biSizeImage = 0; bmp_info.bmiHeader.biXPelsPerMeter = 0; bmp_info.bmiHeader.biYPelsPerMeter = 0; bmp_info.bmiHeader.biClrUsed = 0; bmp_info.bmiHeader.biClrImportant = 0;

HDC mem_dc = ::CreateCompatibleDC(hdc);

void* buf = 0;

HBITMAP bmp = ::CreateDIBSection( mem_dc, &bmp_info, DIB_RGB_COLORS, &buf, 0, 0 );

// Selecting the object before doing anything allows you // to use AGG together with native Windows GDI. HBITMAP temp = (HBITMAP)::SelectObject(mem_dc, bmp);

// Calculate the aligned stride value for the 16-bit BMP. int stride = ((width * 2 + 3) >> 2) << 2;

// Negate the stride value to have the Y-axis flipped agg_draw((unsigned char*)buf, width, height, -stride);

//------------------------------------------------------------ // Display the image. If the image is B-G-R-A (32-bits per pixel) // one can use AlphaBlend instead of BitBlt. In case of AlphaBlend // one also should clear the image with zero alpha, i.e. rgba8(0,0,0,0)

::BitBlt( hdc, rt.left, rt.top, width, height, mem_dc, 0, 0, SRCCOPY );

// Free resources ::SelectObject(mem_dc, temp); ::DeleteObject(bmp); ::DeleteObject(mem_dc);

EndPaint(hWnd, &ps); } break;

case WM_DESTROY: CommandBar_Destroy(hwndCB); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0;}

// Mesage handler for the About box.LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){ RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY;

switch (message) { case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) { GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER | SWP_NOSIZE);

Page 23: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

} return TRUE;

case WM_COMMAND: if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE;}

Download the whole project: (agg_test.zip). It's supposed to be placed inagg2/examples/win32_ce/agg_test/. Create win32_ce and agg_test directories. If youchoose another directory, modify the Additional Include Directories path, and you will have toremove all the AGG source files from the project and add new ones from the actual AGG directory(don't forget to turn Precompiled Headers off).

NOTEI'm not a pro in WinCE and embedded systems, but I presumed that there's an RGB555 pixelformat is used. If it's not so, use another pixel format that fits the native display format.

Change function agg_draw() and try other AGG stuff. Also note, that the whole scene is being drawnfrom scratch every time the WM_PAINT event comes. It's a good idea to cache the rendered image andjust BitBlt() it if there were no changes.

Here's a screenshot (click to see the enlarged version)

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 24: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Compiling AGG under Microsoft eMbedded VC 4.0

http://www.antigrain.com/tips/win_ce/win_ce.agdoc.html[2. 11. 2010 20:37:22]

Page 25: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

Working with GradientsA Simple Step-by-Step Tutorial

This article will explain to you how to set up gradients and render them. We will use a simplecommand-line example that produces the result in the agg_test.ppm file. You can use, for example

IrfanView (www.irfanview.com) to see the results.

You will need to tell the compiler the AGG include directory and add three source files to the project orto the command line: agg_rasterizer_scanline_aa.cpp, agg_trans_affine.cpp, andagg_sqrt_tables.cpp. You can find the source file here: (gradients.cpp).

#include <stdio.h>#include <string.h>#include "agg_pixfmt_rgb.h"#include "agg_renderer_base.h"#include "agg_renderer_scanline.h"#include "agg_scanline_u.h"#include "agg_rasterizer_scanline_aa.h"#include "agg_ellipse.h"#include "agg_span_gradient.h"#include "agg_span_interpolator_linear.h"

enum{ frame_width = 320, frame_height = 200};

// Writing the buffer to a .PPM file, assuming it has // RGB-structure, one byte per color component//--------------------------------------------------bool write_ppm(const unsigned char* buf, unsigned width, unsigned height, const char* file_name){ FILE* fd = fopen(file_name, "wb"); if(fd) { fprintf(fd, "P6 %d %d 255 ", width, height); fwrite(buf, 1, width * height * 3, fd); fclose(fd); return true; } return false;}

// A simple function to form the gradient color array // consisting of 3 colors, "begin", "middle", "end"//---------------------------------------------------template<class Array>void fill_color_array(Array& array, agg::rgba8 begin, agg::rgba8 middle, agg::rgba8 end){ unsigned i; unsigned half_size = array.size() / 2; for(i = 0; i < half_size; ++i) { array[i] = begin.gradient(middle, i / double(half_size)); } for(; i < array.size(); ++i) { array[i] = middle.gradient(end, (i - half_size) / double(half_size)); }}

int main(){ unsigned char* buffer = new unsigned char[frame_width * frame_height * 3];

Page 26: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

agg::rendering_buffer rbuf(buffer, frame_width, frame_height, -frame_width * 3);

// Pixel format and basic renderers. //----------------- typedef agg::pixfmt_rgb24 pixfmt_type; typedef agg::renderer_base<pixfmt_type> renderer_base_type;

// The gradient color array typedef agg::pod_auto_array<agg::rgba8, 256> color_array_type;

// Gradient shape function (linear, radial, custom, etc) //----------------- typedef agg::gradient_x gradient_func_type;

// Span interpolator. This object is used in all span generators // that operate with transformations during iterating of the spans, // for example, image transformers use the interpolator too. //----------------- typedef agg::span_interpolator_linear<> interpolator_type;

// Span allocator is an object that allocates memory for // the array of colors that will be used to render the // color spans. One object can be shared between different // span generators. //----------------- typedef agg::span_allocator<agg::rgba8> span_allocator_type;

// Finally, the gradient span generator working with the agg::rgba8 // color type. // The 4-th argument is the color function that should have // the [] operator returning the color in range of [0...255]. // In our case it will be a simple look-up table of 256 colors. //----------------- typedef agg::span_gradient<agg::rgba8, interpolator_type, gradient_func_type, color_array_type, span_allocator_type> span_gradient_type;

// The gradient scanline renderer type //----------------- typedef agg::renderer_scanline_aa<renderer_base_type, span_gradient_type> renderer_gradient_type;

// Common declarations (pixel format and basic renderer). //---------------- pixfmt_type pixf(rbuf); renderer_base_type rbase(pixf);

// The gradient objects declarations //---------------- gradient_func_type gradient_func; // The gradient function agg::trans_affine gradient_mtx; // Affine transformer interpolator_type span_interpolator(gradient_mtx); // Span interpolator span_allocator_type span_allocator; // Span Allocator color_array_type color_array; // Gradient colors

// Declare the gradient span itself. // The last two arguments are so called "d1" and "d2" // defining two distances in pixels, where the gradient starts // and where it ends. The actual meaning of "d1" and "d2" depands // on the gradient function. //---------------- span_gradient_type span_gradient(span_allocator, span_interpolator, gradient_func, color_array, 0, 100);

// The gradient renderer //---------------- renderer_gradient_type ren_gradient(rbase, span_gradient);

// The rasterizing/scanline stuff //---------------- agg::rasterizer_scanline_aa<> ras; agg::scanline_u8 sl;

// Finally we can draw a circle. //---------------- rbase.clear(agg::rgba8(255, 255, 255));

fill_color_array(color_array,

Page 27: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

agg::rgba8(0,50,50), agg::rgba8(240, 255, 100), agg::rgba8(80, 0, 0));

agg::ellipse ell(50, 50, 50, 50, 100); ras.add_path(ell);

agg::render_scanlines(ras, sl, ren_gradient);

write_ppm(buffer, frame_width, frame_height, "agg_test.ppm");

delete [] buffer; return 0;}

It looks rather complex, especially the necessity to declare a lot of types and objects. But the“complexity” gives you freedom, for example, you can define your own gradient functions or evenarbitrary distortions.

The example renders a circle with linear gradient from (0,0) to (100,0). In AGG you can define anarbitrary color function, in our case it's a simple look-up table generated from three colors, start,middle, and end.

Here is the result (the axes and text were added in Xara X):

It also can seem like an overkill for this simple task, but later you will see that it's not so.

The next step is one little modification. Modify the following:

// Declare the gradient span itself. // The last two arguments are so called "d1" and "d2" // defining two distances in pixels, where the gradient starts // and where it ends. The actual meaning of "d1" and "d2" depands // on the gradient function. //---------------- span_gradient_type span_gradient(span_allocator, span_interpolator, gradient_func, color_array, 50, 100);

The result:

Page 28: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

It should explain those freaky d1 and d2 arguments. In fact, they determine the geometrical startand end of the gradient and their meaning depends on the gradient function.

Now change the gradient function:

// Gradient shape function (linear, radial, custom, etc) //----------------- typedef agg::gradient_circle gradient_func_type;

Set d1 back to 0:

// Declare the gradient span itself. // The last two arguments are so called "d1" and "d2" // defining two distances in pixels, where the gradient starts // and where it ends. The actual meaning of "d1" and "d2" depands // on the gradient function. //---------------- span_gradient_type span_gradient(span_allocator, span_interpolator, gradient_func, color_array, 0, 100);

And modify the circle:

agg::ellipse ell(0, 0, 120, 120, 100);

The result:

Modify d1 again:

// Declare the gradient span itself. // The last two arguments are so called "d1" and "d2" // defining two distances in pixels, where the gradient starts // and where it ends. The actual meaning of "d1" and "d2" depands

Page 29: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

// on the gradient function. //---------------- span_gradient_type span_gradient(span_allocator, span_interpolator, gradient_func, color_array, 50, 100);

So that, in case of a radial gradient, d1 and d2 define the starting and ending radii.

By default the origin point for the gradients is (0,0). How to draw a gradient in some other place? Theanswer is to use affine transformations. Strictly speaking, the transformations are fully defined by thespan interpolator. In our case we use span_interpolator_linear with an affine matrix. The linearinterpolator allows you to speed up the calculations vastly, because we calculate the floating pointcoordinates only in the begin and end of the horizontal spans and then use a fast, integer, Bresenham-like interpolation with Subpixel Accuracy.

Add the following code somewhere before calling agg::render_scanlines(ras, sl,ren_gradient);

gradient_mtx *= agg::trans_affine_scaling(0.75, 1.2); gradient_mtx *= agg::trans_affine_rotation(-agg::pi/3.0); gradient_mtx *= agg::trans_affine_translation(100.0, 100.0); gradient_mtx.invert();

And modify the circle:

agg::ellipse ell(100, 100, 120, 120, 100);

The code of initializing of the affine matrix should be obvious except for some strangegradient_mtx.invert(). It's necessary because the gradient generator uses reversetransformations instead of direct ones. In other words it takes the destination point, applies thetransformations and obtains the coordinates in the gradient. Note that the affine transformations allowyou to turn a circular gradient into elliptical.

Page 30: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

Now it should be obvious how to define a linear gradient from some Point1 to Point2. So, get backto the original code and add the following function:

// Calculate the affine transformation matrix for the linear gradient // from (x1, y1) to (x2, y2). gradient_d2 is the "base" to scale the// gradient. Here d1 must be 0.0, and d2 must equal gradient_d2.//---------------------------------------------------------------void calc_linear_gradient_transform(double x1, double y1, double x2, double y2, agg::trans_affine& mtx, double gradient_d2 = 100.0){ double dx = x2 - x1; double dy = y2 - y1; mtx.reset(); mtx *= agg::trans_affine_scaling(sqrt(dx * dx + dy * dy) / gradient_d2); mtx *= agg::trans_affine_rotation(atan2(dy, dx)); mtx *= agg::trans_affine_translation(x1, y1); mtx.invert();}

Then modify the circle:

agg::ellipse ell(100, 100, 80, 80, 100);

And add the transformations:

calc_linear_gradient_transform(50, 50, 150, 150, gradient_mtx);

Try to play with different parameters, transformations, and gradient functions: gradient_circle,gradient_x, gradient_y, gradient_diamond, gradient_xy, gradient_sqrt_xy, gradient_conic. Also look atthe gradient functions and try to write your own. Actually, the set of the gradient functions in AGG israther poor, it just demonstrates the possibilities. For example, repeating or reflecting gradients shouldbe implemented in gradient functions (or you can write adaptors that will use the existing functions).

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 31: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - Working with Gradients

http://www.antigrain.com/tips/gradients_tutorial/gradients_tutorial.agdoc.html[2. 11. 2010 20:37:28]

Page 32: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

Home/Tips & Tricks/

News Docs Download Mailing List CVS

The Problem of Line AlignmentThe "half-a-pixel" problem when rendering lines

Anti-Aliasing is a tricky thing. If you decided you like AGG and it finally solves all your problems in2D graphics, it's a mistake. Nothing of the kind. The more you worry about the quality the moreproblems there are exposed.

Let us start with a simple rectangle.

Here we have a rectangle with exact integral coordinates (1,1,4,4). Everything looks fine, but tounderstand and see how the Anti-Aliasing and Subpixel Accuracy work let's shift it to 0.5 pixel by Xand Y:

The pixels have intensities proportional to the area of the pixel covered by the rectangle. In practice itmeans that the rectangle looks blur. It's not a caprice, it's a necessity because only in this case we canpreserve the visual area covered by the rectangle the same, regardless of its subpixel position. Theinitial rectangle covers 9 pixels. If we just round off the coordinates, the resulting rectangle can bedrawn as 4 pixels and it can be drawn as 16 pixels, depending on the position and the rounding rules.So that, the “blurness” is much less evil than "jitter" because it allows you to keep the image muchmore consistent.

Page 33: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

Now let's try to calculate an outline of one pixel width around this square:

This is an ideal case. In prcatice we cannot draw anything between pixels, so the result will look evenmore blur:

There are no fully covered pixels at all and this fact creates the problem of line alignment. Bad news isthat there's no ideal solution of it, we'll have to sacrifice something. The good news is there are severalpartial solutions that can be satisfactory. First, let's try to add 0.5 to the coordinates of the outline.Remember, if we add 0.5 to the filled rectangle too, the ones without outlines will look blur (seeabove).

Page 34: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

Looks perfect while the outline is 100% opaque. If we have a translucent boundary it will look worse:

The translucency can be implicit, for example, if we draw a line of 0.5 pixel width, it's simulated withtranslucency! It will look better if we shift both, the fill and its outline.

But remember, it will look worse if it's not outlined. Still, The first solution is to shift everything to 0.5pixel, which can be appropriate when you have outlines in all cases.

The second solution is to shift only outlines, keeping the filled polygons as they are. In this case youmust be sure you always have the outline of at least 1 pixel width. That's not a good restriction. Youcan do even better, shifting only those polygons that have an outline (stroke). But in this case you canhave some inconsistency between polygons with and without strokes.

The shifting transformer is very simple:

namespace agg{ class trans_shift { public: trans_shift() : m_shift(0.0) {} trans_shift(double s) : m_shift(s) {}

void shift(double s) { m_shift = s; } double shift() const { return m_shift; }

void transform(double* x, double* y) const { *x += m_shift; *y += m_shift; } private: double m_shift; };}

And its use is simple too:

Page 35: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

agg::trans_shift ts(0.5);agg::conv_transform<source_class, agg::trans_shift> shift(source, ts);

That is, it's included into the pipeline as yet another transformer. If you use the affine transformer(most probably you will), you can do without this additional converter. Just add the following, after thematrix is formed:

mtx *= agg::trans_affine_translate(0.5, 0.5);

In this case there will be no additional “performance fee”. Of course, you will have to worry about whenand where to add this shift (see cases above).

There is one more solution and it can be even better. Nobody says that we need to apply the sameshift to all coordinates. In case of our rectangle there can be inner or outer outline:

You can achive this with using conv_contour, see also Demo conv_contour.cpp.

But there are some problems too. First of all, the “insideness” becomes important, while conv_strokedoesn't care about it. So that, you should preserve or detect the orientation of the contours, not tomention that self-intersecting polygons don't have a univocal orientation, they can have only a“prevaling” orientation.

The second problem is where to apply this transformation. It should be definitely done before strokeconverter. But there is a contradiction with the succeeding affine transformations. Take the zoomoperation, for example. If you want the line widths to be consistent with the transformations, you haveto use the affine transformer after the outline is calculated. If it's done before, you can change thestroke width respectively, but in this case you breake the integrity if you have different scalingcoefficients by X and Y.

Page 36: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

If you are absolutely sure you will never use different scaling coefficients by X and Y, you cantransform paths before calculating the outline. In other words, the sequence of the conversions isimportant and it can be contadictive. Besides, you have to decide if you only need to correct the “0.5problem” or to have the true inner or outer stroke.

The conclusion is that there's no ideal solution. But the whole idea of Anti-Grain Geometry is that it'syou who chooses the neccessary pipelines and solve your problem-orinted tasks. Thare are no otherAPIs that allow you to have this flexibility.

Copyright © 2002-2006 Maxim ShemanarevWeb Design and Programming Maxim Shemanarev

Page 37: Anti-Grain Geometry - Tips and Tricks

Anti-Grain Geometry - The Problem of Line Alignment

http://www.antigrain.com/tips/line_alignment/line_alignment.agdoc.html[2. 11. 2010 20:37:32]

Page 38: Anti-Grain Geometry - Tips and Tricks

http://www.antigrain.com/tips/win_ce/screenshot.png[2. 11. 2010 20:37:38]