/* draw.c: Drawing functions (continuous stroke) in a hf_struct * * Copyright (C) 2003-2004 Patrice St-Gelais * patrstg@users.sourceforge.net * www.oricom.ca/patrice.st-gelais * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "draw.h" #include "waves.h" draw_buf *draw_buf_new (gint size) { // Buffer containing a "moving window" of the last pixels drawn // Allows a proper merging of the pixels drawn with the original image. // so that there are no holes nor superimposition when the angles // of successive and connected segments are not the same // 2004-01-04 Not required for now draw_buf *db; db = (draw_buf *) malloc(sizeof(draw_buf)); db->size = size; // Diameter db->data = (hf_type *) calloc(db->size * db->size,sizeof(hf_type)); // printf("CALLOC in draw_buf_new: %d\n",db->size * db->size * sizeof(hf_type)); db->active = FALSE; return db; } void draw_buf_init (draw_buf *db) { // Reinitialize the draw buffer to 0 - public method gint i; db->active = FALSE; for (i=0; i<(db->size*db->size); i++) *(db->data+i) = (hf_type) 0; } map_struct *map_new () { // A simple allocator map_struct *ms; ms = (map_struct *) malloc(sizeof(map_struct)); // We allocate the map to the max radius*2+1, to allow maps without square symmetry ms->data = (hf_type *) malloc(sizeof(hf_type) * (2*MAX_PEN_RADIUS+1) * (2*MAX_PEN_RADIUS+1)); ms->tmp = (hf_type *) malloc(sizeof(hf_type) * (2*MAX_PEN_RADIUS+1) * (2*MAX_PEN_RADIUS+1)); ms->radius = 0; ms->square_symmetry = TRUE; ms->dr_buf = NULL; return ms; } void draw_buf_free (draw_buf *dr_buf) { if (dr_buf->data) free(dr_buf->data); free(dr_buf); } void map_init (map_struct *map, gint size, gint level, gint shape, gdouble spacing) { gint x,y, radius; gdouble dlevel, offset, ddist, p, maxd = (gdouble) MAX_HF_VALUE; static gdouble e=2.718281828; shape_type *shape_data=NULL; radius = size >> 1; // The map size should always be odd size = 2*radius+1; if (spacing == SPACING_HIGH_QUALITY) { dlevel = LEVEL_HQ_SPACING; } else { if (spacing == SPACING_STANDARD_QUALITY) dlevel = LEVEL_SQ_SPACING; else if (spacing == SPACING_VHIGH_QUALITY) dlevel = LEVEL_VHQ_SPACING; else dlevel = LEVEL_LQ_SPACING; } if (shape != NO_WAVE_SHAPE) { shape_data = shape_type_new(shape, size); map->square_symmetry = FALSE; } else map->square_symmetry = TRUE; // Position (0,0) in the map is the minimum value // printf("RADIUS: %d; SPACING: %5.2f; LEVEL: %d; DLEVEL: %5.2f; map->data: %d\n",radius,spacing,level,dlevel, map->data); // We "clamp" all the edges to 0 // The maximum edge value is at (0,radius) or (radius,0) or (2*radius, radius) or (radius, 2*radius) offset = BELLD(e,2.0,1.5*DIST2(0,0,0,radius),radius); if (shape_data) { dlevel = ((gdouble) level)*dlevel/(100.0*(gdouble) MAX_HF_VALUE); // dlevel = ((gdouble) level)*dlevel/100.0; // We intersect the gaussian bell (values from 0.0 to 1.0) with the pen tip shape for (x=0; xdata+VECTORIZE(x,y,size)) = (hf_type) (dlevel * *(shape_data+y) * MAX(0.0, BELLD(e,2.0,1.5*ABS(radius-x),radius) - offset) ); */ // Technique #2: same as technique #1, but the shape // is emphasized only around the center // Towards the edges, we average it with a gaussian bell // in proportion with the square of the distance // Increasing the base to 10.0 shortens the shape // so that even with sharp tips, straight lines are invisible in stroke curves p = MAX(0.0, BELLD(10.0,2.0,1.5*ABS(radius-x),radius) - offset); // printf("(%d,%d): p: %5.3f; shape part: %5.2f; gauss part: %5.2f;\n", x, y, p, // p * *(shape_data+y) , (1.0-p)*MAX(0.0, BELLD(e,2.0,1.5*ABS(radius-y),radius) - offset) * maxd ); *(map->data+VECTORIZE(x,y,size)) = (hf_type) (dlevel * (p * *(shape_data+y) + (1.0-p) * MAX(0.0, BELLD(e,2.0,1.5*ABS(radius-y),radius) - offset) * maxd ) * MAX(0.0, BELLD(e,2.0,1.5*ABS(radius-x),radius) - offset) ); } } else { // Level is relative (50 means 50%) dlevel = ((gdouble) level)*dlevel/100.0; for (x=0; x<(radius+1); x++) for (y=0; y<(radius+1); y++) { ddist = (gdouble) DIST2(0,0,x,y); if (ddist >= radius) *(map->data+VECTORIZE(radius-x,radius-y,radius+1)) = 0; else // if ( (!x) && (!y)) // TEST *(map->data+VECTORIZE(radius-x,radius-y,radius+1)) = (hf_type) (dlevel * MAX(0.0, BELLD(e,2.0,1.5*ddist,radius) - offset) ); // = 0xFFFF; // TEST } } map->radius = radius; if (map->dr_buf) draw_buf_free(map->dr_buf); map->dr_buf = NULL; if (shape_data) free(shape_data); } void map_free (map_struct *map) { if (!map) return; if (map->data) { free(map->data); map->data = NULL; } if (map->tmp) { free(map->tmp); map->tmp = NULL; } map->radius = 0; if (map->dr_buf) { draw_buf_free(map->dr_buf); map->dr_buf = NULL; } } pen_struct *pen_new() { pen_struct *p; p = (pen_struct *) malloc(sizeof(pen_struct)); p->size = 83; p->level = 25; // 1 to 100% p->smoothing = SMOOTHING_AUTO; p->spacing = SPACING_HIGH_QUALITY; p->merge = ADD; p->wrap = TILING_AUTO; p->shape = NO_WAVE_SHAPE; p->map = map_new(); map_init(p->map, p->size, p->level, p->shape, p->spacing) ; return p; } void pen_free(pen_struct *ps) { if (!ps) return; if (ps->map) map_free(ps->map); free(ps); } void merge_line (hf_type *out_buf, gint out_x, gint out_y, hf_type *hf, gint hf_size, gint from_x, gint from_y, map_struct *map, gboolean wrap) { // Merge the buffer "out_buf" containing a rotated line // with a hf, beginning at (from_x, from_y) // The calling function must reorder from_x, to_x and from_y,to_y // so that to_x > from_x and to_y > from_y // The "map" gives the radius and the buffer used to compute // the actual value added to the current pixel gint i,j, from_out_x, from_out_y, hf_x, hf_y, idx; glong value_to_write; hf_type value_already_written=0, new_value; // printf("BEFORE TRANSLATION: (from_x,from_y): (%d,%d)\n",from_x,from_y); // The map should begin at the buffer corner! if (wrap) { from_x = WRAP2(from_x - map->radius, hf_size); from_y = WRAP2(from_y - map->radius, hf_size); // (from_out_x, from_out_y): the offset in out_buf from_out_x = 0; from_out_y = 0; } else { // The offset is not 0 when the out_buf corner falls before 0 from_out_x = (from_xradius)?(map->radius-from_x):0; from_out_y = (from_yradius)?(map->radius-from_y):0; from_x = from_x - map->radius; from_y = from_y - map->radius; } // printf("AFTER TRANSLATION: (from_x, from_y): (%d,%d); (from_out_x,from_out_y): (%d,%d);\n",from_x,from_y, from_out_x, from_out_y); for (i=from_out_x; i the pixel has been drawn! // We draw nothing if the new value is less or equal than the value already written // new_value is passed to check_value... because if the pixel is in the buffer range and // if its value is more than the value already written, it replaces the old value idx =VECTORIZE(hf_x,hf_y,hf_size); value_already_written = *(map->dr_buf->data+idx); if ( ((hf_type) new_value) > value_already_written ) { *(map->dr_buf->data+idx) = new_value; value_to_write =( (glong) *(hf+idx) ) + (glong) (new_value - value_already_written); *(hf+idx) = (hf_type) MIN(0xFFFF,MAX(value_to_write,0)); } } } } gdouble get_x (gdouble x_rot, gpointer args) { // Deduce X // (1) In the map buffer virtually stretched as a thick horizontal line // (2) In the map buffer considered as a quarter of the virtual map (square symmetry) gdouble x, radius, dist; radius = (gdouble) *((gint *)args); dist = (gdouble) *(((gint *)args)+1); x = x_rot; // Taking into account the stretched line means: // y_rot is unchanged... >=0 and <=radius (index in base 0) // x_rot: (1) unchanged if <=radius // (2) == radius if >radius && <=(radius+dist) // (3) == (x_rot - dist) if >(radius+dist) if (x > radius) { if (x <= (radius+dist) ) x = radius; else x = x-dist; } // Now we calculate the actual index in the map if (x > radius) x = 2*radius - x; return x; } gdouble get_y (gdouble y_rot, gpointer args) { // Deduce Y, taking into account that the map buffer, of size (radius+1), // is a quarter of the actual map to draw (it has a quare symmetry) gdouble y, radius; radius = (gdouble) *((gint*)args); y = y_rot; if (y > radius) y = 2*radius - y; return y; } void rotate_line (gint dx, gint dy, map_struct* map, hf_type *out_buf, gint out_x, gint out_y) { // Rotate the virtual line in map->data, of map->radius, // stretched so that the total length of the line is (2*radius)+dist, // and put the result in out_buf, of size (out_x,out_y) // Sine and cosine are given by dx, dy (no explicit computation of angle) // out_buf is supposed to be allocated to the right size // See also rotate_map in hf_calc.c // Reverse mapping process gdouble ddist, cos, sin, x_rot, y_rot, xx, yy, dradius, line_len, line_h; gint x, y, dist, x_in, y_in, ix; static gint vect[2]; hf_type value; // "dist" is the length of the line to draw ddist = ceil( sqrt( pow( (gdouble) dx,2.0) + pow( (gdouble) dy, 2.0) ) ); dist = (gint) ddist; cos = ((gdouble) ABS(dx)) / ddist; sin = -((gdouble) ABS(dy)) / ddist; // Reverse mapping dradius= (gdouble) map->radius; line_len = (2.0*dradius) + ddist; line_h = 2.0*dradius; // printf("dradius: %5.1f; (dx,dy): (%d,%d); dist: %d; sin: %5.2f; cos: %5.2f;\n",dradius,dx,dy,dist, sin,cos); for (y=0; yline_len) || (y_rot>line_h)) { value = 0; // printf("(x,y): (%d,%d); (xx,yy): (%5.2f, %5.2f); (x_rot,y_rot): (%7.2f, %7.2f); (x_in, y_in): (0, 0); ",x,y,xx,yy,x_rot,y_rot); } else { vect[0] = map->radius; vect[1] = dist; value = interpolate_get_xy (x_rot, y_rot, map->data, map->radius+1, map->radius+1, get_x, get_y, vect, vect); // printf("(x,y): (%d,%d); (xx,yy): (%5.2f, %5.2f); (x_rot,y_rot): (%7.2f, %7.2f); (x_in, y_in): (%d, %d); ",x,y,xx,yy,x_rot,y_rot,x_in,y_in); } // printf("Data: %d;\n",value); // Revert the indexes depending on the vector direction if ((dx<0) != (dy<0)) // Exclusive OR ix = out_x - x - 1; else ix = x; *(out_buf+VECTORIZE(ix,y,out_x)) = value; } } } void draw_continuous_line (hf_struct_type *hf, pen_struct *pen, gint from_x, gint from_y, gint to_x, gint to_y, gboolean draw_end) { // Drawing a slanted gaussian line by rotating an horizontal line gint out_x, out_y, old_to_x, old_to_y, dx, dy; gboolean wrap; map_struct *map; hf_type *out_buf; map = pen->map; // printf ("DRAW_LINE: from (%d,%d) to (%d,%d);\n", from_x,from_y, to_x, to_y); // The map struct contains the buffer for testing // if something has been drawn (map->dr_buf) dx = to_x - from_x; dy = to_y - from_y; // Radius is 0 for a size of 1, 1 for a size of 3... up to 64 for a size of 127 // Size is always odd wrap = (pen->wrap==TILING_YES) || ((pen->wrap==TILING_AUTO) && hf->if_tiles); // printf("1- MAP: %d; MAP->data: %d; MAP->radius: %d\n",map, map->data,map->radius); // 2004-01-07 Not required, since the map is now updated without delay // if (!map->radius) { // The map needs to be rebuilt // map_init( map, pen->size , pen->level, pen->shape, pen->spacing) ; // } // The buffer used to control the overlapping of the connected line // must be the size of the current HF if (!map->dr_buf) map->dr_buf = draw_buf_new(hf->max_x); else if (map->dr_buf->size != hf->max_x) { draw_buf_free(map->dr_buf); map->dr_buf = draw_buf_new(hf->max_x); } // printf("2- MAP: %d; MAP->data: %d; MAP->radius: %d\n",map, map->data,map->radius); // out_buf contains the rotated line out_x = pen->size + ABS(to_x - from_x); out_y = pen->size + ABS(to_y - from_y); out_buf = (hf_type*) calloc(out_x*out_y,sizeof(hf_type)); // Rotate the line here, around (from_x,from_y) rotate_line (to_x - from_x , to_y - from_y , map, out_buf, out_x, out_y); // printf("3- MAP: %d; MAP->data: %d; MAP->radius: %d\n",map, map->data,map->radius); // Draw the line in the HF old_to_x = to_x; old_to_y = to_y; order_int (&from_x, &to_x); order_int (&from_y, &to_y); merge_line(out_buf, out_x, out_y, hf->hf_buf, hf->max_x, from_x, from_y, map, wrap); // The draw buffer is reinitialized to 0 at the end of a stroke (mouse just released) if (draw_end) draw_buf_init (map->dr_buf); if (out_buf) free(out_buf); } void draw_one_dot (hf_struct_type *hf, pen_struct *pen, gint x, gint y, gdouble **gauss_list) { // Drawing a unique dot, usually for drawing the first dot of a stroke // The remainder of the stroke is drawn with draw_continuous_line_by_dot gboolean wrap; map_struct *map; map = pen->map; wrap = (pen->wrap==TILING_YES) || ((pen->wrap==TILING_AUTO) && hf->if_tiles); if (pen->merge == SMOOTH_OP) map_convolve (map->data,2*map->radius+1, 2*map->radius+1, hf->hf_buf, hf->max_x, hf->max_y, x, y, wrap, 100, gauss_list, TRUE); else generalized_merge( (gpointer) map->data, HF_TYPE_ID, 2*map->radius+1, 2*map->radius+1, hf->hf_buf, HF_TYPE_ID, hf->max_x, hf->max_y, x, y, pen->merge, wrap, pen->map->square_symmetry); } gboolean draw_continuous_line_by_dot (hf_struct_type *hf, pen_struct *pen, gdouble begin_x, gdouble begin_y, gdouble *end_x_ptr, gdouble *end_y_ptr, gboolean draw_end, gdouble **gauss_list) { // Same concept as draw_hf_line // Draw in real coordinates (interpolated from (gdouble)) // Instead of drawing a HF, we draw a gaussian map, whose size is more versatile // We smooth progressively what is drawn // We must draw in a buffer, size of the current HF // Return TRUE if something has been drawn, FALSE otherwise gint i,steps, map_size; gdouble spacing, dist, di, dx, dy, ddist=0.0, end_x, end_y; gboolean wrap; map_struct *map; hf_type *map_to_use; map = pen->map; // We force the map size to be odd map_size = 2*map->radius+1; end_x = *end_x_ptr; end_y = *end_y_ptr; wrap = (pen->wrap==TILING_YES) || ((pen->wrap==TILING_AUTO) && hf->if_tiles); // Radius is 0 for a size of 1, 1 for a size of 3... up to 127 for a size of 255 // Size should always be odd // Draw each dot from the last x,y // Won't draw the last (just before the mouse release) // spacing is the increment applied, in pixels, each time we draw a dot spacing = pen->spacing * (gdouble) map_size; dist = (gdouble) DIST2((gdouble) begin_x, (gdouble) begin_y, (gdouble) end_x, (gdouble) end_y); steps = (gint) floor(dist/spacing); if (!steps) { // We return FALSE is nothing is drawn, // so that the (begin_x, begin_y) coordinates are not changed, for the next line to draw return FALSE; } dx = spacing * ((gdouble) (end_x - begin_x)) / dist; dy = spacing * ((gdouble) (end_y - begin_y)) /dist ; // printf("DX: %5.2f; DY: %5.2f; shape: %d; symm: %d\n",dx,dy,pen->shape, pen->map->square_symmetry); for (i=1; i<=steps; i++) { end_x =begin_x + dx * (gdouble) i; end_y = begin_y + dy * (gdouble) i; if (pen->merge == SMOOTH_OP) map_convolve (map->data,map_size, map_size, hf->hf_buf, hf->max_x, hf->max_y, (gint) end_x, (gint) end_y, wrap, 100, gauss_list, TRUE); else { if (pen->shape != NO_WAVE_SHAPE) { rotate (dx, dy, map->data, map->tmp, map_size, map_size, HF_TYPE_ID, OVERFLOW_ZERO); map_to_use = map->tmp; } else map_to_use = map->data; if ( (pen->smoothing==SMOOTHING_ALL) || ( (pen->smoothing==SMOOTHING_AUTO) && (map->radiusmerge, wrap, pen->map->square_symmetry); else generalized_merge( (gpointer) map_to_use, HF_TYPE_ID, map_size, map_size, hf->hf_buf, HF_TYPE_ID, hf->max_x, hf->max_y, (gint) end_x, (gint) end_y, pen->merge, wrap, pen->map->square_symmetry); } } // The last dot drawn doesn't fell on the coordinates where the motion motify event was emitted, // so we must adjust these coordinates (*end_x_ptr) = end_x; (*end_y_ptr) = end_y; return TRUE; }