1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd_gfx.c graphics wrapper for rrdtool
5 **************************************************************************/
7 /* #define DEBUG */
9 #ifdef DEBUG
10 # define DPRINT(x) (void)(printf x, printf("\n"))
11 #else
12 # define DPRINT(x)
13 #endif
15 #include <png.h>
16 #include <ft2build.h>
17 #include FT_FREETYPE_H
18 #include <math.h>
20 #include "rrd_gfx.h"
22 /* lines are better drawn on the pixle than between pixles */
23 #define LINEOFFSET 0.5
25 static
26 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
27 gfx_node_t *node = art_new(gfx_node_t,1);
28 if (node == NULL) return NULL;
29 node->type = type;
30 node->color = 0x0; /* color of element 0xRRGGBBAA alpha 0xff is solid*/
31 node->size =0.0; /* font size, line width */
32 node->path = NULL; /* path */
33 node->points = 0;
34 node->points_max =0;
35 node->svp = NULL; /* svp */
36 node->filename = NULL; /* font or image filename */
37 node->text = NULL;
38 node->x = 0.0;
39 node->y = 0.0; /* position */
40 node->halign = GFX_H_NULL; /* text alignement */
41 node->valign = GFX_V_NULL; /* text alignement */
42 node->tabwidth = 0.0;
43 node->next = NULL;
44 if (canvas->lastnode != NULL){
45 canvas->lastnode->next = node;
46 }
47 if (canvas->firstnode == NULL){
48 canvas->firstnode = node;
49 }
50 canvas->lastnode = node;
51 return node;
52 }
54 gfx_canvas_t *gfx_new_canvas (void) {
55 gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
56 canvas->firstnode = NULL;
57 canvas->lastnode = NULL;
58 return canvas;
59 }
61 /* create a new line */
62 gfx_node_t *gfx_new_line(gfx_canvas_t *canvas,
63 double x0, double y0,
64 double x1, double y1,
65 double width, gfx_color_t color){
67 gfx_node_t *node;
68 ArtVpath *vec;
69 node = gfx_new_node(canvas,GFX_LINE);
70 if (node == NULL) return NULL;
71 vec = art_new(ArtVpath, 3);
72 if (vec == NULL) return NULL;
73 vec[0].code = ART_MOVETO_OPEN; vec[0].x=x0+LINEOFFSET; vec[0].y=y0+LINEOFFSET;
74 vec[1].code = ART_LINETO; vec[1].x=x1+LINEOFFSET; vec[1].y=y1+LINEOFFSET;
75 vec[2].code = ART_END;
77 node->points = 3;
78 node->points_max = 3;
79 node->color = color;
80 node->size = width;
81 node->path = vec;
82 return node;
83 }
85 /* create a new area */
86 gfx_node_t *gfx_new_area (gfx_canvas_t *canvas,
87 double x0, double y0,
88 double x1, double y1,
89 double x2, double y2,
90 gfx_color_t color) {
92 gfx_node_t *node;
93 ArtVpath *vec;
94 node = gfx_new_node(canvas,GFX_AREA);
95 if (node == NULL) return NULL;
96 vec = art_new(ArtVpath, 5);
97 if (vec == NULL) return NULL;
98 vec[0].code = ART_MOVETO; vec[0].x=x0; vec[0].y=y0;
99 vec[1].code = ART_LINETO; vec[1].x=x1; vec[1].y=y1;
100 vec[2].code = ART_LINETO; vec[2].x=x2; vec[2].y=y2;
101 vec[3].code = ART_LINETO; vec[3].x=x0; vec[3].y=y0;
102 vec[4].code = ART_END;
104 node->points = 5;
105 node->points_max = 5;
106 node->color = color;
107 node->path = vec;
109 return node;
110 }
111 /* create an arc section (2*M_PI is full circle) */
112 gfx_node_t *gfx_arc_sect (gfx_canvas_t *canvas,
113 double centerx, double centery,
114 double radiusx, double radiusy,
115 double start, double end,
116 gfx_color_t color) {
118 gfx_node_t *node;
119 ArtVpath *vec;
120 int counter;
121 double position;
123 /* 20 is too low, 100 is overkill */
124 #define AMOUNT_OF_VECTORS 50
126 node = gfx_new_node(canvas,GFX_AREA);
127 if (node == NULL) return NULL;
128 vec = art_new(ArtVpath, AMOUNT_OF_VECTORS+4);
129 if (vec == NULL) return NULL;
131 vec[0].code = ART_MOVETO;
132 vec[0].x = centerx;
133 vec[0].y = centery;
135 for (counter=0;counter<=AMOUNT_OF_VECTORS;) {
136 position=start + counter*(end-start)/AMOUNT_OF_VECTORS;
138 counter++;
139 vec[counter].code = ART_LINETO;
140 vec[counter].x = centerx + sin(position)*radiusx;
141 vec[counter].y = centery - cos(position)*radiusy;
142 }
144 vec[AMOUNT_OF_VECTORS+2].code = ART_LINETO;
145 vec[AMOUNT_OF_VECTORS+2].x = centerx;
146 vec[AMOUNT_OF_VECTORS+2].y = centery;
148 vec[AMOUNT_OF_VECTORS+3].code = ART_END;
150 node->points = AMOUNT_OF_VECTORS+4;
151 node->points_max = AMOUNT_OF_VECTORS+4;
152 node->color = color;
153 node->path = vec;
155 return node;
156 }
158 /* add a point to a line or to an area */
159 int gfx_add_point (gfx_node_t *node,
160 double x, double y){
161 if (node == NULL) return 1;
162 if (node->type == GFX_AREA) {
163 double x0 = node->path[0].x;
164 double y0 = node->path[0].y;
165 node->points -= 2;
166 art_vpath_add_point (&(node->path),
167 &(node->points),
168 &(node->points_max),
169 ART_LINETO,
170 x,y);
171 art_vpath_add_point (&(node->path),
172 &(node->points),
173 &(node->points_max),
174 ART_LINETO,
175 x0,y0);
176 art_vpath_add_point (&(node->path),
177 &(node->points),
178 &(node->points_max),
179 ART_END,
180 0,0);
181 } else if (node->type == GFX_LINE) {
182 node->points -= 1;
183 art_vpath_add_point (&(node->path),
184 &(node->points),
185 &(node->points_max),
186 ART_LINETO,
187 x+LINEOFFSET,y+LINEOFFSET);
188 art_vpath_add_point (&(node->path),
189 &(node->points),
190 &(node->points_max),
191 ART_END,
192 0,0);
194 } else {
195 /* can only add point to areas and lines */
196 return 1;
197 }
198 return 0;
199 }
203 /* create a text node */
204 gfx_node_t *gfx_new_text (gfx_canvas_t *canvas,
205 double x, double y, gfx_color_t color,
206 char* font, double size,
207 double tabwidth, double angle,
208 enum gfx_h_align_en h_align,
209 enum gfx_v_align_en v_align,
210 char* text){
211 gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
212 if (angle != 0.0){
213 /* currently we only support 0 and 270 */
214 angle = 270.0;
215 }
217 node->text = strdup(text);
218 node->size = size;
219 node->filename = strdup(font);
220 node->x = x;
221 node->y = y;
222 node->color = color;
223 node->tabwidth = tabwidth;
224 node->halign = h_align;
225 node->valign = v_align;
226 return node;
227 }
229 double gfx_get_text_width ( double start, char* font, double size,
230 double tabwidth, char* text){
232 FT_GlyphSlot slot;
233 FT_UInt previous=0;
234 FT_UInt glyph_index=0;
235 FT_Bool use_kerning;
236 int error;
237 FT_Face face;
238 FT_Library library=NULL;
239 double text_width=0;
240 FT_Init_FreeType( &library );
241 error = FT_New_Face( library, font, 0, &face );
242 if ( error ) return -1;
243 error = FT_Set_Char_Size(face, size*64,size*64, 100,100);
244 if ( error ) return -1;
246 use_kerning = FT_HAS_KERNING(face);
247 slot = face->glyph;
248 for(;*text;text++) {
249 previous = glyph_index;
250 glyph_index = FT_Get_Char_Index( face, *text);
252 if (use_kerning && previous && glyph_index){
253 FT_Vector delta;
254 FT_Get_Kerning( face, previous, glyph_index,
255 0, &delta );
256 text_width += (double)delta.x / 64.0;
258 }
259 error = FT_Load_Glyph( face, glyph_index, 0 );
260 if ( error ) {
261 FT_Done_FreeType(library);
262 return -1;
263 }
264 if (! previous) {
265 text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
266 }
267 text_width += (double)slot->metrics.horiAdvance / 64.0;
268 }
269 text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
270 text_width += (double)slot->metrics.width / 64.0; /* add just char width */
271 text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
272 FT_Done_FreeType(library);
273 return text_width;
274 }
279 static int gfx_save_png (art_u8 *buffer, FILE *fp,
280 long width, long height, long bytes_per_pixel);
281 /* render grafics into png image */
282 int gfx_render_png (gfx_canvas_t *canvas,
283 art_u32 width, art_u32 height,
284 double zoom,
285 gfx_color_t background, FILE *fp){
288 FT_Library library;
289 gfx_node_t *node = canvas->firstnode;
290 art_u8 red = background >> 24, green = (background >> 16) & 0xff;
291 art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
292 unsigned long pys_width = width * zoom;
293 unsigned long pys_height = height * zoom;
294 const int bytes_per_pixel = 3;
295 unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
296 art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
297 art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
298 FT_Init_FreeType( &library );
299 while(node){
300 switch (node->type) {
301 case GFX_LINE:
302 case GFX_AREA: {
303 ArtVpath *vec;
304 double dst[6];
305 ArtSVP *svp;
306 art_affine_scale(dst,zoom,zoom);
307 vec = art_vpath_affine_transform(node->path,dst);
308 if(node->type == GFX_LINE){
309 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
310 ART_PATH_STROKE_CAP_ROUND,
311 node->size*zoom,1,1);
312 } else {
313 svp = art_svp_from_vpath ( vec );
314 }
315 art_free(vec);
316 art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
317 node->color, buffer, rowstride, NULL);
318 art_free(svp);
319 break;
320 }
321 case GFX_TEXT: {
322 int error;
323 float text_width=0.0, text_height = 0.0;
324 unsigned char *text;
325 art_u8 fcolor[3],falpha;
326 FT_Face face;
327 FT_GlyphSlot slot;
328 FT_UInt previous=0;
329 FT_UInt glyph_index=0;
330 FT_Bool use_kerning;
332 float pen_x = 0.0 , pen_y = 0.0;
333 /* double x,y; */
334 long ix,iy,iz;
336 fcolor[0] = node->color >> 24;
337 fcolor[1] = (node->color >> 16) & 0xff;
338 fcolor[2] = (node->color >> 8) & 0xff;
339 falpha = node->color & 0xff;
340 error = FT_New_Face( library,
341 (char *)node->filename,
342 0,
343 &face );
344 if ( error ) break;
345 use_kerning = FT_HAS_KERNING(face);
347 error = FT_Set_Char_Size(face, /* handle to face object */
348 (long)(node->size*64),
349 (long)(node->size*64),
350 (long)(100*zoom),
351 (long)(100*zoom));
352 if ( error ) break;
353 pen_x = node->x * zoom;
354 pen_y = node->y * zoom;
355 slot = face->glyph;
357 for(text=(unsigned char *)node->text;*text;text++) {
358 previous = glyph_index;
359 glyph_index = FT_Get_Char_Index( face, *text);
361 if (use_kerning && previous && glyph_index){
362 FT_Vector delta;
363 FT_Get_Kerning( face, previous, glyph_index,
364 0, &delta );
365 text_width += (double)delta.x / 64.0;
367 }
368 error = FT_Load_Glyph( face, glyph_index, 0 );
369 if ( error ) break;
370 if (previous == 0){
371 pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */
372 text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
373 }
374 if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
375 text_height = (double)slot->metrics.horiBearingY / 64.0;
376 }
377 text_width += (double)slot->metrics.horiAdvance / 64.0;
378 }
379 text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
380 text_width += (double)slot->metrics.width / 64.0; /* add just char width */
381 text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
383 switch(node->halign){
384 case GFX_H_RIGHT: pen_x -= text_width; break;
385 case GFX_H_CENTER: pen_x -= text_width / 2.0; break;
386 case GFX_H_LEFT: break;
387 }
389 switch(node->valign){
390 case GFX_V_TOP: pen_y += text_height; break;
391 case GFX_V_CENTER: pen_y += text_height / 2.0; break;
392 case GFX_V_BOTTOM: break;
393 }
395 glyph_index=0;
396 for(text=(unsigned char *)node->text;*text;text++) {
397 int gr;
398 previous = glyph_index;
399 glyph_index = FT_Get_Char_Index( face, *text);
401 if (use_kerning && previous && glyph_index){
402 FT_Vector delta;
403 FT_Get_Kerning( face, previous, glyph_index,
404 0, &delta );
405 pen_x += (double)delta.x / 64.0;
407 }
408 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
409 if ( error ) break;
410 gr = slot->bitmap.num_grays -1;
411 for (iy=0; iy < slot->bitmap.rows; iy++){
412 long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
413 if (buf_y < 0 || buf_y >= pys_height) continue;
414 buf_y *= rowstride;
415 for (ix=0;ix < slot->bitmap.width;ix++){
416 long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
417 art_u8 font_alpha;
419 if (buf_x < 0 || buf_x >= pys_width) continue;
420 buf_x *= bytes_per_pixel ;
421 font_alpha = *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
422 font_alpha = (art_u8)((double)font_alpha / gr * falpha);
423 for (iz = 0; iz < 3; iz++){
424 art_u8 *orig = buffer + buf_y + buf_x + iz;
425 *orig = (art_u8)((double)*orig / gr * ( gr - font_alpha) +
426 (double)fcolor[iz] / gr * (font_alpha));
427 }
428 }
429 }
430 pen_x += (double)slot->metrics.horiAdvance / 64.0;
431 }
432 }
433 }
434 node = node->next;
435 }
436 gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
437 art_free(buffer);
438 FT_Done_FreeType( library );
439 return 0;
440 }
442 /* free memory used by nodes this will also remove memory required for
443 associated paths and svcs ... but not for text strings */
444 int
445 gfx_destroy (gfx_canvas_t *canvas){
446 gfx_node_t *next,*node = canvas->firstnode;
447 while(node){
448 next = node->next;
449 art_free(node->path);
450 art_free(node->svp);
451 free(node->text);
452 free(node->filename);
453 art_free(node);
454 node = next;
455 }
456 return 0;
457 }
459 static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, long bytes_per_pixel){
460 png_structp png_ptr = NULL;
461 png_infop info_ptr = NULL;
462 int i;
463 png_bytep *row_pointers;
464 int rowstride = width * bytes_per_pixel;
465 png_text text[2];
467 if (fp == NULL)
468 return (1);
470 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
471 if (png_ptr == NULL)
472 {
473 return (1);
474 }
475 row_pointers = (png_bytepp)png_malloc(png_ptr,
476 height*sizeof(png_bytep));
478 info_ptr = png_create_info_struct(png_ptr);
480 if (info_ptr == NULL)
481 {
482 png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
483 return (1);
484 }
486 if (setjmp(png_jmpbuf(png_ptr)))
487 {
488 /* If we get here, we had a problem writing the file */
489 png_destroy_write_struct(&png_ptr, &info_ptr);
490 return (1);
491 }
493 png_init_io(png_ptr, fp);
494 png_set_IHDR (png_ptr, info_ptr,width, height,
495 8, PNG_COLOR_TYPE_RGB,
496 PNG_INTERLACE_NONE,
497 PNG_COMPRESSION_TYPE_DEFAULT,
498 PNG_FILTER_TYPE_DEFAULT);
500 text[0].key = "Software";
501 text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
502 text[0].compression = PNG_TEXT_COMPRESSION_NONE;
503 png_set_text (png_ptr, info_ptr, text, 1);
505 /* Write header data */
506 png_write_info (png_ptr, info_ptr);
508 for (i = 0; i < height; i++)
509 row_pointers[i] = (png_bytep) (buffer + i*rowstride);
511 png_write_image(png_ptr, row_pointers);
512 png_write_end(png_ptr, info_ptr);
513 png_destroy_write_struct(&png_ptr, &info_ptr);
514 return 1;
515 }