File: g_space.c

package info (click to toggle)
plotutils 2.4.1-11
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 11,676 kB
  • ctags: 6,967
  • sloc: ansic: 76,305; sh: 15,172; cpp: 12,403; yacc: 2,604; makefile: 888; lex: 144
file content (505 lines) | stat: -rw-r--r-- 18,835 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/* This file contains the space method, which is a standard part of
   libplot.  It sets the mapping from user coordinates to display
   coordinates.  On the display device, the drawing region is a fixed
   rectangle (usually a square).  The arguments to the space method are the
   lower left and upper right vertices of a `window' (a drawing rectangle),
   in user coordinates.  This window, whose axes are aligned with the
   coordinate axes, will be mapped affinely onto the drawing region on the
   display device.

   Equivalently, the space method sets the transformation matrix attribute
   that will be used for graphical objects that are subsequently drawn.
   Any transformation matrix produced by invoking space() will necessarily
   preserve coordinate axes.

   This file also contains the space2 method, which is a GNU extension to
   libplot.  The arguments to the space2 method are the vertices of an
   `affine window' (a drawing parallelogram), in user coordinates.  (The
   specified vertices are the lower left, the lower right, and the upper
   left.)  This window will be mapped affinely onto the drawing region on
   the display device.  Transformation matrices produced by invoking
   space() do not necessarily preserve coordinate axes.

   space and space2 are simply wrappers around the fsetmatrix() method. */

/* This file also contains the fsetmatrix method, which is a GNU extension
   to libplot.  Much as in Postscript, it sets the transformation matrix
   from user coordinates to NDC (normalized device coordinates).  This, in
   turn, determines the map from user coordinates to device coordinates.
   The resulting transformation matrix will be used as an attribute of
   objects that are subsequently drawn on the graphics display. */

/* This file also contains the fconcat method, which is a GNU extension to
   libplot.  fconcat is simply a wrapper around fsetmatrix.  As in
   Postscript, it left-multiplies the transformation matrix from user
   coordinates to NDC coordinates by a specified matrix.  That is, it
   modifies the affine transformation from user coordinates to NDC and
   hence to device coordinates, by requiring that the transformation
   currently in effect be be preceded by a specified affine
   transformation. */

/* N.B. Invoking fsetmatrix causes the default line width and default font
   size, which are expressed in user units, to be recomputed.  That is
   because those two quantities are specified as a fraction of the size of
   the display: in device terms, rather than in terms of user units.  The
   idea is that no matter what the arguments of fsetmatrix are, switching
   later to the default line width or default font size, by passing an
   out-of-bounds argument to linewidth() or fontsize(), should yield a
   reasonable result. */

#include "sys-defines.h"
#include "extern.h"

/* possible rotation angles for viewport on output device */
enum rotation_type { ROT_0, ROT_90, ROT_180, ROT_270 };

/* potential roundoff error (absolute, for defining boundary of display) */
#define ROUNDING_FUZZ 0.0000001

/* potential roundoff error (relative, used for checking isotropy etc.) */
#define OTHER_FUZZ 0.0000001

/* The vertices of the parallelogram in user space have coordinates (going
   counterclockwise) (x0,y0), (x1,y1), (x1,y1)+(x2,y2)-(x0,y0), and
   (x2,y2). */

int
#ifdef _HAVE_PROTOS
_API_fspace2 (R___(Plotter *_plotter) double x0, double y0, double x1, double y1, double x2, double y2)
#else
_API_fspace2 (R___(_plotter) x0, y0, x1, y1, x2, y2)
     S___(Plotter *_plotter;) 
     double x0, y0, x1, y1, x2, y2;
