1 /* $XConsortium: spaces.c,v 1.8 95/06/08 23:20:39 gildea 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)
48 static void FindFfcn();
49 static void FindIfcn();
51 :h3.Entry Points Provided to the TYPE1IMAGER User
54 /*SHARED LINE(S) ORIGINATED HERE*/
57 :h3.Entry Points Provided to Other Modules
61 In addition, other modules call the SPACES module through function
62 vectors in the "XYspace" structure. The entry points accessed that
63 way are "FConvert()", "IConvert()", and "ForceFloat()".
66 /*SHARED LINE(S) ORIGINATED HERE*/
68 :h3.Macros and Typedefs Provided to Other Modules
70 :h4.Duplicating and Killing Spaces
72 Destroying XYspaces is so simple we can do it with a
76 /*SHARED LINE(S) ORIGINATED HERE*/
78 On the other hand, duplicating XYspaces is slightly more difficult
79 because of the need to keep a unique ID in the space, see
80 :hdref refid=dupspace..
82 :h4.Fixed Point Pel Representation
84 We represent pel positions with fixed point numbers. This does NOT
85 mean integer, but truly means fixed point, with a certain number
86 of binary digits (FRACTBITS) representing the fractional part of the
90 /*SHARED LINE(S) ORIGINATED HERE*/
92 :h2.Data Structures for Coordinate Spaces and Points
95 :h3 id=matrix.Matrices
97 TYPE1IMAGER uses 2x2 transformation matrices. We'll use C notation for
98 such a matrix (M[2][2]), the first index being rows, the second columns.
102 :h3.The "doublematrix" Structure
104 We frequently find it desirable to store both a matrix and its
105 inverse. We store these in a "doublematrix" structure.
108 /*SHARED LINE(S) ORIGINATED HERE*/
111 :h3.The "XYspace" Structure
113 The XYspace structure represents the XYspace object.
116 /*SHARED LINE(S) ORIGINATED HERE*/
117 #define RESERVED 10 /* 'n' IDs are reserved for invalid & immortal spaces */
120 #define NEXTID ((SpaceID < RESERVED) ? (SpaceID = RESERVED) : ++SpaceID)
122 static unsigned int SpaceID = 1;
124 struct XYspace *CopySpace(S)
125 register struct XYspace *S;
127 S = (struct XYspace *)Allocate(sizeof(struct XYspace), S, 0);
132 :h3.The "fractpoint" Structure
134 A fractional point is just a "fractpel" x and y:
137 /*SHARED LINE(S) ORIGINATED HERE*/
140 :h3.Lazy Evaluation of Matrix Inverses
142 Calculating the inverse of a matrix is somewhat involved, and we usually
143 do not need them. So, we flag whether or not the space has the inverse
147 #define HASINVERSE(flag) ((flag)&0x80)
150 The following macro forces a space to have an inverse:
153 #define CoerceInverse(S) if (!HASINVERSE((S)->flag)) { \
154 MatrixInvert((S)->tofract.normal, (S)->tofract.inverse); (S)->flag |= HASINVERSE(ON); }
158 IDENTITY space is (logically) the space corresponding to the identity
159 transformation matrix. However, since all our transformation matrices
160 have a common FRACTFLOAT scale factor to convert to 'fractpel's, that
161 is actually what we store in 'tofract' matrix of IDENTITY:
164 static struct XYspace identity = { SPACETYPE, ISPERMANENT(ON) + ISIMMORTAL(ON)
165 + HASINVERSE(ON), 2, /* added 3-26-91 PNM */
167 NULL, NULL, NULL, NULL,
169 FRACTFLOAT, 0.0, 0.0, FRACTFLOAT,
170 1.0/FRACTFLOAT, 0.0, 0.0, 1.0/FRACTFLOAT,
172 struct XYspace *IDENTITY = &identity;
176 #define MAXCONTEXTS 16
178 static struct doublematrix contexts[MAXCONTEXTS];
182 static int nextcontext = 1;
184 /*SHARED LINE(S) ORIGINATED HERE*/
187 #define pointer void *
189 #define pointer char *
193 :h3.FindDeviceContext() - Find the Context Given a Device
195 This routine, given a device, returns the index of the device's
196 transformation matrix in the context array. If it cannot find it,
197 it will allocate a new array entry and fill it out.
200 static int FindDeviceContext(device)
201 pointer device; /* device token */
203 double M[2][2]; /* temporary matrix */
204 float Xres,Yres; /* device resolution */
205 int orient = -1; /* device orientation */
206 int rc = -1; /* return code for QueryDeviceState */
208 if (rc != 0) /* we only bother with this check once */
209 abort("Context: QueryDeviceState didn't work");
211 M[0][0] = M[1][0] = M[0][1] = M[1][1] = 0.0;
215 M[0][0] = Xres; M[1][1] = -Yres;
218 M[1][0] = Yres; M[0][1] = Xres;
221 M[0][0] = -Xres; M[1][1] = Yres;
224 M[1][0] = -Yres; M[0][1] = -Xres;
227 abort("QueryDeviceState returned invalid orientation");
229 return(FindContext(M));
233 :h3.FindContext() - Find the Context Given a Matrix
235 This routine, given a matrix, returns the index of that matrix matrix in
236 the context array. If it cannot find it, it will allocate a new array
237 entry and fill it out.
241 double M[2][2]; /* array to search for */
243 register int i; /* loop variable for search */
244 for (i=0; i < nextcontext; i++)
245 if (M[0][0] == contexts[i].normal[0][0] && M[1][0] == contexts[i].normal[1][0]
246 && M[0][1] == contexts[i].normal[0][1] && M[1][1] == contexts[i].normal[1][1])
249 if (i >= nextcontext) {
250 if (i >= MAXCONTEXTS)
251 abort("Context: out of them");
252 LONGCOPY(contexts[i].normal, M, sizeof(contexts[i].normal));
253 MatrixInvert(M, contexts[i].inverse);
261 :h3.Context() - Create a Coordinate Space for a Device
263 This user operator is implemented by first finding the device context
264 array index, then transforming IDENTITY space to create an appropriate
268 struct XYspace *Context(device, units)
269 pointer device; /* device token */
270 double units; /* multiples of one inch */
272 double M[2][2]; /* device transformation matrix */
273 register int n; /* will hold device context number */
274 register struct XYspace *S; /* XYspace constructed */
276 IfTrace2((MustTraceCalls),"Context(%x, %f)\n", device, &units);
278 ARGCHECK((device == NULL), "Context of NULLDEVICE not allowed",
279 NULL, IDENTITY, (0), struct XYspace *);
280 ARGCHECK((units == 0.0), "Context: bad units", NULL, IDENTITY, (0), struct XYspace *);
282 n = FindDeviceContext(device);
284 LONGCOPY(M, contexts[n].normal, sizeof(M));
291 S = (struct XYspace *)Xform(IDENTITY, M);
299 :h3.ConsiderContext() - Adjust a Matrix to Take Out Device Transform
301 Remember, we have :f/x times U times D/ and :f/M/ and and we want :f/x
302 times U times M times D/. An easy way to do this is to calculate
303 :f/D sup <-1> times M times D/, because:
305 x times U times D times D sup <-1> times M times D = x times U times M times D
307 So this subroutine, given an :f/M/and an object, finds the :f/D/ for that
308 object and modifies :f/M/ so it is :f/D sup <-1> times M times D/.
311 static void ConsiderContext(obj, M)
312 register struct xobject *obj; /* object to be transformed */
313 register double M[2][2]; /* matrix (may be changed) */
315 register int context; /* index in contexts array */
317 if (obj == NULL) return;
319 if (ISPATHTYPE(obj->type)) {
320 struct segment *path = (struct segment *) obj;
322 context = path->context;
324 else if (obj->type == SPACETYPE) {
325 struct XYspace *S = (struct XYspace *) obj;
327 context = S->context;
329 else if (obj->type == PICTURETYPE) {
333 context = NULLCONTEXT;
335 if (context != NULLCONTEXT) {
336 MatrixMultiply(contexts[context].inverse, M, M);
337 MatrixMultiply(M, contexts[context].normal, M);
342 :h2.Conversion from User's X,Y to "fractpel" X,Y
344 When the user is building paths (lines, moves, curves, etc.) he passes
345 the control points (x,y) for the paths together with an XYspace. We
346 must convert from the user's (x,y) to our internal representation
347 which is in pels (fractpels, actually). This involves transforming
348 the user's (x,y) under the coordinate space transformation. It is
349 important that we do this quickly. So, we store pointers to different
350 conversion functions right in the XYspace structure. This allows us
351 to have simpler special case functions for the more commonly
352 encountered types of transformations.
354 :h3.Convert(), IConvert(), and ForceFloat() - Called Through "XYspace" Structure
356 These are functions that fit in the "convert" and "iconvert" function
357 pointers in the XYspace structure. They call the "xconvert", "yconvert",
358 "ixconvert", and "iyconvert" as appropriate to actually do the work.
359 These secondary routines come in many flavors to handle different
360 special cases as quickly as possible.
363 static void FXYConvert(pt, S, x, y)
364 register struct fractpoint *pt; /* point to set */
365 register struct XYspace *S; /* relevant coordinate space */
366 register double x,y; /* user's coordinates of point */
368 pt->x = (*S->xconvert)(S->tofract.normal[0][0], S->tofract.normal[1][0], x, y);
369 pt->y = (*S->yconvert)(S->tofract.normal[0][1], S->tofract.normal[1][1], x, y);
372 static void IXYConvert(pt, S, x, y)
373 register struct fractpoint *pt; /* point to set */
374 register struct XYspace *S; /* relevant coordinate space */
375 register long x,y; /* user's coordinates of point */
377 pt->x = (*S->ixconvert)(S->itofract[0][0], S->itofract[1][0], x, y);
378 pt->y = (*S->iyconvert)(S->itofract[0][1], S->itofract[1][1], x, y);
382 ForceFloat is a substitute for IConvert(), when we just do not have
383 enough significant digits in the coefficients to get high enough
384 precision in the answer with fixed point arithmetic. So, we force the
385 integers to floats, and do the arithmetic all with floats:
388 static void ForceFloat(pt, S, x, y)
389 register struct fractpoint *pt; /* point to set */
390 register struct XYspace *S; /* relevant coordinate space */
391 register long x,y; /* user's coordinates of point */
393 (*S->convert)(pt, S, (double) x, (double) y);
397 :h3.FXYboth(), FXonly(), FYonly() - Floating Point Conversion
399 These are the routines we use when the user has given us floating
400 point numbers for x and y. FXYboth() is the general purpose routine;
401 FXonly() and FYonly() are special cases when one of the coefficients
405 static fractpel FXYboth(cx, cy, x, y)
406 register double cx,cy; /* x and y coefficients */
407 register double x,y; /* user x,y */
409 register double r; /* temporary float */
412 return((fractpel) r);
416 static fractpel FXonly(cx, cy, x, y)
417 register double cx,cy; /* x and y coefficients */
418 register double x,y; /* user x,y */
420 register double r; /* temporary float */
423 return((fractpel) r);
427 static fractpel FYonly(cx, cy, x, y)
428 register double cx,cy; /* x and y coefficients */
429 register double x,y; /* user x,y */
431 register double r; /* temporary float */
434 return((fractpel) r);
438 :h3.IXYboth(), IXonly(), IYonly() - Simple Integer Conversion
440 These are the routines we use when the user has given us integers for
441 x and y, and the coefficients have enough significant digits to
442 provide precise answers with only "long" (32 bit?) multiplication.
443 IXYboth() is the general purpose routine; IXonly() and IYonly() are
444 special cases when one of the coefficients is 0.
447 static fractpel IXYboth(cx, cy, x, y)
448 register fractpel cx,cy; /* x and y coefficients */
449 register long x,y; /* user x,y */
451 return(x * cx + y * cy);
455 static fractpel IXonly(cx, cy, x, y)
456 register fractpel cx,cy; /* x and y coefficients */
457 register long x,y; /* user x,y */
463 static fractpel IYonly(cx, cy, x, y)
464 register fractpel cx,cy; /* x and y coefficients */
465 register long x,y; /* user x,y */
472 :h3.FPXYboth(), FPXonly(), FPYonly() - More Involved Integer Conversion
474 These are the routines we use when the user has given us integers for
475 x and y, but the coefficients do not have enough significant digits to
476 provide precise answers with only "long" (32 bit?) multiplication.
477 We have increased the number of significant bits in the coefficients
478 by FRACTBITS; therefore we must use "double long" (64 bit?)
479 multiplication by calling FPmult(). FPXYboth() is the general purpose
480 routine; FPXonly() and FPYonly() are special cases when one of the
483 Note that it is perfectly possible for us to calculate X with the
484 "FP" method and Y with the "I" method, or vice versa. It all depends
485 on how the functions in the XYspace structure are filled out.
488 static fractpel FPXYboth(cx, cy, x, y)
489 register fractpel cx,cy; /* x and y coefficients */
490 register long x,y; /* user x,y */
492 return( FPmult(x, cx) + FPmult(y, cy) );
496 static fractpel FPXonly(cx, cy, x, y)
497 register fractpel cx,cy; /* x and y coefficients */
498 register long x,y; /* user x,y */
500 return( FPmult(x, cx) );
504 static fractpel FPYonly(cx, cy, x, y)
505 register fractpel cx,cy; /* x and y coefficients */
506 register long x,y; /* user x,y */
508 return( FPmult(y, cy) );
514 :h3.FillOutFcns() - Determine the Appropriate Functions to Use for Conversion
516 This function fills out the "convert" and "iconvert" function pointers
517 in an XYspace structure, and also fills the "helper"
518 functions that actually do the work.
521 static void FillOutFcns(S)
522 register struct XYspace *S; /* functions will be set in this structure */
524 S->convert = FXYConvert;
525 S->iconvert = IXYConvert;
527 FindFfcn(S->tofract.normal[0][0], S->tofract.normal[1][0], &S->xconvert);
528 FindFfcn(S->tofract.normal[0][1], S->tofract.normal[1][1], &S->yconvert);
529 FindIfcn(S->tofract.normal[0][0], S->tofract.normal[1][0],
530 &S->itofract[0][0], &S->itofract[1][0], &S->ixconvert);
531 FindIfcn(S->tofract.normal[0][1], S->tofract.normal[1][1],
532 &S->itofract[0][1], &S->itofract[1][1], &S->iyconvert);
534 if (S->ixconvert == NULL || S->iyconvert == NULL)
535 S->iconvert = ForceFloat;
539 :h4.FindFfcn() - Subroutine of FillOutFcns() to Fill Out Floating Functions
541 This function tests for the special case of one of the coefficients
545 static void FindFfcn(cx, cy, fcnP)
546 register double cx,cy; /* x and y coefficients */
547 register fractpel (**fcnP)(); /* pointer to function to set */
558 :h4.FindIfcn() - Subroutine of FillOutFcns() to Fill Out Integer Functions
560 There are two types of integer functions, the 'I' type and the 'FP' type.
561 We use the I type functions when we are satisfied with simple integer
562 arithmetic. We used the FP functions when we feel we need higher
563 precision (but still fixed point) arithmetic. If all else fails,
564 we store a NULL indicating that this we should do the conversion in
568 static void FindIfcn(cx, cy, icxP, icyP, fcnP)
569 register double cx,cy; /* x and y coefficients */
570 register fractpel *icxP,*icyP; /* fixed point coefficients to set */
571 register fractpel (**fcnP)(); /* pointer to function to set */
573 register fractpel imax; /* maximum of cx and cy */
578 if (cx != (float) (*icxP) || cy != (float) (*icyP)) {
580 At this point we know our integer approximations of the coefficients
581 are not exact. However, we will still use them if the maximum
582 coefficient will not fit in a 'fractpel'. Of course, we have little
583 choice at that point, but we haven't lost that much precision by
584 staying with integer arithmetic. We have enough significant digits
586 any error we introduce is less than one part in 2:sup/16/.
589 imax = MAX(ABS(*icxP), ABS(*icyP));
590 if (imax < (fractpel) (1<<(FRACTBITS-1)) ) {
592 At this point we know our integer approximations just do not have
593 enough significant digits for accuracy. We will add FRACTBITS
594 significant digits to the coefficients (by multiplying them by
595 1<<FRACTBITS) and go to the "FP" form of the functions. First, we
596 check to see if we have ANY significant digits at all (that is, if
597 imax == 0). If we don't, we suspect that adding FRACTBITS digits
598 won't help, so we punt the whole thing.
616 Now we check for special cases where one coefficient is zero (after
620 *fcnP = (*fcnP == FPXYboth) ? FPYonly : IYonly;
622 *fcnP = (*fcnP == FPXYboth) ? FPXonly : IXonly;
625 :h3.UnConvert() - Find User Coordinates From FractPoints
627 The interesting thing with this routine is that we avoid calculating
628 the matrix inverse of the device transformation until we really need
629 it, which is to say, until this routine is called for the first time
630 with a given coordinate space.
632 We also only calculate it only once. If the inverted matrix is valid,
633 we don't calculate it; if not, we do. We never expect matrices with
634 zero determinants, so by convention, we mark the matrix is invalid by
635 marking both X terms zero.
638 void UnConvert(S, pt, xp, yp)
639 register struct XYspace *S; /* relevant coordinate space */
640 register struct fractpoint *pt; /* device coordinates */
641 double *xp,*yp; /* where to store resulting x,y */
648 *xp = S->tofract.inverse[0][0] * x + S->tofract.inverse[1][0] * y;
649 *yp = S->tofract.inverse[0][1] * x + S->tofract.inverse[1][1] * y;
656 :h3 id=xform.Xform() - Transform Object in X and Y
658 TYPE1IMAGER wants transformations of objects like paths to be identical
659 to transformations of spaces. For example, if you scale a line(1,1)
660 by 10 it should yield the same result as generating the line(1,1) in
661 a coordinate space that has been scaled by 10.
663 We handle fonts by storing the accumulated transform, for example, SR
664 (accumulating on the right). Then when we map the font through space TD,
665 for example, we multiply the accumulated font transform on the left by
666 the space transform on the right, yielding SRTD in this case. We will
667 get the same result if we did S, then R, then T on the space and mapping
668 an unmodified font through that space.
671 struct xobject *t1_Xform(obj, M)
672 register struct xobject *obj; /* object to transform */
673 register double M[2][2]; /* transformation matrix */
678 if (obj->type == FONTTYPE) {
679 register struct font *F = (struct font *) obj;
682 return((struct xobject*)F);
684 if (obj->type == PICTURETYPE) {
686 In the case of a picture, we choose both to update the picture's
687 transformation matrix and keep the handles up to date.
689 register struct picture *P = (struct picture *) obj;
690 register struct segment *handles; /* temporary path to transform handles */
692 P = UniquePicture(P);
693 handles = PathSegment(LINETYPE, P->origin.x, P->origin.y);
694 handles = Join(handles,
695 PathSegment(LINETYPE, P->ending.x, P->ending.y) );
696 handles = (struct segment *)Xform((struct xobject *) handles, M);
697 P->origin = handles->dest;
698 P->ending = handles->link->dest;
700 return((struct xobject *)P);
703 if (ISPATHTYPE(obj->type)) {
704 struct XYspace pseudo; /* local temporary space */
705 PseudoSpace(&pseudo, M);
706 return((struct xobject *) PathTransform(obj, &pseudo));
710 if (obj->type == SPACETYPE) {
711 register struct XYspace *S = (struct XYspace *) obj;
713 /* replaced ISPERMANENT(S->flag) with S->references > 1 3-26-91 PNM */
714 if (S->references > 1)
719 MatrixMultiply(S->tofract.normal, M, S->tofract.normal);
721 * mark inverted matrix invalid:
723 S->flag &= ~HASINVERSE(ON);
726 return((struct xobject *) S);
729 return(ArgErr("Untransformable object", obj, obj));
733 :h3.Transform() - Transform an Object
735 This is the external user's entry point.
737 struct xobject *t1_Transform(obj, cxx, cyx, cxy, cyy)
739 double cxx,cyx,cxy,cyy; /* 2x2 transform matrix elements in row order */
743 IfTrace1((MustTraceCalls),"Transform(%z,", obj);
744 IfTrace4((MustTraceCalls)," %f %f %f %f)\n", &cxx, &cyx, &cxy, &cyy);
750 ConsiderContext(obj, M);
751 return(Xform(obj, M));
754 :h3.Scale() - Special Case of Transform()
756 This is a user operator.
759 struct xobject *t1_Scale(obj, sx, sy)
760 struct xobject *obj; /* object to scale */
761 double sx,sy; /* scale factors in x and y */
764 IfTrace3((MustTraceCalls),"Scale(%z, %f, %f)\n", obj, &sx, &sy);
767 M[1][0] = M[0][1] = 0.0;
768 ConsiderContext(obj, M);
769 return(Xform(obj, M));
773 :h3 id=rotate.Rotate() - Special Case of Transform()
775 We special-case different settings of 'degrees' for performance
776 and accuracy within the DegreeSin() and DegreeCos() routines themselves.
780 struct xobject *xiRotate(obj, degrees)
781 struct xobject *obj; /* object to be transformed */
782 double degrees; /* degrees of COUNTER-clockwise rotation */
787 IfTrace2((MustTraceCalls),"Rotate(%z, %f)\n", obj, °rees);
789 M[0][0] = M[1][1] = DegreeCos(degrees);
790 M[1][0] = - (M[0][1] = DegreeSin(degrees));
791 ConsiderContext(obj, M);
792 return(Xform(obj, M));
797 :h3.PseudoSpace() - Build a Coordinate Space from a Matrix
799 Since we have built all this optimized code that, given an (x,y) and
800 a coordinate space, yield transformed (x,y), it seems a shame not to
801 use the same logic when we need to multiply an (x,y) by an arbitrary
802 matrix that is not (initially) part of a coordinate space. This
803 subroutine takes the arbitrary matrix and builds a coordinate
804 space, with all its nifty function pointers.
807 void PseudoSpace(S, M)
808 struct XYspace *S; /* coordinate space structure to fill out */
809 double M[2][2]; /* matrix that will become 'tofract.normal' */
812 S->flag = ISPERMANENT(ON) + ISIMMORTAL(ON);
813 S->references = 2; /* 3-26-91 added PNM */
814 S->tofract.normal[0][0] = M[0][0];
815 S->tofract.normal[1][0] = M[1][0];
816 S->tofract.normal[0][1] = M[0][1];
817 S->tofract.normal[1][1] = M[1][1];
823 :h2 id=matrixa.Matrix Arithmetic
825 Following the convention in Newman and Sproull, :hp1/Interactive
827 matrices are organized:
832 A point is horizontal, for example:
837 :formula/x prime = cxx times x + cxy times y/
838 :formula/y prime = cyx times x + cyy times y/
839 I've seen the other convention, where transform matrices are
840 transposed, equally often in the literature.
844 :h3.MatrixMultiply() - Implements Multiplication of Two Matrices
846 Implements matrix multiplication, A * B = C.
848 To remind myself, matrix multiplication goes rows of A times columns
850 The output matrix may be the same as one of the input matrices.
852 void MatrixMultiply(A, B, C)
853 register double A[2][2],B[2][2]; /* input matrices */
854 register double C[2][2]; /* output matrix */
856 register double txx,txy,tyx,tyy;
858 txx = A[0][0] * B[0][0] + A[0][1] * B[1][0];
859 txy = A[1][0] * B[0][0] + A[1][1] * B[1][0];
860 tyx = A[0][0] * B[0][1] + A[0][1] * B[1][1];
861 tyy = A[1][0] * B[0][1] + A[1][1] * B[1][1];
869 :h3.MatrixInvert() - Invert a Matrix
871 My reference for matrix inversion was :hp1/Elementary Linear Algebra/
872 by Paul C. Shields, Worth Publishers, Inc., 1968.
874 void MatrixInvert(M, Mprime)
875 double M[2][2]; /* input matrix */
876 double Mprime[2][2]; /* output inverted matrix */
878 register double D; /* determinant of matrix M */
879 register double txx,txy,tyx,tyy;
886 D = M[1][1] * M[0][0] - M[1][0] * M[0][1];
888 abort("MatrixInvert: can't");
890 Mprime[0][0] = tyy / D;
891 Mprime[1][0] = -txy / D;
892 Mprime[0][1] = -tyx / D;
893 Mprime[1][1] = txx / D;
896 :h2.Initialization, Queries, and Debug
899 :h3.InitSpaces() - Initialize Constant Spaces
901 For compatibility, we initialize a coordinate space called USER which
902 maps 72nds of an inch to pels on the default device.
905 struct XYspace *USER = &identity;
909 extern char *DEFAULTDEVICE;
911 IDENTITY->type = SPACETYPE;
912 FillOutFcns(IDENTITY);
914 contexts[NULLCONTEXT].normal[1][0]
915 = contexts[NULLCONTEXT].normal[0][1]
916 = contexts[NULLCONTEXT].inverse[1][0]
917 = contexts[NULLCONTEXT].inverse[0][1] = 0.0;
918 contexts[NULLCONTEXT].normal[0][0]
919 = contexts[NULLCONTEXT].normal[1][1]
920 = contexts[NULLCONTEXT].inverse[0][0]
921 = contexts[NULLCONTEXT].inverse[1][1] = 1.0;
923 USER->flag |= ISIMMORTAL(ON);
927 :h3.QuerySpace() - Returns the Transformation Matrix of a Space
929 Since the tofract matrix of an XYspace includes the scale factor
930 necessary to produce fractpel results (i.e., FRACTFLOAT), this
931 must be taken out before we return the matrix to the user. Fortunately,
932 this is simple: just multiply by the inverse of IDENTITY!
935 void QuerySpace(S, cxxP, cyxP, cxyP, cyyP)
936 register struct XYspace *S; /* space asked about */
937 register double *cxxP,*cyxP,*cxyP,*cyyP; /* where to put answer */
939 double M[2][2]; /* temp matrix to build user's answer */
941 if (S->type != SPACETYPE) {
942 ArgErr("QuerySpace: not a space", S, NULL);
945 MatrixMultiply(S->tofract.normal, IDENTITY->tofract.inverse, M);
953 :h3.FormatFP() - Format a Fixed Point Pel
955 We format the pel as "dddd.XXXX", where XX's are hexidecimal digits,
956 and the dd's are decimal digits. This might be a little confusing
957 mixing hexidecimal and decimal like that, but it is convenient
960 We make sure we have N (FRACTBITS/4) digits past the decimal point.
962 #define FRACTMASK ((1<<FRACTBITS)-1) /* mask for fractional part */
964 void FormatFP(string, fpel)
965 register char *string; /* output string */
966 register fractpel fpel; /* fractional pel input */
979 sprintf(temp, "000%x", fpel & FRACTMASK);
980 s = temp + strlen(temp) - (FRACTBITS/4);
982 sprintf(string, "%s%d.%sx", sign, fpel >> FRACTBITS, s);
986 :h3.DumpSpace() - Display a Coordinate Space
990 register struct XYspace *S;
992 IfTrace4(TRUE,"--Coordinate space at %x,ID=%d,convert=%x,iconvert=%x\n",
993 S, S->ID, S->convert, S->iconvert);
994 IfTrace2(TRUE," | %12.3f %12.3f |",
995 &S->tofract.normal[0][0], &S->tofract.normal[0][1]);
996 IfTrace2(TRUE," [ %p %p ]\n", S->itofract[0][0], S->itofract[0][1]);
997 IfTrace2(TRUE," | %12.3f %12.3f |",
998 &S->tofract.normal[1][0], &S->tofract.normal[1][1]);
999 IfTrace2(TRUE," [ %p %p ]\n", S->itofract[1][0], S->itofract[1][1]);