do propper error checking and release memmory when it is not required anymore
[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 ( 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 }
228  
229
230
231
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){
239     
240     
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;
284
285             float pen_x = 0.0 , pen_y = 0.0;
286             /* double x,y; */
287             long   ix,iy,iz;
288             
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);
299
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;
309
310             for(text=(unsigned char *)node->text;*text;text++) {        
311                 previous = glyph_index;
312                 glyph_index = FT_Get_Char_Index( face, *text);
313                 
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;
319                     
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 */
335             
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             }
341
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             }
347
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);
353                 
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;
359                     
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;
371                         
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 }
394
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 }
411  
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];
419   
420   if (fp == NULL)
421     return (1);
422
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));
430
431   info_ptr = png_create_info_struct(png_ptr);
432
433   if (info_ptr == NULL)
434     {
435       png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
436       return (1);
437     }
438
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     }
445
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);
452
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);
457
458   /* Write header data */
459   png_write_info (png_ptr, info_ptr);
460
461   for (i = 0; i < height; i++)
462     row_pointers[i] = (png_bytep) (buffer + i*rowstride);
463
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 }