The BIG graph update
[rrdtool.git] / src / rrd_gfx.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd_gfx.c  graphics wrapper for rrdtool
5   **************************************************************************/
6
7 /* #define DEBUG */
8
9 #ifdef DEBUG
10 # define DPRINT(x)    (void)(printf x, printf("\n"))
11 #else
12 # define DPRINT(x)
13 #endif
14
15 #include <png.h>
16 #include <ft2build.h>
17 #include FT_FREETYPE_H
18
19 #include "rrd_gfx.h"
20
21 /* lines are better drawn on the pixle than between pixles */
22 #define LINEOFFSET 0.5
23
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 }
52
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 }
59
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){
65
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;
75   
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 }
83
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) {
90
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;
102   
103   node->points = 5;
104   node->points_max = 5;
105   node->color = color;
106   node->path  = vec;
107
108   return node;
109 }
110
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);
146     
147   } else {
148     /* can only add point to areas and lines */
149     return 1;
150   }
151   return 0;
152 }
153
154
155
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    }
169    
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 }
181
182 double gfx_get_text_width ( double start, char* font, double size,                            
183                             double tabwidth, char* text){
184
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;
198
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);
204     
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;
210       
211     }
212     error = FT_Load_Glyph( face, glyph_index, 0 );
213     if (! previous) {
214       text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */        
215     }
216     text_width += (double)slot->metrics.horiAdvance / 64.0;
217   }
218   text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
219   text_width += (double)slot->metrics.width / 64.0; /* add just char width */
220   text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
221   return text_width;
222 }
223  
224
225
226
227 static int gfx_save_png (art_u8 *buffer, FILE *fp,
228                      long width, long height, long bytes_per_pixel);
229 /* render grafics into png image */
230 int           gfx_render_png (gfx_canvas_t *canvas, 
231                               art_u32 width, art_u32 height, 
232                               double zoom, 
233                               gfx_color_t background, FILE *fp){
234     
235     
236     FT_Library    library;
237     gfx_node_t *node = canvas->firstnode;    
238     art_u8 red = background >> 24, green = (background >> 16) & 0xff;
239     art_u8 blue = (background >> 8) & 0xff, alpha = ( background & 0xff );
240     unsigned long pys_width = width * zoom;
241     unsigned long pys_height = height * zoom;
242     const int bytes_per_pixel = 3;
243     unsigned long rowstride = pys_width*bytes_per_pixel; /* bytes per pixel */
244     art_u8 *buffer = art_new (art_u8, rowstride*pys_height);
245     art_rgb_run_alpha (buffer, red, green, blue, alpha, pys_width*pys_height);
246     FT_Init_FreeType( &library );
247     while(node){
248         switch (node->type) {
249         case GFX_LINE:
250         case GFX_AREA: {   
251             ArtVpath *vec;
252             double dst[6];     
253             ArtSVP *svp;
254             art_affine_scale(dst,zoom,zoom);
255             vec = art_vpath_affine_transform(node->path,dst);
256             if(node->type == GFX_LINE){
257                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
258                                              ART_PATH_STROKE_CAP_ROUND,
259                                              node->size*zoom,1,1);
260             } else {
261                 svp = art_svp_from_vpath ( vec );
262             }
263             art_free(vec);
264             art_rgb_svp_alpha (svp ,0,0, pys_width, pys_height,
265                                node->color, buffer, rowstride, NULL);
266             art_free(svp);
267             break;
268         }
269         case GFX_TEXT: {
270             int  error;
271             float text_width=0.0, text_height = 0.0;
272             unsigned char *text;
273             art_u8 fcolor[3],falpha;
274             FT_Face       face;
275             FT_GlyphSlot  slot;
276             FT_UInt       previous=0;
277             FT_UInt       glyph_index=0;
278             FT_Bool       use_kerning;
279
280             float pen_x = 0.0 , pen_y = 0.0;
281             /* double x,y; */
282             long   ix,iy,iz;
283             
284             fcolor[0] = node->color >> 24;
285             fcolor[1] = (node->color >> 16) & 0xff;
286             fcolor[2] = (node->color >> 8) & 0xff;
287             falpha = node->color & 0xff;
288             error = FT_New_Face( library,
289                                  (char *)node->filename,
290                                  0,
291                                  &face );
292             use_kerning = FT_HAS_KERNING(face);
293
294             if ( error ) break;
295             error = FT_Set_Char_Size(face,   /* handle to face object            */
296                                      (long)(node->size*64),
297                                      (long)(node->size*64),
298                                      (long)(100*zoom),
299                                      (long)(100*zoom));
300             pen_x = node->x * zoom;
301             pen_y = node->y * zoom;
302             slot = face->glyph;
303
304             for(text=(unsigned char *)node->text;*text;text++) {        
305                 previous = glyph_index;
306                 glyph_index = FT_Get_Char_Index( face, *text);
307                 
308                 if (use_kerning && previous && glyph_index){
309                     FT_Vector  delta;
310                     FT_Get_Kerning( face, previous, glyph_index,
311                                     0, &delta );
312                     text_width += (double)delta.x / 64.0;
313                     
314                 }
315                 error = FT_Load_Glyph( face, glyph_index, 0 );
316                 if (previous == 0){
317                   pen_x -= (double)slot->metrics.horiBearingX / 64.0; /* adjust pos for first char */   
318                   text_width -= (double)slot->metrics.horiBearingX / 64.0; /* add just char width */    
319                 }
320                 if ( text_height < (double)slot->metrics.horiBearingY / 64.0 ) {
321                   text_height = (double)slot->metrics.horiBearingY / 64.0;
322                 }
323                 text_width += (double)slot->metrics.horiAdvance / 64.0;
324             }
325             text_width -= (double)slot->metrics.horiAdvance / 64.0; /* remove last step */
326             text_width += (double)slot->metrics.width / 64.0; /* add just char width */
327             text_width += (double)slot->metrics.horiBearingX / 64.0; /* add just char width */
328             
329             switch(node->halign){
330             case GFX_H_RIGHT:  pen_x -= text_width; break;
331             case GFX_H_CENTER: pen_x -= text_width / 2.0; break;          
332             case GFX_H_LEFT: break;          
333             }
334
335             switch(node->valign){
336             case GFX_V_TOP:    pen_y += text_height; break;
337             case GFX_V_CENTER: pen_y += text_height / 2.0; break;          
338             case GFX_V_BOTTOM: break;          
339             }
340
341             glyph_index=0;
342             for(text=(unsigned char *)node->text;*text;text++) {
343                 int gr;          
344                 previous = glyph_index;
345                 glyph_index = FT_Get_Char_Index( face, *text);
346                 
347                 if (use_kerning && previous && glyph_index){
348                     FT_Vector  delta;
349                     FT_Get_Kerning( face, previous, glyph_index,
350                                     0, &delta );
351                     pen_x += (double)delta.x / 64.0;
352                     
353                 }
354                 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER );
355                 gr = slot->bitmap.num_grays -1;
356                 for (iy=0; iy < slot->bitmap.rows; iy++){
357                     long buf_y = iy+(pen_y+0.5)-slot->bitmap_top;
358                     if (buf_y < 0 || buf_y >= pys_height) continue;
359                     buf_y *= rowstride;
360                     for (ix=0;ix < slot->bitmap.width;ix++){
361                         long buf_x = ix + (pen_x + 0.5) + (double)slot->bitmap_left ;
362                         art_u8 font_alpha;
363                         
364                         if (buf_x < 0 || buf_x >= pys_width) continue;
365                         buf_x *=  bytes_per_pixel ;
366                         font_alpha =  *(slot->bitmap.buffer + iy * slot->bitmap.width + ix);
367                         font_alpha =  (art_u8)((double)font_alpha / gr * falpha);
368                         for (iz = 0; iz < 3; iz++){
369                             art_u8 *orig = buffer + buf_y + buf_x + iz;
370                             *orig =  (art_u8)((double)*orig / gr * ( gr - font_alpha) +
371                                               (double)fcolor[iz] / gr * (font_alpha));
372                         }
373                     }
374                 }
375                 pen_x += (double)slot->metrics.horiAdvance / 64.0;
376             }
377         }
378         }
379         node = node->next;
380     }  
381     gfx_save_png(buffer,fp , pys_width,pys_height,bytes_per_pixel);
382     art_free(buffer);
383     return 0;    
384 }
385
386 /* free memory used by nodes this will also remove memory required for
387    associated paths and svcs ... but not for text strings */
388 int
389 gfx_destroy    (gfx_canvas_t *canvas){  
390   gfx_node_t *next,*node = canvas->firstnode;
391   while(node){
392     next = node->next;
393     art_free(node->path);
394     art_free(node->svp);
395     free(node->text);
396     free(node->filename);
397     art_free(node);
398     node = next;
399   }
400   return 0;
401 }
402  
403 static int gfx_save_png (art_u8 *buffer, FILE *fp,  long width, long height, long bytes_per_pixel){
404   png_structp png_ptr = NULL;
405   png_infop   info_ptr = NULL;
406   int i;
407   png_bytep *row_pointers;
408   int rowstride = width * bytes_per_pixel;
409   png_text text[2];
410   
411   if (fp == NULL)
412     return (1);
413
414   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
415   if (png_ptr == NULL)
416    {
417       return (1);
418    }
419    row_pointers = (png_bytepp)png_malloc(png_ptr,
420                                      height*sizeof(png_bytep));
421
422   info_ptr = png_create_info_struct(png_ptr);
423
424   if (info_ptr == NULL)
425     {
426       png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
427       return (1);
428     }
429
430   if (setjmp(png_jmpbuf(png_ptr)))
431     {
432       /* If we get here, we had a problem writing the file */
433       png_destroy_write_struct(&png_ptr, &info_ptr);
434       return (1);
435     }
436
437   png_init_io(png_ptr, fp);
438   png_set_IHDR (png_ptr, info_ptr,width, height,
439                 8, PNG_COLOR_TYPE_RGB,
440                 PNG_INTERLACE_NONE,
441                 PNG_COMPRESSION_TYPE_DEFAULT,
442                 PNG_FILTER_TYPE_DEFAULT);
443
444   text[0].key = "Software";
445   text[0].text = "RRDtool, Tobias Oetiker <tobi@oetike.ch>, http://tobi.oetiker.ch";
446   text[0].compression = PNG_TEXT_COMPRESSION_NONE;
447   png_set_text (png_ptr, info_ptr, text, 1);
448
449   /* Write header data */
450   png_write_info (png_ptr, info_ptr);
451
452   for (i = 0; i < height; i++)
453     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
454
455   png_write_image(png_ptr, row_pointers);
456   png_write_end(png_ptr, info_ptr);
457   png_destroy_write_struct(&png_ptr, &info_ptr);
458   return 1;
459 }