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
19 #include "rrd_gfx.h"
21 /* lines are better drawn on the pixle than between pixles */
22 #define LINEOFFSET 0.5
24 static
25 gfx_node_t *gfx_new_node( gfx_canvas_t *canvas,enum gfx_en type){
26 gfx_node_t *node = art_new(gfx_node_t,1);
27 if (node == NULL) return NULL;
28 node->type = type;
29 node->color = 0x0; /* color of element 0xRRGGBBAA alpha 0xff is solid*/
30 node->size =0.0; /* font size, line width */
31 node->path = NULL; /* path */
32 node->points = 0;
33 node->points_max =0;
34 node->svp = NULL; /* svp */
35 node->filename = NULL; /* font or image filename */
36 node->text = NULL;
37 node->x = 0.0;
38 node->y = 0.0; /* position */
39 node->halign = GFX_H_NULL; /* text alignement */
40 node->valign = GFX_V_NULL; /* text alignement */
41 node->tabwidth = 0.0;
42 node->next = NULL;
43 if (canvas->lastnode != NULL){
44 canvas->lastnode->next = node;
45 }
46 if (canvas->firstnode == NULL){
47 canvas->firstnode = node;
48 }
49 canvas->lastnode = node;
50 return node;
51 }
53 gfx_canvas_t *gfx_new_canvas (void) {
54 gfx_canvas_t *canvas = art_new(gfx_canvas_t,1);
55 canvas->firstnode = NULL;
56 canvas->lastnode = NULL;
57 return canvas;
58 }
60 /* create a new line */
61 gfx_node_t *gfx_new_line(gfx_canvas_t *canvas,
62 double x0, double y0,
63 double x1, double y1,
64 double width, gfx_color_t color){
66 gfx_node_t *node;
67 ArtVpath *vec;
68 node = gfx_new_node(canvas,GFX_LINE);
69 if (node == NULL) return NULL;
70 vec = art_new(ArtVpath, 3);
71 if (vec == NULL) return NULL;
72 vec[0].code = ART_MOVETO_OPEN; vec[0].x=x0+LINEOFFSET; vec[0].y=y0+LINEOFFSET;
73 vec[1].code = ART_LINETO; vec[1].x=x1+LINEOFFSET; vec[1].y=y1+LINEOFFSET;
74 vec[2].code = ART_END;
76 node->points = 3;
77 node->points_max = 3;
78 node->color = color;
79 node->size = width;
80 node->path = vec;
81 return node;
82 }
84 /* create a new area */
85 gfx_node_t *gfx_new_area (gfx_canvas_t *canvas,
86 double x0, double y0,
87 double x1, double y1,
88 double x2, double y2,
89 gfx_color_t color) {
91 gfx_node_t *node;
92 ArtVpath *vec;
93 node = gfx_new_node(canvas,GFX_AREA);
94 if (node == NULL) return NULL;
95 vec = art_new(ArtVpath, 5);
96 if (vec == NULL) return NULL;
97 vec[0].code = ART_MOVETO; vec[0].x=x0; vec[0].y=y0;
98 vec[1].code = ART_LINETO; vec[1].x=x1; vec[1].y=y1;
99 vec[2].code = ART_LINETO; vec[2].x=x2; vec[2].y=y2;
100 vec[3].code = ART_LINETO; vec[3].x=x0; vec[3].y=y0;
101 vec[4].code = ART_END;
103 node->points = 5;
104 node->points_max = 5;
105 node->color = color;
106 node->path = vec;
108 return node;
109 }
111 /* add a point to a line or to an area */
112 int gfx_add_point (gfx_node_t *node,
113 double x, double y){
114 if (node == NULL) return 1;
115 if (node->type == GFX_AREA) {
116 double x0 = node->path[0].x;
117 double y0 = node->path[0].y;
118 node->points -= 2;
119 art_vpath_add_point (&(node->path),
120 &(node->points),
121 &(node->points_max),
122 ART_LINETO,
123 x,y);
124 art_vpath_add_point (&(node->path),
125 &(node->points),
126 &(node->points_max),
127 ART_LINETO,
128 x0,y0);
129 art_vpath_add_point (&(node->path),
130 &(node->points),
131 &(node->points_max),
132 ART_END,
133 0,0);
134 } else if (node->type == GFX_LINE) {
135 node->points -= 1;
136 art_vpath_add_point (&(node->path),
137 &(node->points),
138 &(node->points_max),
139 ART_LINETO,
140 x+LINEOFFSET,y+LINEOFFSET);
141 art_vpath_add_point (&(node->path),
142 &(node->points),
143 &(node->points_max),
144 ART_END,
145 0,0);
147 } else {
148 /* can only add point to areas and lines */
149 return 1;
150 }
151 return 0;
152 }
156 /* create a text node */
157 gfx_node_t *gfx_new_text (gfx_canvas_t *canvas,
158 double x, double y, gfx_color_t color,
159 char* font, double size,
160 double tabwidth, double angle,
161 enum gfx_h_align_en h_align,
162 enum gfx_v_align_en v_align,
163 char* text){
164 gfx_node_t *node = gfx_new_node(canvas,GFX_TEXT);
165 if (angle != 0.0){
166 /* currently we only support 0 and 270 */
167 angle = 270.0;
168 }
170 node->text = strdup(text);
171 node->size = size;
172 node->filename = strdup(font);
173 node->x = x;
174 node->y = y;
175 node->color = color;
176 node->tabwidth = tabwidth;
177 node->halign = h_align;
178 node->valign = v_align;
179 return node;
180 }
182 double gfx_get_text_width ( double start, char* font, double size,
183 double tabwidth, char* text){
185 FT_GlyphSlot slot;
186 FT_UInt previous=0;
187 FT_UInt glyph_index=0;
188 FT_Bool use_kerning;
189 int error;
190 FT_Face face;
191 FT_Library library=NULL;
192 double text_width=0;
193 FT_Init_FreeType( &library );
194 error = FT_New_Face( library, font, 0, &face );
195 if ( error ) return -1;
196 error = FT_Set_Char_Size(face, size*64,size*64, 100,100);
197 if ( error ) return -1;
199 use_kerning = FT_HAS_KERNING(face);
200 slot = face->glyph;
201 for(;*text;text++) {
202 previous = glyph_index;
203 glyph_index = FT_Get_Char_Index( face, *text);
205 if (use_kerning && previous && glyph_index){
206 FT_Vector delta;
207 FT_Get_Kerning( face, previous, glyph_index,
208 0, &delta );
209 text_width += (double)delta.x / 64.0;
211 }
212 error = FT_Load_Glyph( face, glyph_index, 0 );
213 if ( error ) {
214 FT_Done_FreeType(library);
215 return -1;
216 }
217 if (! previous) {
218 text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
219 }
220 text_width += (double)slot->metrics.horiAdvance / 64.0;
221 }
222 text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
223 text_width += (double)slot->metrics.width / 64.0; /* add just char width */
224 text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
225 FT_Done_FreeType(library);
226 return text_width;
227 }
232 static int gfx_save_png (art_u8 *buffer, FILE *fp,
233 long width, long height, long bytes_per_pixel);
234 /* render grafics into png image */
235 int gfx_render_png (gfx_canvas_t *canvas,
236 art_u32 width, art_u32 height,
237 double zoom,
238 gfx_color_t background, FILE *fp){
241 FT_Library library;
242 gfx_node_t *node = canvas->firstnode;
243 art_u8 red = background >> 24, green = (background >> 16) & 0xff;
244 art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
245 unsigned long pys_width = width * zoom;
246 unsigned long pys_height = height * zoom;
247 const int bytes_per_pixel = 3;
248 unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
249 art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
250 art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
251 FT_Init_FreeType( &library );
252 while(node){
253 switch (node->type) {
254 case GFX_LINE:
255 case GFX_AREA: {
256 ArtVpath *vec;
257 double dst[6];
258 ArtSVP *svp;
259 art_affine_scale(dst,zoom,zoom);
260 vec = art_vpath_affine_transform(node->path,dst);
261 if(node->type == GFX_LINE){
262 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
263 ART_PATH_STROKE_CAP_ROUND,
264 node->size*zoom,1,1);
265 } else {
266 svp = art_svp_from_vpath ( vec );
267 }
268 art_free(vec);
269 art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
270 node->color, buffer, rowstride, NULL);
271 art_free(svp);
272 break;
273 }
274 case GFX_TEXT: {
275 int error;
276 float text_width=0.0, text_height = 0.0;
277 unsigned char *text;
278 art_u8 fcolor[3],falpha;
279 FT_Face face;
280 FT_GlyphSlot slot;
281 FT_UInt previous=0;
282 FT_UInt glyph_index=0;
283 FT_Bool use_kerning;
285 float pen_x = 0.0 , pen_y = 0.0;
286 /* double x,y; */
287 long ix,iy,iz;
289 fcolor[0] = node->color >> 24;
290 fcolor[1] = (node->color >> 16) & 0xff;
291 fcolor[2] = (node->color >> 8) & 0xff;
292 falpha = node->color & 0xff;
293 error = FT_New_Face( library,
294 (char *)node->filename,
295 0,
296 &face );
297 if ( error ) break;
298 use_kerning = FT_HAS_KERNING(face);
300 error = FT_Set_Char_Size(face, /* handle to face object */
301 (long)(node->size*64),
302 (long)(node->size*64),
303 (long)(100*zoom),
304 (long)(100*zoom));
305 if ( error ) break;
306 pen_x = node->x * zoom;
307 pen_y = node->y * zoom;
308 slot = face->glyph;
310 for(text=(unsigned char *)node->text;*text;text++) {
311 previous = glyph_index;
312 glyph_index = FT_Get_Char_Index( face, *text);
314 if (use_kerning && previous && glyph_index){
315 FT_Vector delta;
316 FT_Get_Kerning( face, previous, glyph_index,
317 0, &delta );
318 text_width += (double)delta.x / 64.0;
320 }
321 error = FT_Load_Glyph( face, glyph_index, 0 );
322 if ( error ) break;
323 if (previous == 0){
324 pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */
325 text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
326 }
327 if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
328 text_height = (double)slot->metrics.horiBearingY / 64.0;
329 }
330 text_width += (double)slot->metrics.horiAdvance / 64.0;
331 }
332 text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
333 text_width += (double)slot->metrics.width / 64.0; /* add just char width */
334 text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
336 switch(node->halign){
337 case GFX_H_RIGHT: pen_x -= text_width; break;
338 case GFX_H_CENTER: pen_x -= text_width / 2.0; break;
339 case GFX_H_LEFT: break;
340 }
342 switch(node->valign){
343 case GFX_V_TOP: pen_y += text_height; break;
344 case GFX_V_CENTER: pen_y += text_height / 2.0; break;
345 case GFX_V_BOTTOM: break;
346 }
348 glyph_index=0;
349 for(text=(unsigned char *)node->text;*text;text++) {
350 int gr;
351 previous = glyph_index;
352 glyph_index = FT_Get_Char_Index( face, *text);
354 if (use_kerning && previous && glyph_index){
355 FT_Vector delta;
356 FT_Get_Kerning( face, previous, glyph_index,
357 0, &delta );
358 pen_x += (double)delta.x / 64.0;
360 }
361 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
362 if ( error ) break;
363 gr = slot->bitmap.num_grays -1;
364 for (iy=0; iy < slot->bitmap.rows; iy++){
365 long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
366 if (buf_y < 0 || buf_y >= pys_height) continue;
367 buf_y *= rowstride;
368 for (ix=0;ix < slot->bitmap.width;ix++){
369 long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
370 art_u8 font_alpha;
372 if (buf_x < 0 || buf_x >= pys_width) continue;
373 buf_x *= bytes_per_pixel ;
374 font_alpha = *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
375 font_alpha = (art_u8)((double)font_alpha / gr * falpha);
376 for (iz = 0; iz < 3; iz++){
377 art_u8 *orig = buffer + buf_y + buf_x + iz;
378 *orig = (art_u8)((double)*orig / gr * ( gr - font_alpha) +
379 (double)fcolor[iz] / gr * (font_alpha));
380 }
381 }
382 }
383 pen_x += (double)slot->metrics.horiAdvance / 64.0;
384 }
385 }
386 }
387 node = node->next;
388 }
389 gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
390 art_free(buffer);
391 FT_Done_FreeType( library );
392 return 0;
393 }
395 /* free memory used by nodes this will also remove memory required for
396 associated paths and svcs ... but not for text strings */
397 int
398 gfx_destroy (gfx_canvas_t *canvas){
399 gfx_node_t *next,*node = canvas->firstnode;
400 while(node){
401 next = node->next;
402 art_free(node->path);
403 art_free(node->svp);
404 free(node->text);
405 free(node->filename);
406 art_free(node);
407 node = next;
408 }
409 return 0;
410 }
412 static int gfx_save_png (art_u8 *buffer, FILE *fp, long width, long height, long bytes_per_pixel){
413 png_structp png_ptr = NULL;
414 png_infop info_ptr = NULL;
415 int i;
416 png_bytep *row_pointers;
417 int rowstride = width * bytes_per_pixel;
418 png_text text[2];
420 if (fp == NULL)
421 return (1);
423 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
424 if (png_ptr == NULL)
425 {
426 return (1);
427 }
428 row_pointers = (png_bytepp)png_malloc(png_ptr,
429 height*sizeof(png_bytep));
431 info_ptr = png_create_info_struct(png_ptr);
433 if (info_ptr == NULL)
434 {
435 png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
436 return (1);
437 }
439 if (setjmp(png_jmpbuf(png_ptr)))
440 {
441 /* If we get here, we had a problem writing the file */
442 png_destroy_write_struct(&png_ptr, &info_ptr);
443 return (1);
444 }
446 png_init_io(png_ptr, fp);
447 png_set_IHDR (png_ptr, info_ptr,width, height,
448 8, PNG_COLOR_TYPE_RGB,
449 PNG_INTERLACE_NONE,
450 PNG_COMPRESSION_TYPE_DEFAULT,
451 PNG_FILTER_TYPE_DEFAULT);
453 text[0].key = "Software";
454 text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
455 text[0].compression = PNG_TEXT_COMPRESSION_NONE;
456 png_set_text (png_ptr, info_ptr, text, 1);
458 /* Write header data */
459 png_write_info (png_ptr, info_ptr);
461 for (i = 0; i < height; i++)
462 row_pointers[i] = (png_bytep) (buffer + i*rowstride);
464 png_write_image(png_ptr, row_pointers);
465 png_write_end(png_ptr, info_ptr);
466 png_destroy_write_struct(&png_ptr, &info_ptr);
467 return 1;
468 }