#endif
{
  double s[6];
  double v0x, v0y, v1x, v1y, v2x, v2y;
  double cross;

  if (!_plotter->data->open)
    {
      _plotter->error (R___(_plotter) 
		       "fspace2: invalid operation");
      return -1;
    }

  /* Compute affine transformation from user frame to NDC [normalized
     device coordinates] frame.  The parallelogram in the user frame is
     mapped to the square [0,1]x[0,1] in the NDC frame.  */

  v0x = x0;
  v0y = y0;
  v1x = x1 - x0;
  v1y = y1 - y0;
  v2x = x2 - x0;
  v2y = y2 - y0;
  cross = v1x * v2y - v1y * v2x;

  if (cross == 0.0) 
    {
      _plotter->error (R___(_plotter) 
		       "cannot perform singular affine transformation");
      return -1;
    }

  /* linear transformation */  
  s[0] = v2y / cross;
  s[1] = -v1y / cross;
  s[2] = -v2x / cross;
  s[3] = v1x / cross;

  /* translation */
  s[4] = - (v0x * v2y - v0y * v2x) / cross;
  s[5] = (v0x * v1y - v0y * v1x) / cross;
  
  return _API_fsetmatrix (R___(_plotter) 
			  s[0], s[1], s[2], s[3], s[4], s[5]);
}

int
#ifdef _HAVE_PROTOS
_API_fspace (R___(Plotter *_plotter) double x0, double y0, double x1, double y1)
#else
_API_fspace (R___(_plotter) x0, y0, x1, y1)
     S___(Plotter *_plotter;)
     double x0, y0, x1, y1;
#endif
{
  return _API_fspace2 (R___(_plotter) x0, y0, x1, y0, x0, y1);
}

int
#ifdef _HAVE_PROTOS
_API_fsetmatrix (R___(Plotter *_plotter) double m0, double m1, double m2, double m3, double m4, double m5)
#else
_API_fsetmatrix (R___(_plotter) m0, m1, m2, m3, m4, m5)
     S___(Plotter *_plotter;) 
     double m0, m1, m2, m3, m4, m5;
