xref: /plan9/sys/src/cmd/gs/src/gsmatrix.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989, 1995, 1996, 1998, 1999 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: gsmatrix.c,v 1.8 2004/08/31 13:23:16 igor Exp $ */
18 /* Matrix operators for Ghostscript library */
19 #include "math_.h"
20 #include "memory_.h"
21 #include "gx.h"
22 #include "gserrors.h"
23 #include "gxfarith.h"
24 #include "gxfixed.h"
25 #include "gxmatrix.h"
26 #include "stream.h"
27 
28 /* The identity matrix */
29 private const gs_matrix gs_identity_matrix =
30 {identity_matrix_body};
31 
32 /* ------ Matrix creation ------ */
33 
34 /* Create an identity matrix */
35 void
gs_make_identity(gs_matrix * pmat)36 gs_make_identity(gs_matrix * pmat)
37 {
38     *pmat = gs_identity_matrix;
39 }
40 
41 /* Create a translation matrix */
42 int
gs_make_translation(floatp dx,floatp dy,gs_matrix * pmat)43 gs_make_translation(floatp dx, floatp dy, gs_matrix * pmat)
44 {
45     *pmat = gs_identity_matrix;
46     pmat->tx = dx;
47     pmat->ty = dy;
48     return 0;
49 }
50 
51 /* Create a scaling matrix */
52 int
gs_make_scaling(floatp sx,floatp sy,gs_matrix * pmat)53 gs_make_scaling(floatp sx, floatp sy, gs_matrix * pmat)
54 {
55     *pmat = gs_identity_matrix;
56     pmat->xx = sx;
57     pmat->yy = sy;
58     return 0;
59 }
60 
61 /* Create a rotation matrix. */
62 /* The angle is in degrees. */
63 int
gs_make_rotation(floatp ang,gs_matrix * pmat)64 gs_make_rotation(floatp ang, gs_matrix * pmat)
65 {
66     gs_sincos_t sincos;
67 
68     gs_sincos_degrees(ang, &sincos);
69     pmat->yy = pmat->xx = sincos.cos;
70     pmat->xy = sincos.sin;
71     pmat->yx = -sincos.sin;
72     pmat->tx = pmat->ty = 0.0;
73     return 0;
74 }
75 
76 /* ------ Matrix arithmetic ------ */
77 
78 /* Multiply two matrices.  We should check for floating exceptions, */
79 /* but for the moment it's just too awkward. */
80 /* Since this is used heavily, we check for shortcuts. */
81 int
gs_matrix_multiply(const gs_matrix * pm1,const gs_matrix * pm2,gs_matrix * pmr)82 gs_matrix_multiply(const gs_matrix * pm1, const gs_matrix * pm2, gs_matrix * pmr)
83 {
84     double xx1 = pm1->xx, yy1 = pm1->yy;
85     double tx1 = pm1->tx, ty1 = pm1->ty;
86     double xx2 = pm2->xx, yy2 = pm2->yy;
87     double xy2 = pm2->xy, yx2 = pm2->yx;
88 
89     if (is_xxyy(pm1)) {
90 	pmr->tx = tx1 * xx2 + pm2->tx;
91 	pmr->ty = ty1 * yy2 + pm2->ty;
92 	if (is_fzero(xy2))
93 	    pmr->xy = 0;
94 	else
95 	    pmr->xy = xx1 * xy2,
96 		pmr->ty += tx1 * xy2;
97 	pmr->xx = xx1 * xx2;
98 	if (is_fzero(yx2))
99 	    pmr->yx = 0;
100 	else
101 	    pmr->yx = yy1 * yx2,
102 		pmr->tx += ty1 * yx2;
103 	pmr->yy = yy1 * yy2;
104     } else {
105 	double xy1 = pm1->xy, yx1 = pm1->yx;
106 
107 	pmr->xx = xx1 * xx2 + xy1 * yx2;
108 	pmr->xy = xx1 * xy2 + xy1 * yy2;
109 	pmr->yy = yx1 * xy2 + yy1 * yy2;
110 	pmr->yx = yx1 * xx2 + yy1 * yx2;
111 	pmr->tx = tx1 * xx2 + ty1 * yx2 + pm2->tx;
112 	pmr->ty = tx1 * xy2 + ty1 * yy2 + pm2->ty;
113     }
114     return 0;
115 }
116 
117 /* Invert a matrix.  Return gs_error_undefinedresult if not invertible. */
118 int
gs_matrix_invert(const gs_matrix * pm,gs_matrix * pmr)119 gs_matrix_invert(const gs_matrix * pm, gs_matrix * pmr)
120 {				/* We have to be careful about fetch/store order, */
121     /* because pm might be the same as pmr. */
122     if (is_xxyy(pm)) {
123 	if (is_fzero(pm->xx) || is_fzero(pm->yy))
124 	    return_error(gs_error_undefinedresult);
125 	pmr->tx = -(pmr->xx = 1.0 / pm->xx) * pm->tx;
126 	pmr->xy = 0.0;
127 	pmr->yx = 0.0;
128 	pmr->ty = -(pmr->yy = 1.0 / pm->yy) * pm->ty;
129     } else {
130 	double det = pm->xx * pm->yy - pm->xy * pm->yx;
131 	double mxx = pm->xx, mtx = pm->tx;
132 
133 	if (det == 0)
134 	    return_error(gs_error_undefinedresult);
135 	pmr->xx = pm->yy / det;
136 	pmr->xy = -pm->xy / det;
137 	pmr->yx = -pm->yx / det;
138 	pmr->yy = mxx / det;	/* xx is already changed */
139 	pmr->tx = -(mtx * pmr->xx + pm->ty * pmr->yx);
140 	pmr->ty = -(mtx * pmr->xy + pm->ty * pmr->yy);	/* tx ditto */
141     }
142     return 0;
143 }
144 
145 /* Translate a matrix, possibly in place. */
146 int
gs_matrix_translate(const gs_matrix * pm,floatp dx,floatp dy,gs_matrix * pmr)147 gs_matrix_translate(const gs_matrix * pm, floatp dx, floatp dy, gs_matrix * pmr)
148 {
149     gs_point trans;
150     int code = gs_distance_transform(dx, dy, pm, &trans);
151 
152     if (code < 0)
153 	return code;
154     if (pmr != pm)
155 	*pmr = *pm;
156     pmr->tx += trans.x;
157     pmr->ty += trans.y;
158     return 0;
159 }
160 
161 /* Scale a matrix, possibly in place. */
162 int
gs_matrix_scale(const gs_matrix * pm,floatp sx,floatp sy,gs_matrix * pmr)163 gs_matrix_scale(const gs_matrix * pm, floatp sx, floatp sy, gs_matrix * pmr)
164 {
165     pmr->xx = pm->xx * sx;
166     pmr->xy = pm->xy * sx;
167     pmr->yx = pm->yx * sy;
168     pmr->yy = pm->yy * sy;
169     if (pmr != pm) {
170 	pmr->tx = pm->tx;
171 	pmr->ty = pm->ty;
172     }
173     return 0;
174 }
175 
176 /* Rotate a matrix, possibly in place.  The angle is in degrees. */
177 int
gs_matrix_rotate(const gs_matrix * pm,floatp ang,gs_matrix * pmr)178 gs_matrix_rotate(const gs_matrix * pm, floatp ang, gs_matrix * pmr)
179 {
180     double mxx, mxy;
181     gs_sincos_t sincos;
182 
183     gs_sincos_degrees(ang, &sincos);
184     mxx = pm->xx, mxy = pm->xy;
185     pmr->xx = sincos.cos * mxx + sincos.sin * pm->yx;
186     pmr->xy = sincos.cos * mxy + sincos.sin * pm->yy;
187     pmr->yx = sincos.cos * pm->yx - sincos.sin * mxx;
188     pmr->yy = sincos.cos * pm->yy - sincos.sin * mxy;
189     if (pmr != pm) {
190 	pmr->tx = pm->tx;
191 	pmr->ty = pm->ty;
192     }
193     return 0;
194 }
195 
196 /* ------ Coordinate transformations (floating point) ------ */
197 
198 /* Note that all the transformation routines take separate */
199 /* x and y arguments, but return their result in a point. */
200 
201 /* Transform a point. */
202 int
gs_point_transform(floatp x,floatp y,const gs_matrix * pmat,gs_point * ppt)203 gs_point_transform(floatp x, floatp y, const gs_matrix * pmat,
204 		   gs_point * ppt)
205 {
206     ppt->x = x * pmat->xx + pmat->tx;
207     ppt->y = y * pmat->yy + pmat->ty;
208     if (!is_fzero(pmat->yx))
209 	ppt->x += y * pmat->yx;
210     if (!is_fzero(pmat->xy))
211 	ppt->y += x * pmat->xy;
212     return 0;
213 }
214 
215 /* Inverse-transform a point. */
216 /* Return gs_error_undefinedresult if the matrix is not invertible. */
217 int
gs_point_transform_inverse(floatp x,floatp y,const gs_matrix * pmat,gs_point * ppt)218 gs_point_transform_inverse(floatp x, floatp y, const gs_matrix * pmat,
219 			   gs_point * ppt)
220 {
221     if (is_xxyy(pmat)) {
222 	if (is_fzero(pmat->xx) || is_fzero(pmat->yy))
223 	    return_error(gs_error_undefinedresult);
224 	ppt->x = (x - pmat->tx) / pmat->xx;
225 	ppt->y = (y - pmat->ty) / pmat->yy;
226 	return 0;
227     } else if (is_xyyx(pmat)) {
228 	if (is_fzero(pmat->xy) || is_fzero(pmat->yx))
229 	    return_error(gs_error_undefinedresult);
230 	ppt->x = (y - pmat->ty) / pmat->xy;
231 	ppt->y = (x - pmat->tx) / pmat->yx;
232 	return 0;
233     } else {			/* There are faster ways to do this, */
234 	/* but we won't implement one unless we have to. */
235 	gs_matrix imat;
236 	int code = gs_matrix_invert(pmat, &imat);
237 
238 	if (code < 0)
239 	    return code;
240 	return gs_point_transform(x, y, &imat, ppt);
241     }
242 }
243 
244 /* Transform a distance. */
245 int
gs_distance_transform(floatp dx,floatp dy,const gs_matrix * pmat,gs_point * pdpt)246 gs_distance_transform(floatp dx, floatp dy, const gs_matrix * pmat,
247 		      gs_point * pdpt)
248 {
249     pdpt->x = dx * pmat->xx;
250     pdpt->y = dy * pmat->yy;
251     if (!is_fzero(pmat->yx))
252 	pdpt->x += dy * pmat->yx;
253     if (!is_fzero(pmat->xy))
254 	pdpt->y += dx * pmat->xy;
255     return 0;
256 }
257 
258 /* Inverse-transform a distance. */
259 /* Return gs_error_undefinedresult if the matrix is not invertible. */
260 int
gs_distance_transform_inverse(floatp dx,floatp dy,const gs_matrix * pmat,gs_point * pdpt)261 gs_distance_transform_inverse(floatp dx, floatp dy,
262 			      const gs_matrix * pmat, gs_point * pdpt)
263 {
264     if (is_xxyy(pmat)) {
265 	if (is_fzero(pmat->xx) || is_fzero(pmat->yy))
266 	    return_error(gs_error_undefinedresult);
267 	pdpt->x = dx / pmat->xx;
268 	pdpt->y = dy / pmat->yy;
269     } else if (is_xyyx(pmat)) {
270 	if (is_fzero(pmat->xy) || is_fzero(pmat->yx))
271 	    return_error(gs_error_undefinedresult);
272 	pdpt->x = dy / pmat->xy;
273 	pdpt->y = dx / pmat->yx;
274     } else {
275 	double det = pmat->xx * pmat->yy - pmat->xy * pmat->yx;
276 
277 	if (det == 0)
278 	    return_error(gs_error_undefinedresult);
279 	pdpt->x = (dx * pmat->yy - dy * pmat->yx) / det;
280 	pdpt->y = (dy * pmat->xx - dx * pmat->xy) / det;
281     }
282     return 0;
283 }
284 
285 /* Compute the bounding box of 4 points. */
286 int
gs_points_bbox(const gs_point pts[4],gs_rect * pbox)287 gs_points_bbox(const gs_point pts[4], gs_rect * pbox)
288 {
289 #define assign_min_max(vmin, vmax, v0, v1)\
290   if ( v0 < v1 ) vmin = v0, vmax = v1; else vmin = v1, vmax = v0
291 #define assign_min_max_4(vmin, vmax, v0, v1, v2, v3)\
292   { double min01, max01, min23, max23;\
293     assign_min_max(min01, max01, v0, v1);\
294     assign_min_max(min23, max23, v2, v3);\
295     vmin = min(min01, min23);\
296     vmax = max(max01, max23);\
297   }
298     assign_min_max_4(pbox->p.x, pbox->q.x,
299 		     pts[0].x, pts[1].x, pts[2].x, pts[3].x);
300     assign_min_max_4(pbox->p.y, pbox->q.y,
301 		     pts[0].y, pts[1].y, pts[2].y, pts[3].y);
302 #undef assign_min_max
303 #undef assign_min_max_4
304     return 0;
305 }
306 
307 /* Transform or inverse-transform a bounding box. */
308 /* Return gs_error_undefinedresult if the matrix is not invertible. */
309 private int
bbox_transform_either_only(const gs_rect * pbox_in,const gs_matrix * pmat,gs_point pts[4],int (* point_xform)(floatp,floatp,const gs_matrix *,gs_point *))310 bbox_transform_either_only(const gs_rect * pbox_in, const gs_matrix * pmat,
311 			   gs_point pts[4],
312      int (*point_xform) (floatp, floatp, const gs_matrix *, gs_point *))
313 {
314     int code;
315 
316     if ((code = (*point_xform) (pbox_in->p.x, pbox_in->p.y, pmat, &pts[0])) < 0 ||
317 	(code = (*point_xform) (pbox_in->p.x, pbox_in->q.y, pmat, &pts[1])) < 0 ||
318 	(code = (*point_xform) (pbox_in->q.x, pbox_in->p.y, pmat, &pts[2])) < 0 ||
319      (code = (*point_xform) (pbox_in->q.x, pbox_in->q.y, pmat, &pts[3])) < 0
320 	)
321 	DO_NOTHING;
322     return code;
323 }
324 
325 private int
bbox_transform_either(const gs_rect * pbox_in,const gs_matrix * pmat,gs_rect * pbox_out,int (* point_xform)(floatp,floatp,const gs_matrix *,gs_point *))326 bbox_transform_either(const gs_rect * pbox_in, const gs_matrix * pmat,
327 		      gs_rect * pbox_out,
328      int (*point_xform) (floatp, floatp, const gs_matrix *, gs_point *))
329 {
330     int code;
331 
332     /*
333      * In principle, we could transform only one point and two
334      * distance vectors; however, because of rounding, we will only
335      * get fully consistent results if we transform all 4 points.
336      * We must compute the max and min after transforming,
337      * since a rotation may be involved.
338      */
339     gs_point pts[4];
340 
341     if ((code = bbox_transform_either_only(pbox_in, pmat, pts, point_xform)) < 0)
342 	return code;
343     return gs_points_bbox(pts, pbox_out);
344 }
345 int
gs_bbox_transform(const gs_rect * pbox_in,const gs_matrix * pmat,gs_rect * pbox_out)346 gs_bbox_transform(const gs_rect * pbox_in, const gs_matrix * pmat,
347 		  gs_rect * pbox_out)
348 {
349     return bbox_transform_either(pbox_in, pmat, pbox_out,
350 				 gs_point_transform);
351 }
352 int
gs_bbox_transform_only(const gs_rect * pbox_in,const gs_matrix * pmat,gs_point points[4])353 gs_bbox_transform_only(const gs_rect * pbox_in, const gs_matrix * pmat,
354 		       gs_point points[4])
355 {
356     return bbox_transform_either_only(pbox_in, pmat, points,
357 				      gs_point_transform);
358 }
359 int
gs_bbox_transform_inverse(const gs_rect * pbox_in,const gs_matrix * pmat,gs_rect * pbox_out)360 gs_bbox_transform_inverse(const gs_rect * pbox_in, const gs_matrix * pmat,
361 			  gs_rect * pbox_out)
362 {
363     return bbox_transform_either(pbox_in, pmat, pbox_out,
364 				 gs_point_transform_inverse);
365 }
366 
367 /* ------ Coordinate transformations (to fixed point) ------ */
368 
369 #define f_fits_in_fixed(f) f_fits_in_bits(f, fixed_int_bits)
370 
371 /* Make a gs_matrix_fixed from a gs_matrix. */
372 int
gs_matrix_fixed_from_matrix(gs_matrix_fixed * pfmat,const gs_matrix * pmat)373 gs_matrix_fixed_from_matrix(gs_matrix_fixed *pfmat, const gs_matrix *pmat)
374 {
375     *(gs_matrix *)pfmat = *pmat;
376     if (f_fits_in_fixed(pmat->tx) && f_fits_in_fixed(pmat->ty)) {
377 	pfmat->tx = fixed2float(pfmat->tx_fixed = float2fixed(pmat->tx));
378 	pfmat->ty = fixed2float(pfmat->ty_fixed = float2fixed(pmat->ty));
379 	pfmat->txy_fixed_valid = true;
380     } else {
381 	pfmat->txy_fixed_valid = false;
382     }
383     return 0;
384 }
385 
386 /* Transform a point with a fixed-point result. */
387 int
gs_point_transform2fixed(const gs_matrix_fixed * pmat,floatp x,floatp y,gs_fixed_point * ppt)388 gs_point_transform2fixed(const gs_matrix_fixed * pmat,
389 			 floatp x, floatp y, gs_fixed_point * ppt)
390 {
391     fixed px, py, t;
392     double xtemp, ytemp;
393     int code;
394 
395     if (!pmat->txy_fixed_valid) {	/* The translation is out of range.  Do the */
396 	/* computation in floating point, and convert to */
397 	/* fixed at the end. */
398 	gs_point fpt;
399 
400 	gs_point_transform(x, y, (const gs_matrix *)pmat, &fpt);
401 	if (!(f_fits_in_fixed(fpt.x) && f_fits_in_fixed(fpt.y)))
402 	    return_error(gs_error_limitcheck);
403 	ppt->x = float2fixed(fpt.x);
404 	ppt->y = float2fixed(fpt.y);
405 	return 0;
406     }
407     if (!is_fzero(pmat->xy)) {	/* Hope for 90 degree rotation */
408 	if ((code = CHECK_DFMUL2FIXED_VARS(px, y, pmat->yx, xtemp)) < 0 ||
409 	    (code = CHECK_DFMUL2FIXED_VARS(py, x, pmat->xy, ytemp)) < 0
410 	    )
411 	    return code;
412 	FINISH_DFMUL2FIXED_VARS(px, xtemp);
413 	FINISH_DFMUL2FIXED_VARS(py, ytemp);
414 	if (!is_fzero(pmat->xx)) {
415 	    if ((code = CHECK_DFMUL2FIXED_VARS(t, x, pmat->xx, xtemp)) < 0)
416 		return code;
417 	    FINISH_DFMUL2FIXED_VARS(t, xtemp);
418 	    if ((code = CHECK_SET_FIXED_SUM(px, px, t)) < 0)
419 	        return code;
420 	}
421 	if (!is_fzero(pmat->yy)) {
422 	    if ((code = CHECK_DFMUL2FIXED_VARS(t, y, pmat->yy, ytemp)) < 0)
423 		return code;
424 	    FINISH_DFMUL2FIXED_VARS(t, ytemp);
425 	    if ((code = CHECK_SET_FIXED_SUM(py, py, t)) < 0)
426 	        return code;
427 	}
428     } else {
429 	if ((code = CHECK_DFMUL2FIXED_VARS(px, x, pmat->xx, xtemp)) < 0 ||
430 	    (code = CHECK_DFMUL2FIXED_VARS(py, y, pmat->yy, ytemp)) < 0
431 	    )
432 	    return code;
433 	FINISH_DFMUL2FIXED_VARS(px, xtemp);
434 	FINISH_DFMUL2FIXED_VARS(py, ytemp);
435 	if (!is_fzero(pmat->yx)) {
436 	    if ((code = CHECK_DFMUL2FIXED_VARS(t, y, pmat->yx, ytemp)) < 0)
437 		return code;
438 	    FINISH_DFMUL2FIXED_VARS(t, ytemp);
439 	    if ((code = CHECK_SET_FIXED_SUM(px, px, t)) < 0)
440 	        return code;
441 	}
442     }
443     if (((code = CHECK_SET_FIXED_SUM(ppt->x, px, pmat->tx_fixed)) < 0) ||
444         ((code = CHECK_SET_FIXED_SUM(ppt->y, py, pmat->ty_fixed)) < 0) )
445         return code;
446     return 0;
447 }
448 
449 #if PRECISE_CURRENTPOINT
450 /* Transform a point with a fixed-point result. */
451 /* Used for the best precision of the current point,
452    see comment in clamp_point_aux. */
453 int
gs_point_transform2fixed_rounding(const gs_matrix_fixed * pmat,floatp x,floatp y,gs_fixed_point * ppt)454 gs_point_transform2fixed_rounding(const gs_matrix_fixed * pmat,
455 			 floatp x, floatp y, gs_fixed_point * ppt)
456 {
457     gs_point fpt;
458 
459     gs_point_transform(x, y, (const gs_matrix *)pmat, &fpt);
460     if (!(f_fits_in_fixed(fpt.x) && f_fits_in_fixed(fpt.y)))
461 	return_error(gs_error_limitcheck);
462     ppt->x = float2fixed_rounded(fpt.x);
463     ppt->y = float2fixed_rounded(fpt.y);
464     return 0;
465 }
466 #endif
467 
468 /* Transform a distance with a fixed-point result. */
469 int
gs_distance_transform2fixed(const gs_matrix_fixed * pmat,floatp dx,floatp dy,gs_fixed_point * ppt)470 gs_distance_transform2fixed(const gs_matrix_fixed * pmat,
471 			    floatp dx, floatp dy, gs_fixed_point * ppt)
472 {
473     fixed px, py, t;
474     double xtemp, ytemp;
475     int code;
476 
477     if ((code = CHECK_DFMUL2FIXED_VARS(px, dx, pmat->xx, xtemp)) < 0 ||
478 	(code = CHECK_DFMUL2FIXED_VARS(py, dy, pmat->yy, ytemp)) < 0
479 	)
480 	return code;
481     FINISH_DFMUL2FIXED_VARS(px, xtemp);
482     FINISH_DFMUL2FIXED_VARS(py, ytemp);
483     if (!is_fzero(pmat->yx)) {
484 	if ((code = CHECK_DFMUL2FIXED_VARS(t, dy, pmat->yx, ytemp)) < 0)
485 	    return code;
486 	FINISH_DFMUL2FIXED_VARS(t, ytemp);
487 	if ((code = CHECK_SET_FIXED_SUM(px, px, t)) < 0)
488 	    return code;
489     }
490     if (!is_fzero(pmat->xy)) {
491 	if ((code = CHECK_DFMUL2FIXED_VARS(t, dx, pmat->xy, xtemp)) < 0)
492 	    return code;
493 	FINISH_DFMUL2FIXED_VARS(t, xtemp);
494 	if ((code = CHECK_SET_FIXED_SUM(py, py, t)) < 0)
495 	    return code;
496     }
497     ppt->x = px;
498     ppt->y = py;
499     return 0;
500 }
501 
502 /* ------ Serialization ------ */
503 
504 /*
505  * For maximum conciseness in band lists, we write a matrix as a control
506  * byte followed by 0 to 6 values.  The control byte has the format
507  * AABBCD00.  AA and BB control (xx,yy) and (xy,yx) as follows:
508  *	00 = values are (0.0, 0.0)
509  *	01 = values are (V, V) [1 value follows]
510  *	10 = values are (V, -V) [1 value follows]
511  *	11 = values are (U, V) [2 values follow]
512  * C and D control tx and ty as follows:
513  *	0 = value is 0.0
514  *	1 = value follows
515  * The following code is the only place that knows this representation.
516  */
517 
518 /* Put a matrix on a stream. */
519 int
sput_matrix(stream * s,const gs_matrix * pmat)520 sput_matrix(stream *s, const gs_matrix *pmat)
521 {
522     byte buf[1 + 6 * sizeof(float)];
523     byte *cp = buf + 1;
524     byte b = 0;
525     float coeff[6];
526     int i;
527     uint ignore;
528 
529     coeff[0] = pmat->xx;
530     coeff[1] = pmat->xy;
531     coeff[2] = pmat->yx;
532     coeff[3] = pmat->yy;
533     coeff[4] = pmat->tx;
534     coeff[5] = pmat->ty;
535     for (i = 0; i < 4; i += 2) {
536 	float u = coeff[i], v = coeff[i ^ 3];
537 
538 	b <<= 2;
539 	if (u != 0 || v != 0) {
540 	    memcpy(cp, &u, sizeof(float));
541 	    cp += sizeof(float);
542 
543 	    if (v == u)
544 		b += 1;
545 	    else if (v == -u)
546 		b += 2;
547 	    else {
548 		b += 3;
549 		memcpy(cp, &v, sizeof(float));
550 		cp += sizeof(float);
551 	    }
552 	}
553     }
554     for (; i < 6; ++i) {
555 	float v = coeff[i];
556 
557 	b <<= 1;
558 	if (v != 0) {
559 	    ++b;
560 	    memcpy(cp, &v, sizeof(float));
561 	    cp += sizeof(float);
562 	}
563     }
564     buf[0] = b << 2;
565     return sputs(s, buf, cp - buf, &ignore);
566 }
567 
568 /* Get a matrix from a stream. */
569 int
sget_matrix(stream * s,gs_matrix * pmat)570 sget_matrix(stream *s, gs_matrix *pmat)
571 {
572     int b = sgetc(s);
573     float coeff[6];
574     int i;
575     int status;
576     uint nread;
577 
578     if (b < 0)
579 	return b;
580     for (i = 0; i < 4; i += 2, b <<= 2)
581 	if (!(b & 0xc0))
582 	    coeff[i] = coeff[i ^ 3] = 0.0;
583 	else {
584 	    float value;
585 
586 	    status = sgets(s, (byte *)&value, sizeof(value), &nread);
587 	    if (status < 0 && status != EOFC)
588 		return_error(gs_error_ioerror);
589 	    coeff[i] = value;
590 	    switch ((b >> 6) & 3) {
591 		case 1:
592 		    coeff[i ^ 3] = value;
593 		    break;
594 		case 2:
595 		    coeff[i ^ 3] = -value;
596 		    break;
597 		case 3:
598 		    status = sgets(s, (byte *)&coeff[i ^ 3],
599 				   sizeof(coeff[0]), &nread);
600 		    if (status < 0 && status != EOFC)
601 			return_error(gs_error_ioerror);
602 	    }
603 	}
604     for (; i < 6; ++i, b <<= 1)
605 	if (b & 0x80) {
606 	    status = sgets(s, (byte *)&coeff[i], sizeof(coeff[0]), &nread);
607 	    if (status < 0 && status != EOFC)
608 		return_error(gs_error_ioerror);
609 	} else
610 	    coeff[i] = 0.0;
611     pmat->xx = coeff[0];
612     pmat->xy = coeff[1];
613     pmat->yx = coeff[2];
614     pmat->yy = coeff[3];
615     pmat->tx = coeff[4];
616     pmat->ty = coeff[5];
617     return 0;
618 }
619