1
/* $XConsortium: spaces.c,v 1.4 91/10/10 11:19:16 rws Exp $ */
2
/* Copyright International Business Machines, Corp. 1991
4
* Copyright Lexmark International, Inc. 1991
7
* License to use, copy, modify, and distribute this software and its
8
* documentation for any purpose and without fee is hereby granted,
9
* provided that the above copyright notice appear in all copies and that
10
* both that copyright notice and this permission notice appear in
11
* supporting documentation, and that the name of IBM or Lexmark not be
12
* used in advertising or publicity pertaining to distribution of the
13
* software without specific, written prior permission.
15
* IBM AND LEXMARK PROVIDE THIS SOFTWARE "AS IS", WITHOUT ANY WARRANTIES OF
16
* ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY
17
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
18
* AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. THE ENTIRE RISK AS TO THE
19
* QUALITY AND PERFORMANCE OF THE SOFTWARE, INCLUDING ANY DUTY TO SUPPORT
20
* OR MAINTAIN, BELONGS TO THE LICENSEE. SHOULD ANY PORTION OF THE
21
* SOFTWARE PROVE DEFECTIVE, THE LICENSEE (NOT IBM OR LEXMARK) ASSUMES THE
22
* ENTIRE COST OF ALL SERVICING, REPAIR AND CORRECTION. IN NO EVENT SHALL
23
* IBM OR LEXMARK BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
24
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
25
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
26
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
29
/* SPACES CWEB V0021 ******** */
31
:h1 id=spaces.SPACES Module - Handles Coordinate Spaces
33
This module is responsible for handling the TYPE1IMAGER "XYspace" object.
35
&author. Jeffrey B. Lotspiech (lotspiech@almaden.ibm.com)
54
static int FindFfcn();
55
static int FindIfcn();
57
:h3.Entry Points Provided to the TYPE1IMAGER User
60
/*SHARED LINE(S) ORIGINATED HERE*/
63
:h3.Entry Points Provided to Other Modules
67
In addition, other modules call the SPACES module through function
68
vectors in the "XYspace" structure. The entry points accessed that
69
way are "FConvert()", "IConvert()", and "ForceFloat()".
72
/*SHARED LINE(S) ORIGINATED HERE*/
74
:h3.Macros and Typedefs Provided to Other Modules
76
:h4.Duplicating and Killing Spaces
78
Destroying XYspaces is so simple we can do it with a
82
/*SHARED LINE(S) ORIGINATED HERE*/
84
On the other hand, duplicating XYspaces is slightly more difficult
85
because of the need to keep a unique ID in the space, see
86
:hdref refid=dupspace..
88
:h4.Fixed Point Pel Representation
90
We represent pel positions with fixed point numbers. This does NOT
91
mean integer, but truly means fixed point, with a certain number
92
of binary digits (FRACTBITS) representing the fractional part of the
96
/*SHARED LINE(S) ORIGINATED HERE*/
98
:h2.Data Structures for Coordinate Spaces and Points
101
:h3 id=matrix.Matrices
103
TYPE1IMAGER uses 2x2 transformation matrices. We'll use C notation for
104
such a matrix (M[2][2]), the first index being rows, the second columns.
108
:h3.The "doublematrix" Structure
110
We frequently find it desirable to store both a matrix and its
111
inverse. We store these in a "doublematrix" structure.
114
/*SHARED LINE(S) ORIGINATED HERE*/
117
:h3.The "XYspace" Structure
119
The XYspace structure represents the XYspace object.
122
/*SHARED LINE(S) ORIGINATED HERE*/
123
#define RESERVED 10 /* 'n' IDs are reserved for invalid & immortal spaces */
126
#define NEXTID ((SpaceID < RESERVED) ? (SpaceID = RESERVED) : ++SpaceID)
128
static unsigned int SpaceID = 1;
130
struct XYspace *CopySpace(S)
131
register struct XYspace *S;
133
S = (struct XYspace *)Allocate(sizeof(struct XYspace), S, 0);
138
:h3.The "fractpoint" Structure
140
A fractional point is just a "fractpel" x and y:
143
/*SHARED LINE(S) ORIGINATED HERE*/
146
:h3.Lazy Evaluation of Matrix Inverses
148
Calculating the inverse of a matrix is somewhat involved, and we usually
149
do not need them. So, we flag whether or not the space has the inverse
153
#define HASINVERSE(flag) ((flag)&0x80)
156
The following macro forces a space to have an inverse:
159
#define CoerceInverse(S) if (!HASINVERSE((S)->flag)) { \
160
MatrixInvert((S)->tofract.normal, (S)->tofract.inverse); (S)->flag |= HASINVERSE(ON); }
164
IDENTITY space is (logically) the space corresponding to the identity
165
transformation matrix. However, since all our transformation matrices
166
have a common FRACTFLOAT scale factor to convert to 'fractpel's, that
167
is actually what we store in 'tofract' matrix of IDENTITY:
170
static struct XYspace identity = { SPACETYPE, ISPERMANENT(ON) + ISIMMORTAL(ON)
171
+ HASINVERSE(ON), 2, /* added 3-26-91 PNM */
173
NULL, NULL, NULL, NULL,
175
{{{FRACTFLOAT, 0.0}, {0.0, FRACTFLOAT}},
176
{{1.0/FRACTFLOAT, 0.0}, {0.0, 1.0/FRACTFLOAT}}},
178
struct XYspace *IDENTITY = &identity;
182
#define MAXCONTEXTS 16
184
static struct doublematrix contexts[MAXCONTEXTS];
188
static int nextcontext = 1;
190
/*SHARED LINE(S) ORIGINATED HERE*/
193
#define pointer void *
195
#define pointer char *
199
:h3.FindDeviceContext() - Find the Context Given a Device
201
This routine, given a device, returns the index of the device's
202
transformation matrix in the context array. If it cannot find it,
203
it will allocate a new array entry and fill it out.
206
static int FindDeviceContext(device)
207
pointer device; /* device token */
209
DOUBLE M[2][2]; /* temporary matrix */
210
float Xres,Yres; /* device resolution */
211
int orient = -1; /* device orientation */
212
int rc = -1; /* return code for QueryDeviceState */
214
if (rc != 0) /* we only bother with this check once */
215
abort("Context: QueryDeviceState didn't work", 44);
217
M[0][0] = M[1][0] = M[0][1] = M[1][1] = 0.0;
221
M[0][0] = Xres; M[1][1] = -Yres;
224
M[1][0] = Yres; M[0][1] = Xres;
227
M[0][0] = -Xres; M[1][1] = Yres;
230
M[1][0] = -Yres; M[0][1] = -Xres;
233
abort("QueryDeviceState returned invalid orientation", 45);
235
return(FindContext(M));
239
:h3.FindContext() - Find the Context Given a Matrix
241
This routine, given a matrix, returns the index of that matrix matrix in
242
the context array. If it cannot find it, it will allocate a new array
243
entry and fill it out.
247
DOUBLE M[2][2]; /* array to search for */
249
register int i; /* loop variable for search */
250
for (i=0; i < nextcontext; i++)
251
if (M[0][0] == contexts[i].normal[0][0] && M[1][0] == contexts[i].normal[1][0]
252
&& M[0][1] == contexts[i].normal[0][1] && M[1][1] == contexts[i].normal[1][1])
255
if (i >= nextcontext) {
256
if (i >= MAXCONTEXTS)
257
abort("Context: out of them", 46);
258
LONGCOPY(contexts[i].normal, M, sizeof(contexts[i].normal));
259
MatrixInvert(M, contexts[i].inverse);
267
:h3.Context() - Create a Coordinate Space for a Device
269
This user operator is implemented by first finding the device context
270
array index, then transforming IDENTITY space to create an appropriate
274
struct XYspace *Context(device, units)
275
pointer device; /* device token */
276
DOUBLE units; /* multiples of one inch */
278
DOUBLE M[2][2]; /* device transformation matrix */
279
register int n; /* will hold device context number */
280
register struct XYspace *S; /* XYspace constructed */
282
IfTrace2((MustTraceCalls),"Context(%x, %f)\n", device, &units);
284
ARGCHECK((device == NULL), "Context of NULLDEVICE not allowed",
285
NULL, IDENTITY, (0), struct XYspace *);
286
ARGCHECK((units == 0.0), "Context: bad units", NULL, IDENTITY, (0), struct XYspace *);
288
n = FindDeviceContext(device);
290
LONGCOPY(M, contexts[n].normal, sizeof(M));
297
S = (struct XYspace *)Xform(IDENTITY, M);
305
:h3.ConsiderContext() - Adjust a Matrix to Take Out Device Transform
307
Remember, we have :f/x times U times D/ and :f/M/ and and we want :f/x
308
times U times M times D/. An easy way to do this is to calculate
309
:f/D sup <-1> times M times D/, because:
311
x times U times D times D sup <-1> times M times D = x times U times M times D
313
So this subroutine, given an :f/M/and an object, finds the :f/D/ for that
314
object and modifies :f/M/ so it is :f/D sup <-1> times M times D/.
317
static void ConsiderContext(obj, M)
318
register struct xobject *obj; /* object to be transformed */
319
register DOUBLE M[2][2]; /* matrix (may be changed) */
321
register int context=0; /* index in contexts array */
323
if (obj == NULL) return;
325
if (ISPATHTYPE(obj->type)) {
326
struct segment *path = (struct segment *) obj;
328
context = path->context;
330
else if (obj->type == SPACETYPE) {
331
struct XYspace *S = (struct XYspace *) obj;
333
context = S->context;
335
else if (obj->type == PICTURETYPE) {
339
context = NULLCONTEXT;
341
if (context != NULLCONTEXT) {
342
MatrixMultiply(contexts[context].inverse, M, M);
343
MatrixMultiply(M, contexts[context].normal, M);
348
:h2.Conversion from User's X,Y to "fractpel" X,Y
350
When the user is building paths (lines, moves, curves, etc.) he passes
351
the control points (x,y) for the paths together with an XYspace. We
352
must convert from the user's (x,y) to our internal representation
353
which is in pels (fractpels, actually). This involves transforming
354
the user's (x,y) under the coordinate space transformation. It is
355
important that we do this quickly. So, we store pointers to different
356
conversion functions right in the XYspace structure. This allows us
357
to have simpler special case functions for the more commonly
358
encountered types of transformations.
360
:h3.Convert(), IConvert(), and ForceFloat() - Called Through "XYspace" Structure
362
These are functions that fit in the "convert" and "iconvert" function
363
pointers in the XYspace structure. They call the "xconvert", "yconvert",
364
"ixconvert", and "iyconvert" as appropriate to actually do the work.
365
These secondary routines come in many flavors to handle different
366
special cases as quickly as possible.
369
int FXYConvert(pt, S, x, y)
370
register struct fractpoint *pt; /* point to set */
371
register struct XYspace *S; /* relevant coordinate space */
372
register DOUBLE x,y; /* user's coordinates of point */
374
pt->x = (*S->xconvert)(S->tofract.normal[0][0], S->tofract.normal[1][0], x, y);
375
pt->y = (*S->yconvert)(S->tofract.normal[0][1], S->tofract.normal[1][1], x, y);
380
int IXYConvert(pt, S, x, y)
381
register struct fractpoint *pt; /* point to set */
382
register struct XYspace *S; /* relevant coordinate space */
383
register LONG x,y; /* user's coordinates of point */
385
pt->x = (*S->ixconvert)(S->itofract[0][0], S->itofract[1][0], x, y);
386
pt->y = (*S->iyconvert)(S->itofract[0][1], S->itofract[1][1], x, y);
392
ForceFloat is a substitute for IConvert(), when we just do not have
393
enough significant digits in the coefficients to get high enough
394
precision in the answer with fixed point arithmetic. So, we force the
395
integers to floats, and do the arithmetic all with floats:
398
int ForceFloat(pt, S, x, y)
399
register struct fractpoint *pt; /* point to set */
400
register struct XYspace *S; /* relevant coordinate space */
401
register LONG x,y; /* user's coordinates of point */
403
(*S->convert)(pt, S, (DOUBLE) x, (DOUBLE) y);
409
:h3.FXYboth(), FXonly(), FYonly() - Floating Point Conversion
411
These are the routines we use when the user has given us floating
412
point numbers for x and y. FXYboth() is the general purpose routine;
413
FXonly() and FYonly() are special cases when one of the coefficients
417
fractpel FXYboth(cx, cy, x, y)
418
register DOUBLE cx,cy; /* x and y coefficients */
419
register DOUBLE x,y; /* user x,y */
421
register DOUBLE r; /* temporary float */
424
return((fractpel) r);
428
fractpel FXonly(cx, cy, x, y)
429
register DOUBLE cx,cy; /* x and y coefficients */
430
register DOUBLE x,y; /* user x,y */
432
register DOUBLE r; /* temporary float */
435
return((fractpel) r);
439
fractpel FYonly(cx, cy, x, y)
440
register DOUBLE cx,cy; /* x and y coefficients */
441
register DOUBLE x,y; /* user x,y */
443
register DOUBLE r; /* temporary float */
446
return((fractpel) r);
450
:h3.IXYboth(), IXonly(), IYonly() - Simple Integer Conversion
452
These are the routines we use when the user has given us integers for
453
x and y, and the coefficients have enough significant digits to
454
provide precise answers with only "long" (32 bit?) multiplication.
455
IXYboth() is the general purpose routine; IXonly() and IYonly() are
456
special cases when one of the coefficients is 0.
459
fractpel IXYboth(cx, cy, x, y)
460
register fractpel cx,cy; /* x and y coefficients */
461
register LONG x,y; /* user x,y */
463
return(x * cx + y * cy);
467
fractpel IXonly(cx, cy, x, y)
468
register fractpel cx,cy; /* x and y coefficients */
469
register LONG x,y; /* user x,y */
475
fractpel IYonly(cx, cy, x, y)
476
register fractpel cx,cy; /* x and y coefficients */
477
register LONG x,y; /* user x,y */
484
:h3.FPXYboth(), FPXonly(), FPYonly() - More Involved Integer Conversion
486
These are the routines we use when the user has given us integers for
487
x and y, but the coefficients do not have enough significant digits to
488
provide precise answers with only "long" (32 bit?) multiplication.
489
We have increased the number of significant bits in the coefficients
490
by FRACTBITS; therefore we must use "double long" (64 bit?)
491
multiplication by calling FPmult(). FPXYboth() is the general purpose
492
routine; FPXonly() and FPYonly() are special cases when one of the
495
Note that it is perfectly possible for us to calculate X with the
496
"FP" method and Y with the "I" method, or vice versa. It all depends
497
on how the functions in the XYspace structure are filled out.
500
fractpel FPXYboth(cx, cy, x, y)
501
register fractpel cx,cy; /* x and y coefficients */
502
register LONG x,y; /* user x,y */
504
return( FPmult(x, cx) + FPmult(y, cy) );
508
fractpel FPXonly(cx, cy, x, y)
509
register fractpel cx,cy; /* x and y coefficients */
510
register LONG x,y; /* user x,y */
512
return( FPmult(x, cx) );
516
fractpel FPYonly(cx, cy, x, y)
517
register fractpel cx,cy; /* x and y coefficients */
518
register LONG x,y; /* user x,y */
520
return( FPmult(y, cy) );
526
:h3.FillOutFcns() - Determine the Appropriate Functions to Use for Conversion
528
This function fills out the "convert" and "iconvert" function pointers
529
in an XYspace structure, and also fills the "helper"
530
functions that actually do the work.
533
static void FillOutFcns(S)
534
register struct XYspace *S; /* functions will be set in this structure */
536
S->convert = FXYConvert;
537
S->iconvert = IXYConvert;
539
FindFfcn(S->tofract.normal[0][0], S->tofract.normal[1][0], &S->xconvert);
540
FindFfcn(S->tofract.normal[0][1], S->tofract.normal[1][1], &S->yconvert);
541
FindIfcn(S->tofract.normal[0][0], S->tofract.normal[1][0],
542
&S->itofract[0][0], &S->itofract[1][0], &S->ixconvert);
543
FindIfcn(S->tofract.normal[0][1], S->tofract.normal[1][1],
544
&S->itofract[0][1], &S->itofract[1][1], &S->iyconvert);
546
if (S->ixconvert == NULL || S->iyconvert == NULL)
547
S->iconvert = ForceFloat;
551
:h4.FindFfcn() - Subroutine of FillOutFcns() to Fill Out Floating Functions
553
This function tests for the special case of one of the coefficients
557
static int FindFfcn(cx, cy, fcnP)
558
register DOUBLE cx,cy; /* x and y coefficients */
559
register fractpel (**fcnP)(); /* pointer to function to set */
572
:h4.FindIfcn() - Subroutine of FillOutFcns() to Fill Out Integer Functions
574
There are two types of integer functions, the 'I' type and the 'FP' type.
575
We use the I type functions when we are satisfied with simple integer
576
arithmetic. We used the FP functions when we feel we need higher
577
precision (but still fixed point) arithmetic. If all else fails,
578
we store a NULL indicating that this we should do the conversion in
582
static int FindIfcn(cx, cy, icxP, icyP, fcnP)
583
register DOUBLE cx,cy; /* x and y coefficients */
584
register fractpel *icxP,*icyP; /* fixed point coefficients to set */
585
register fractpel (**fcnP)(); /* pointer to function to set */
587
register fractpel imax; /* maximum of cx and cy */
592
if (cx != (float) (*icxP) || cy != (float) (*icyP)) {
594
At this point we know our integer approximations of the coefficients
595
are not exact. However, we will still use them if the maximum
596
coefficient will not fit in a 'fractpel'. Of course, we have little
597
choice at that point, but we haven't lost that much precision by
598
staying with integer arithmetic. We have enough significant digits
600
any error we introduce is less than one part in 2:sup/16/.
603
imax = TYPE1_MAX(TYPE1_ABS(*icxP), TYPE1_ABS(*icyP));
604
if (imax < (fractpel) (1<<(FRACTBITS-1)) ) {
606
At this point we know our integer approximations just do not have
607
enough significant digits for accuracy. We will add FRACTBITS
608
significant digits to the coefficients (by multiplying them by
609
1<<FRACTBITS) and go to the "FP" form of the functions. First, we
610
check to see if we have ANY significant digits at all (that is, if
611
imax == 0). If we don't, we suspect that adding FRACTBITS digits
612
won't help, so we punt the whole thing.
630
Now we check for special cases where one coefficient is zero (after
634
*fcnP = (*fcnP == FPXYboth) ? FPYonly : IYonly;
636
*fcnP = (*fcnP == FPXYboth) ? FPXonly : IXonly;
641
:h3.UnConvert() - Find User Coordinates From FractPoints
643
The interesting thing with this routine is that we avoid calculating
644
the matrix inverse of the device transformation until we really need
645
it, which is to say, until this routine is called for the first time
646
with a given coordinate space.
648
We also only calculate it only once. If the inverted matrix is valid,
649
we don't calculate it; if not, we do. We never expect matrices with
650
zero determinants, so by convention, we mark the matrix is invalid by
651
marking both X terms zero.
654
void UnConvert(S, pt, xp, yp)
655
register struct XYspace *S; /* relevant coordinate space */
656
register struct fractpoint *pt; /* device coordinates */
657
DOUBLE *xp,*yp; /* where to store resulting x,y */
664
*xp = S->tofract.inverse[0][0] * x + S->tofract.inverse[1][0] * y;
665
*yp = S->tofract.inverse[0][1] * x + S->tofract.inverse[1][1] * y;
672
:h3 id=xform.Xform() - Transform Object in X and Y
674
TYPE1IMAGER wants transformations of objects like paths to be identical
675
to transformations of spaces. For example, if you scale a line(1,1)
676
by 10 it should yield the same result as generating the line(1,1) in
677
a coordinate space that has been scaled by 10.
679
We handle fonts by storing the accumulated transform, for example, SR
680
(accumulating on the right). Then when we map the font through space TD,
681
for example, we multiply the accumulated font transform on the left by
682
the space transform on the right, yielding SRTD in this case. We will
683
get the same result if we did S, then R, then T on the space and mapping
684
an unmodified font through that space.
687
struct xobject *t1_Xform(obj, M)
688
register struct xobject *obj; /* object to transform */
689
register DOUBLE M[2][2]; /* transformation matrix */
694
if (obj->type == FONTTYPE) {
695
register struct font *F = (struct font *) obj;
698
return((struct xobject*)F);
700
if (obj->type == PICTURETYPE) {
702
In the case of a picture, we choose both to update the picture's
703
transformation matrix and keep the handles up to date.
705
register struct picture *P = (struct picture *) obj;
706
register struct segment *handles; /* temporary path to transform handles */
708
P = UniquePicture(P);
709
handles = PathSegment(LINETYPE, P->origin.x, P->origin.y);
710
handles = Join(handles,
711
PathSegment(LINETYPE, P->ending.x, P->ending.y) );
712
handles = (struct segment *)Xform((struct xobject *) handles, M);
713
P->origin = handles->dest;
714
P->ending = handles->link->dest;
716
return((struct xobject *)P);
719
if (ISPATHTYPE(obj->type)) {
720
struct XYspace pseudo; /* local temporary space */
721
PseudoSpace(&pseudo, M);
722
return((struct xobject *) PathTransform(obj, &pseudo));
726
if (obj->type == SPACETYPE) {
727
register struct XYspace *S = (struct XYspace *) obj;
729
/* replaced ISPERMANENT(S->flag) with S->references > 1 3-26-91 PNM */
730
if (S->references > 1)
735
MatrixMultiply(S->tofract.normal, M, S->tofract.normal);
737
* mark inverted matrix invalid:
739
S->flag &= ~HASINVERSE(ON);
742
return((struct xobject *) S);
745
return(ArgErr("Untransformable object", obj, obj));
749
:h3.Transform() - Transform an Object
751
This is the external user's entry point.
753
struct xobject *t1_Transform(obj, cxx, cyx, cxy, cyy)
755
DOUBLE cxx,cyx,cxy,cyy; /* 2x2 transform matrix elements in row order */
759
IfTrace1((MustTraceCalls),"Transform(%p,", obj);
760
IfTrace4((MustTraceCalls)," %f %f %f %f)\n", cxx, cyx, cxy, cyy);
766
ConsiderContext(obj, M);
767
return(Xform(obj, M));
770
:h3.Scale() - Special Case of Transform()
772
This is a user operator.
775
struct xobject *t1_Scale(obj, sx, sy)
776
struct xobject *obj; /* object to scale */
777
DOUBLE sx,sy; /* scale factors in x and y */
780
IfTrace3((MustTraceCalls),"Scale(%p, %f, %f)\n", obj, sx, sy);
783
M[1][0] = M[0][1] = 0.0;
784
ConsiderContext(obj, M);
785
return(Xform(obj, M));
789
:h3 id=rotate.Rotate() - Special Case of Transform()
791
We special-case different settings of 'degrees' for performance
792
and accuracy within the DegreeSin() and DegreeCos() routines themselves.
796
struct xobject *xiRotate(obj, degrees)
797
struct xobject *obj; /* object to be transformed */
798
DOUBLE degrees; /* degrees of COUNTER-clockwise rotation */
803
IfTrace2((MustTraceCalls),"Rotate(%z, %f)\n", obj, °rees);
805
M[0][0] = M[1][1] = DegreeCos(degrees);
806
M[1][0] = - (M[0][1] = DegreeSin(degrees));
807
ConsiderContext(obj, M);
808
return(Xform(obj, M));
813
:h3.PseudoSpace() - Build a Coordinate Space from a Matrix
815
Since we have built all this optimized code that, given an (x,y) and
816
a coordinate space, yield transformed (x,y), it seems a shame not to
817
use the same logic when we need to multiply an (x,y) by an arbitrary
818
matrix that is not (initially) part of a coordinate space. This
819
subroutine takes the arbitrary matrix and builds a coordinate
820
space, with all its nifty function pointers.
823
void PseudoSpace(S, M)
824
struct XYspace *S; /* coordinate space structure to fill out */
825
DOUBLE M[2][2]; /* matrix that will become 'tofract.normal' */
828
S->flag = ISPERMANENT(ON) + ISIMMORTAL(ON);
829
S->references = 2; /* 3-26-91 added PNM */
830
S->tofract.normal[0][0] = M[0][0];
831
S->tofract.normal[1][0] = M[1][0];
832
S->tofract.normal[0][1] = M[0][1];
833
S->tofract.normal[1][1] = M[1][1];
839
:h2 id=matrixa.Matrix Arithmetic
841
Following the convention in Newman and Sproull, :hp1/Interactive
843
matrices are organized:
848
A point is horizontal, for example:
853
:formula/x prime = cxx times x + cxy times y/
854
:formula/y prime = cyx times x + cyy times y/
855
I've seen the other convention, where transform matrices are
856
transposed, equally often in the literature.
860
:h3.MatrixMultiply() - Implements Multiplication of Two Matrices
862
Implements matrix multiplication, A * B = C.
864
To remind myself, matrix multiplication goes rows of A times columns
866
The output matrix may be the same as one of the input matrices.
868
void MatrixMultiply(A, B, C)
869
register DOUBLE A[2][2],B[2][2]; /* input matrices */
870
register DOUBLE C[2][2]; /* output matrix */
872
register DOUBLE txx,txy,tyx,tyy;
874
txx = A[0][0] * B[0][0] + A[0][1] * B[1][0];
875
txy = A[1][0] * B[0][0] + A[1][1] * B[1][0];
876
tyx = A[0][0] * B[0][1] + A[0][1] * B[1][1];
877
tyy = A[1][0] * B[0][1] + A[1][1] * B[1][1];
885
:h3.MatrixInvert() - Invert a Matrix
887
My reference for matrix inversion was :hp1/Elementary Linear Algebra/
888
by Paul C. Shields, Worth Publishers, Inc., 1968.
890
void MatrixInvert(M, Mprime)
891
DOUBLE M[2][2]; /* input matrix */
892
DOUBLE Mprime[2][2]; /* output inverted matrix */
894
register DOUBLE D; /* determinant of matrix M */
895
register DOUBLE txx,txy,tyx,tyy;
902
D = M[1][1] * M[0][0] - M[1][0] * M[0][1];
904
abort("MatrixInvert: can't", 47);
906
Mprime[0][0] = tyy / D;
907
Mprime[1][0] = -txy / D;
908
Mprime[0][1] = -tyx / D;
909
Mprime[1][1] = txx / D;
912
:h2.Initialization, Queries, and Debug
915
:h3.InitSpaces() - Initialize Constant Spaces
917
For compatibility, we initialize a coordinate space called USER which
918
maps 72nds of an inch to pels on the default device.
921
struct XYspace *USER = &identity;
925
/* extern char *DEFAULTDEVICE; */
927
IDENTITY->type = SPACETYPE;
928
FillOutFcns(IDENTITY);
930
contexts[NULLCONTEXT].normal[1][0]
931
= contexts[NULLCONTEXT].normal[0][1]
932
= contexts[NULLCONTEXT].inverse[1][0]
933
= contexts[NULLCONTEXT].inverse[0][1] = 0.0;
934
contexts[NULLCONTEXT].normal[0][0]
935
= contexts[NULLCONTEXT].normal[1][1]
936
= contexts[NULLCONTEXT].inverse[0][0]
937
= contexts[NULLCONTEXT].inverse[1][1] = 1.0;
939
USER->flag |= ISIMMORTAL(ON);
943
:h3.QuerySpace() - Returns the Transformation Matrix of a Space
945
Since the tofract matrix of an XYspace includes the scale factor
946
necessary to produce fractpel results (i.e., FRACTFLOAT), this
947
must be taken out before we return the matrix to the user. Fortunately,
948
this is simple: just multiply by the inverse of IDENTITY!
951
void QuerySpace(S, cxxP, cyxP, cxyP, cyyP)
952
register struct XYspace *S; /* space asked about */
953
register DOUBLE *cxxP,*cyxP,*cxyP,*cyyP; /* where to put answer */
955
DOUBLE M[2][2]; /* temp matrix to build user's answer */
957
if (S->type != SPACETYPE) {
958
ArgErr("QuerySpace: not a space", S, NULL);
961
MatrixMultiply(S->tofract.normal, IDENTITY->tofract.inverse, M);
969
:h3.FormatFP() - Format a Fixed Point Pel
971
We format the pel as "dddd.XXXX", where XX's are hexidecimal digits,
972
and the dd's are decimal digits. This might be a little confusing
973
mixing hexidecimal and decimal like that, but it is convenient
976
We make sure we have N (FRACTBITS/4) digits past the decimal point.
978
#define FRACTMASK ((1<<FRACTBITS)-1) /* mask for fractional part */
980
void FormatFP(string, fpel)
981
register char *string; /* output string */
982
register fractpel fpel; /* fractional pel input */
995
sprintf(temp, "000%x", fpel & FRACTMASK);
996
s = temp + strlen(temp) - (FRACTBITS/4);
998
sprintf(string, "%s%d.%sx", sign, fpel >> FRACTBITS, s);
1002
:h3.DumpSpace() - Display a Coordinate Space
1006
register struct XYspace *S;
1008
IfTrace4(TRUE,"--Coordinate space at %p,ID=%d,convert=%p,iconvert=%p\n",
1009
S, S->ID, S->convert, S->iconvert);
1010
IfTrace2(TRUE," | %12.3f %12.3f |",
1011
S->tofract.normal[0][0], S->tofract.normal[0][1]);
1012
IfTrace2(TRUE," [ %d %d ]\n", S->itofract[0][0], S->itofract[0][1]);
1013
IfTrace2(TRUE," | %12.3f %12.3f |",
1014
S->tofract.normal[1][0], S->tofract.normal[1][1]);
1015
IfTrace2(TRUE," [ %d %d ]\n", S->itofract[1][0], S->itofract[1][1]);