#endif
{
  int i;
  double s[6], t[6];
  double norm, min_sing_val, max_sing_val;

  if (!_plotter->data->open)
    {
      _plotter->error (R___(_plotter) 
		       "fsetmatrix: invalid operation");
      return -1;
    }

  /* linear transformation */
  s[0] = m0;
  s[1] = m1;
  s[2] = m2;
  s[3] = m3;

  /* translation */
  s[4] = m4;
  s[5] = m5;

  /* store new user_frame->NDC_frame map in drawing state */
  for (i = 0; i < 6; i++)
    _plotter->drawstate->transform.m_user_to_ndc[i] = s[i];

  /* compute the user_frame -> device_frame map, as product of this map
     with the following NDC_frame->device_frame map: store in drawing state */
  _matrix_product (s, _plotter->data->m_ndc_to_device, t);
  for (i = 0; i < 6; i++)
    _plotter->drawstate->transform.m[i] = t[i];

  /* for convenience, precompute boolean properties of the
     user_frame->device_frame map: store in drawing state */

  /* Does the user_frame->device_frame map preserve axis directions?
     (Since our NDC_frame->device frame map always does, this is the same
     as, does the user_frame->NDC_frame map preserve axis directions?) */
  _plotter->drawstate->transform.axes_preserved = 
    (t[1] == 0.0 && t[2] == 0.0) ? true : false;

  /* Is the user_frame->device_frame map a uniform scaling (possibly
     involving a rotation or reflection)?  We need to know this because
     it's only uniform maps that map circles to circles, and circular arcs
     to circular arcs.  Also some Plotters, e.g. Fig Plotters, don't
     support non-uniformly transformed fonts. */

#define IS_ZERO(arg) (IS_ZERO1(arg) && IS_ZERO2(arg))
#define IS_ZERO1(arg) (FABS(arg) < OTHER_FUZZ * DMAX(t[0] * t[0], t[1] * t[1]))
#define IS_ZERO2(arg) (FABS(arg) < OTHER_FUZZ * DMAX(t[2] * t[2], t[3] * t[3]))
  /* if row vectors are of equal length and orthogonal... */
  if (IS_ZERO(t[0] * t[0] + t[1] * t[1] - t[2] * t[2] - t[3] * t[3])
      &&
      IS_ZERO(t[0] * t[2] + t[1] * t[3]))
    /* map's scaling is uniform */
    _plotter->drawstate->transform.uniform = true;
  else
    /* map's scaling not uniform */
    _plotter->drawstate->transform.uniform = false; 

  /* Does the user_frame->physical_frame map involve a reflection?  This is
     useful to know because some Plotters, e.g. Fig Plotters, don't support
     reflected fonts, even if they're uniformly transformed.

     This is a tricky question, because it isn't a question about the
     user_frame->device_frame map alone.  There's a sequence of maps:

     	user_frame -> NDC_frame -> device_frame -> physical_frame

     If the device_frame uses `flipped y' coordinates, then by definition,
     the default NDC_frame->device_frame map and the
     device_frame->physical_frame map both include a reflection, so they
     cancel each other out.

     (Though depending on the Plotter, non-default behavior could obtain.
     For example, the PAGESIZE parameter allows the specification of xsize
     and ysize, and if exactly one of these two is negative, the
     NDC_frame->device_frame map will include an extra reflection.)

     What we do is look at the `sign' or orientation-preservingness of the
     user_frame->device_frame map, and flip it if the
     device_frame->physical_frame map is flagged as `flipped y'. */
  {
    double det;
    
    det = t[0] * t[3] - t[1] * t[2];
    _plotter->drawstate->transform.nonreflection 
      = ((_plotter->data->flipped_y ? -1 : 1) * det >= 0) ? true : false;
  }
  
  /* DO SOME OTHER STUFF, ALL RELATED TO LINE WIDTHS AND FONT SIZES */

  /* For scaling purposes, compute matrix norm of linear transformation
     appearing in the affine map from the user frame to the NDC frame. */

  /* This minimum singular value isn't really the norm.  But it's close
     enough. */
  _matrix_sing_vals (s, &min_sing_val, &max_sing_val);
  norm = min_sing_val;

  /* Set new default line width in user frame.  This default value will be
     switched to, later, if the user calls linewidth() with a negative
     (i.e. out-of-bound) argument. */

  if (_plotter->data->display_coors_type 
      == (int)DISP_DEVICE_COORS_INTEGER_LIBXMI)
    /* using libxmi or a compatible rendering algorithm; so set default
       line width to zero (interpreted as specifying a Bresenham line) */
    _plotter->drawstate->default_line_width = 0.0;
  else
    /* not using libxmi or a compatible rendering algorithm; so set default
       line width to a nonzero fraction of the display size */
    {
      if (norm == 0.0)		/* avoid division by 0 */
	_plotter->drawstate->default_line_width = 0.0;
      else
	_plotter->drawstate->default_line_width 
	  = DEFAULT_LINE_WIDTH_AS_FRACTION_OF_DISPLAY_SIZE / norm;
    }

  if (_plotter->data->linewidth_invoked == false)
  /* help out lusers who rely on us to initialize the linewidth to a
     reasonable value, as if this were plot(3) rather than GNU libplot */
    {
      /* invoke API function flinewidth(), which computes a nominal
	 device-frame line width, using the transformation matrix;
	 specifying a negative linewidth switches to the default */
      _API_flinewidth (R___(_plotter) -1.0);

      /* pretend we haven't invoked flinewidth() yet, so that the luser can
	 invoke space() and/or fsetmatrix() additional times, each time
	 automatically resetting the linewidth */
      _plotter->data->linewidth_invoked = false;
    }
  else
    /* invoke API function merely to compute a new nominal device-frame
       line width, from the current user-frame line width */
    _API_flinewidth (R___(_plotter) _plotter->drawstate->line_width);

  /* Similarly, set new default font size in user frame.  This default
     value will be switched to, later, if the user calls fontsize() with
     out-of-bound arguments. */

  if (norm == 0.0)		/* avoid division by 0 */
    _plotter->drawstate->default_font_size = 0.0;
  else
    _plotter->drawstate->default_font_size
      = DEFAULT_FONT_SIZE_AS_FRACTION_OF_DISPLAY_SIZE / norm;

  /* Help out users who rely on us to choose a reasonable font size, as if
     this were Unix plot(3) rather than GNU libplot.  We don't wish to
     retrieve an actual font here, so we don't invoke _API_fontsize().
     However, this size will be used by the Plotter-specific method
     _paint_text(), which will first do the retrieval. */
  if (_plotter->data->fontsize_invoked == false)
    _plotter->drawstate->font_size = _plotter->drawstate->default_font_size;
  
  return 0;
}

int
#ifdef _HAVE_PROTOS
_API_fconcat (R___(Plotter *_plotter) double m0, double m1, double m2, double m3, double m4, double m5)
#else
_API_fconcat (R___(_plotter) m0, m1, m2, m3, m4, m5)
     S___(Plotter *_plotter;) 
     double m0, m1, m2, m3, m4, m5;
#endif
{
  double m[6], s[6];

  if (!_plotter->data->open)
    {
      _plotter->error (R___(_plotter) 
		       "fconcat: invalid operation");
      return -1;
    }

  m[0] = m0;
  m[1] = m1;
  m[2] = m2;
  m[3] = m3;
  m[4] = m4;
  m[5] = m5;

  /* compute new user->NDC affine map */
  _matrix_product (m, _plotter->drawstate->transform.m_user_to_ndc, s);

  /* set it in drawing state */
  return _API_fsetmatrix (R___(_plotter) 
			  s[0], s[1], s[2], s[3], s[4], s[5]);
}

/* Compute the affine transformation from the NDC frame to the device
   frame.  This is an internal method, called by any Plotter at
   initialization time, or at latest during the first invocation of
   openpl().

   The square [0,1]x[0,1] in the NDC frame is mapped to the viewport in the
   device frame (a square or rectangular region).  So, the
   NDC_frame->device_frame map preserves coordinate axes.  (Though either
   the x or y axis may be flipped, the latter being more common, because
   some devices' native coordinate system has a flipped-y convention, which
   means that the final device_frame->physical_frame map flips in the y
   direction.)

   There is support for the ROTATION Plotter parameter, which allows the
   NDC frame to be rotated by 90, 180, or 270 degrees, before it is mapped
   to the device frame. */

bool
#ifdef _HAVE_PROTOS
_compute_ndc_to_device_map (plPlotterData *data)
#else
_compute_ndc_to_device_map (data)
     plPlotterData *data;
#endif
{
  double t[6];
  double device_x_left, device_x_right, device_y_bottom, device_y_top;
  const char *rotation_s;
  int i, rotation_angle;

  /* begin by computing device coordinate ranges */
  switch (data->display_model_type)
    {
    case (int)DISP_MODEL_PHYSICAL:
      /* Plotter has a physical display, ranges in device coordinates of
	 the viewport are known (they're expressed in inches, and are
	 computed from the PAGESIZE parameter when the Plotter is created,
	 see ?_defplot.c).  E.g., AI, Fig, HPGL, PCL, and PS Plotters. */
      {
	device_x_left = data->xmin;
	device_x_right = data->xmax;
	device_y_bottom = data->ymin;
	device_y_top = data->ymax;
      }
      break;

    case (int)DISP_MODEL_VIRTUAL:
    default:
      /* Plotter has a display, but its size isn't specified in physical
         units such as inches.  E.g., CGM, SVG, GIF, PNM, Tektronix, X, and
         X Drawable Plotters.  CGM and SVG Plotters are hybrids of a sort:
         the PAGESIZE parameter is meaningful for them, as far as nominal
         viewport size goes, but we treat a CGM or SVG display as `virtual'
         because a CGM or SVG viewer or interpreter is free to ignore the
         requested viewport size.  */
      {
	switch ((int)data->display_coors_type)
	  {
	  case (int)DISP_DEVICE_COORS_REAL:
	  default:
	    /* Real-coordinate virtual display device.  E.g., generic and
	       Metafile Plotters; also SVG Plotters. */
	    device_x_left = data->xmin;
	    device_x_right = data->xmax;
	    device_y_bottom = data->ymin;
	    device_y_top = data->ymax;
	    break;
	  case (int)DISP_DEVICE_COORS_INTEGER_LIBXMI:
	  case (int)DISP_DEVICE_COORS_INTEGER_NON_LIBXMI:
	    /* Integer-coordinate virtual display device, in the sense that
	       we emit integer coordinates only (sometimes by choice).
	       
	       Of the Plotters that have virtual displays (see above), GIF,
	       PNM, X, and X Drawable Plotters use libxmi-compatible scan
	       conversion; Tektronix Plotters and CGM Plotters do not.
	       
	       In both cases, compute device coordinate ranges from imin,
	       imax, jmin, jmax, which are already available (see
	       ?_defplot.c; e.g., for Plotters with adjustable-size
	       displays, they are taken from the BITMAPSIZE parameter).

	       The subtraction/addition of 0.5-ROUNDING_FUZZ, which widens
	       the rectangle by nearly 0.5 pixel on each side, is magic. */
	    {
	      /* test whether NCD_frame->device_frame map reflects in the x
                 and/or y direction */
	      double x_sign = (data->imin < data->imax ? 1.0 : -1.0);
	      double y_sign = (data->jmin < data->jmax ? 1.0 : -1.0);
	      
	      device_x_left = ((double)(data->imin) 
			      + x_sign * (- 0.5 + ROUNDING_FUZZ));
	      device_x_right = ((double)(data->imax) 
			      + x_sign * (0.5 - ROUNDING_FUZZ));
	      device_y_bottom = ((double)(data->jmin)
			      + y_sign * (- 0.5 + ROUNDING_FUZZ));
	      device_y_top = ((double)(data->jmax) 
			      + y_sign * (0.5 - ROUNDING_FUZZ));
	    }
	    break;
	  }
      }
      break;
    }

  /* device coordinate ranges now known, so work out transformation from
     NDC frame to device frame; take ROTATION parameter into account */

  rotation_s = (const char *)_get_plot_param (data, "ROTATION");
  if (rotation_s == NULL)
    rotation_s = (const char *)_get_default_plot_param ("ROTATION");
  if (strcmp (rotation_s, "90") == 0
      || strcmp (rotation_s, "yes") == 0) /* "yes" means "90" */
    rotation_angle = (int)ROT_90;
  else if (strcmp (rotation_s, "180") == 0)
    rotation_angle = (int)ROT_180;
  else if (strcmp (rotation_s, "270") == 0)
    rotation_angle = (int)ROT_270;
  else rotation_angle = (int)ROT_0; /* default, includes "no" */

  switch (rotation_angle)
    {
    case (int)ROT_0:
    default:
      /* NDC point (0,0) [lower left corner] gets mapped into this */
      t[4] = device_x_left;
      t[5] = device_y_bottom;
      /* NDC vector (1,0) gets mapped into this */
      t[0] = device_x_right - device_x_left;
      t[1] = 0.0;
      /* NDC vector (0,1) gets mapped into this */
      t[2] = 0.0;
      t[3] = device_y_top - device_y_bottom;
      break;
    case (int)ROT_90:
      /* NDC point (0,0) [lower left corner] gets mapped into this */
      t[4] = device_x_right;
      t[5] = device_y_bottom;
      /* NDC vector (1,0) gets mapped into this */
      t[0] = 0.0;
      t[1] = device_y_top - device_y_bottom;
      /* NDC vector (0,1) gets mapped into this */
      t[2] = device_x_left - device_x_right;
      t[3] = 0.0;
      break;
    case (int)ROT_180:
      /* NDC point (0,0) [lower left corner] gets mapped into this */
      t[4] = device_x_right;
      t[5] = device_y_top;
      /* NDC vector (1,0) gets mapped into this */
      t[0] = device_x_left - device_x_right;
      t[1] = 0.0;
      /* NDC vector (0,1) gets mapped into this */
      t[2] = 0.0;
      t[3] = device_y_bottom - device_y_top;
      break;
    case (int)ROT_270:
      /* NDC point (0,0) [lower left corner] gets mapped into this */
      t[4] = device_x_left;
      t[5] = device_y_top;
      /* NDC vector (1,0) gets mapped into this */
      t[0] = 0.0;
      t[1] = device_y_bottom - device_y_top;
      /* NDC vector (0,1) gets mapped into this */
      t[2] = device_x_right - device_x_left;
      t[3] = 0.0;
      break;
    }
  
  /* set affine transformation in Plotter */
  for (i = 0; i < 6; i++)
    data->m_ndc_to_device[i] = t[i];

  return true;
}