xref: /plan9/sys/src/cmd/gs/src/gdevplan9.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
1 /*
2  *		Copyright (c) 1998 by Lucent Technologies.
3  * Permission to use, copy, modify, and distribute this software for any
4  * purpose without fee is hereby granted, provided that this entire notice
5  * is included in all copies of any software which is or includes a copy
6  * or modification of this software.
7  *
8  * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
9  * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
10  * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
11  * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
12  */
13 
14 /*
15  * gdevplan9.c: gs device to generate plan9 bitmaps
16  * Russ Cox <rsc@plan9.bell-labs.com>, 3/25/98 (gdevifno)
17  * Updated to fit in the standard GS distribution, 5/14/98
18  * Added support for true-color bitmaps, 6/7/02
19  */
20 
21 #include "gdevprn.h"
22 #include "gsparam.h"
23 #include "gxlum.h"
24 #include <stdlib.h>
25 #undef printf
26 
27 #define nil ((void*)0)
28 enum {
29 	ERROR = -2
30 };
31 
32 typedef struct WImage WImage;
33 typedef struct Rectangle Rectangle;
34 typedef struct Point Point;
35 
36 struct Point {
37 	int x;
38 	int y;
39 };
40 
41 struct Rectangle {
42 	Point min;
43 	Point max;
44 };
45 private Point ZP = { 0, 0 };
46 
47 private WImage* initwriteimage(FILE *f, Rectangle r, char*, int depth);
48 private int writeimageblock(WImage *w, uchar *data, int ndata);
49 private int bytesperline(Rectangle, int);
50 private int rgb2cmap(int, int, int);
51 private long cmap2rgb(int);
52 
53 #define X_DPI	100
54 #define Y_DPI	100
55 
56 private dev_proc_map_rgb_color(plan9_rgb2cmap);
57 private dev_proc_map_color_rgb(plan9_cmap2rgb);
58 private dev_proc_open_device(plan9_open);
59 private dev_proc_close_device(plan9_close);
60 private dev_proc_print_page(plan9_print_page);
61 private dev_proc_put_params(plan9_put_params);
62 private dev_proc_get_params(plan9_get_params);
63 
64 typedef struct plan9_device_s {
65 	gx_device_common;
66 	gx_prn_device_common;
67 	int dither;
68 
69 	int ldepth;
70 	int lastldepth;
71 	int cmapcall;
72 } plan9_device;
73 
74 enum {
75 	Nbits = 8,
76 	Bitmask = (1<<Nbits)-1,
77 };
78 
79 private const gx_device_procs plan9_procs =
80 	prn_color_params_procs(plan9_open, gdev_prn_output_page, gdev_prn_close,
81 		plan9_rgb2cmap, plan9_cmap2rgb,
82 		gdev_prn_get_params, gdev_prn_put_params);
83 /*
84 		plan9_get_params, plan9_put_params);
85 */
86 
87 
88 plan9_device far_data gs_plan9_device =
89 { prn_device_body(plan9_device, plan9_procs, "plan9",
90 	DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
91 	X_DPI, Y_DPI,
92 	0,0,0,0,	/* margins */
93 	3,		/* 3 = RGB, 1 = gray, 4 = CMYK */
94 	Nbits*3,		/* # of bits per pixel */
95 	(1<<Nbits)-1,		/* # of distinct gray levels. */
96 	(1<<Nbits)-1,		/* # of distinct color levels. */
97 	1<<Nbits,		/* dither gray ramp size.  used in alpha? */
98 	1<<Nbits,    	/* dither color ramp size.  used in alpha? */
99 	plan9_print_page),
100 	1,
101 };
102 
103 /*
104  * ghostscript asks us how to convert between
105  * rgb and color map entries
106  */
107 private gx_color_index
108 plan9_rgb2cmap(P4(gx_device *dev, gx_color_value r,
109   gx_color_value g, gx_color_value b)) {
110 	int shift;
111 	plan9_device *idev;
112 	ulong red, green, blue;
113 
114 	idev = (plan9_device*) dev;
115 
116 	shift = gx_color_value_bits - Nbits;
117 	red = r >> shift;
118 	green = g >> shift;
119 	blue = b >> shift;
120 
121 	/*
122 	 * we keep track of what ldepth bitmap this is by watching
123 	 * what colors gs asks for.
124 	 *
125 	 * one catch: sometimes print_page gets called more than one
126 	 * per page (for multiple copies) without cmap calls inbetween.
127 	 * if idev->cmapcall is 0 when print_page gets called, it uses
128 	 * the ldepth of the last page.
129 	 */
130 	if(red == green && green == blue) {
131 		if(red == 0 || red == Bitmask)
132 			;
133 		else if(red == Bitmask/3 || red == 2*Bitmask/3) {
134 			if(idev->ldepth < 1)
135 				idev->ldepth = 1;
136 		} else {
137 			if(idev->ldepth < 2)
138 				idev->ldepth = 2;
139 		}
140 	} else
141 		idev->ldepth = 3;
142 
143 	idev->cmapcall = 1;
144 	return (blue << (2*Nbits)) | (green << Nbits) | red;
145 }
146 
147 private int
148 plan9_cmap2rgb(P3(gx_device *dev, gx_color_index color,
149   gx_color_value rgb[3])) {
150 	int shift, i;
151 	plan9_device *idev;
152 
153 	if((ulong)color > 0xFFFFFF)
154 		return_error(gs_error_rangecheck);
155 
156 	idev = (plan9_device*) dev;
157 	shift = gx_color_value_bits - Nbits;
158 
159 	rgb[2] = ((color >> (2*Nbits)) & Bitmask) << shift;
160 	rgb[1] = ((color >> Nbits) & Bitmask) << shift;
161 	rgb[0] = (color & Bitmask) << shift;
162 
163 	return 0;
164 }
165 
166 private int
167 plan9_put_param_int(gs_param_list *plist, gs_param_name pname, int *pv,
168 	int minval, int maxval, int ecode)
169 {
170 	int code, value;
171 	switch(code = param_read_int(plist, pname, &value)) {
172 	default:
173 		return code;
174 
175 	case 1:
176 		return ecode;
177 
178 	case 0:
179 		if(value < minval || value > maxval)
180 			param_signal_error(plist, pname, gs_error_rangecheck);
181 		*pv = value;
182 		return (ecode < 0 ? ecode : 1);
183 	}
184 }
185 
186 private int
187 plan9_get_params(gx_device *pdev, gs_param_list *plist)
188 {
189 	int code;
190 	plan9_device *idev;
191 
192 	idev = (plan9_device*) pdev;
193 //	printf("plan9_get_params dither %d\n", idev->dither);
194 
195 	if((code = gdev_prn_get_params(pdev, plist)) < 0
196 	 || (code = param_write_int(plist, "Dither", &idev->dither)) < 0)
197 		return code;
198 	printf("getparams: dither=%d\n", idev->dither);
199 	return code;
200 }
201 
202 private int
203 plan9_put_params(gx_device * pdev, gs_param_list * plist)
204 {
205 	int code;
206 	int dither;
207 	plan9_device *idev;
208 
209 	printf("plan9_put_params\n");
210 
211 	idev = (plan9_device*)pdev;
212 	dither = idev->dither;
213 
214 	code = plan9_put_param_int(plist, "Dither", &dither, 0, 1, 0);
215 	if(code < 0)
216 		return code;
217 
218 	idev->dither = dither;
219 	return 0;
220 }
221 /*
222  * plan9_open() is supposed to initialize the device.
223  * there's not much to do.
224  */
225 extern void init_p9color();	/* in gdevifno.c */
226 private int
227 plan9_open(P1(gx_device *dev))
228 {
229 	int code;
230 	plan9_device *idev;
231 
232 	idev = (plan9_device*) dev;
233 	idev->cmapcall = 0;
234 	idev->ldepth = 0;
235 
236 //	printf("plan9_open gs_plan9_device.dither = %d idev->dither = %d\n",
237 //		gs_plan9_device.dither, idev->dither);
238 	setbuf(stderr, 0);
239 	init_p9color();
240 
241 	return gdev_prn_open(dev);
242 }
243 
244 /*
245  * plan9_print_page() is called once for each page
246  * (actually once for each copy of each page, but we won't
247  * worry about that).
248  */
249 private int
250 plan9_print_page(P2(gx_device_printer *pdev, FILE *f))
251 {
252 	char *chanstr;
253 	uchar *buf;	/* [8192*3*8/Nbits] BUG: malloc this */
254 	uchar *p;
255 	WImage *w;
256 	int bpl, y;
257 	int x, xmod;
258 	int ldepth;
259 	int ppb[] = {8, 4, 2, 1};	/* pixels per byte */
260 	int bpp[] = {1, 2, 4, 8};	/* bits per pixel */
261 	int gsbpl;
262 	int dither;
263 	int depth;
264 	ulong u;
265 	ushort us;
266 	Rectangle rect;
267 	plan9_device *idev;
268 	uchar *r;
269 
270 	setbuf(stderr, 0);
271 	gsbpl = gdev_prn_raster(pdev);
272 	buf = gs_malloc(gsbpl, 1, "plan9_print_page");
273 
274 	if(buf == nil) {
275 		fprintf(stderr, "out of memory\n");
276 		return_error(gs_error_Fatal);
277 	}
278 
279 	idev = (plan9_device *) pdev;
280 	if(idev->cmapcall) {
281 		idev->lastldepth = idev->ldepth;
282 		idev->ldepth = 0;
283 		idev->cmapcall = 0;
284 	}
285 	ldepth = idev->lastldepth;
286 	dither = idev->dither;
287 
288 	if(pdev->color_info.anti_alias.graphics_bits || pdev->color_info.anti_alias.text_bits)
289 		if(ldepth < 2)
290 			ldepth = 2;
291 
292 	chanstr = nil;
293 	depth = 0;
294 	switch(ldepth){
295 	case 0:
296 		chanstr = "k1";
297 		depth = 1;
298 		break;
299 	case 1:
300 		return_error(gs_error_Fatal);
301 	case 2:
302 		chanstr = "k4";
303 		depth = 4;
304 		break;
305 	case 3:
306 		chanstr = "r8g8b8";
307 		depth = 24;
308 		break;
309 	}
310 
311 //	printf("plan9_print_page dither %d ldepth %d idither %d\n", dither, ldepth, gs_plan9_device.dither);
312 	rect.min = ZP;
313 	rect.max.x = pdev->width;
314 	rect.max.y = pdev->height;
315 	bpl = bytesperline(rect, depth);
316 	w = initwriteimage(f, rect, chanstr, depth);
317 	if(w == nil) {
318 		fprintf(stderr, "initwriteimage failed\n");
319 		return_error(gs_error_Fatal);
320 	}
321 
322 	/*
323 	 * i wonder if it is faster to put the switch around the for loops
324 	 * to save all the ldepth lookups.
325 	 */
326 	for(y=0; y<pdev->height; y++) {
327 		gdev_prn_get_bits(pdev, y, buf, &p);
328 		r = p+2;
329 		switch(depth){
330 		default:
331 			return_error(gs_error_Fatal);
332 		case 1:
333 			for(x=0; x<pdev->width; x++){
334 				if((x%8) == 0)
335 					p[x/8] = (*r>>4)&1;
336 				else
337 					p[x/8] = (p[x/8]<<1) | (*r>>4)&1;
338 				r += 3;
339 			}
340 			break;
341 		case 4:
342 			for(x=0; x<pdev->width; x++){
343 				if((x%2) == 0)
344 					p[x/2] = (*r>>4) & 0xF;
345 				else
346 					p[x/2] = (p[x/2]<<4) | ((*r>>4)&0xF);
347 				r += 3;
348 			}
349 			break;
350 		case 24:
351 			break;
352 		}
353 
354 		/* pad last byte over if we didn't fill it */
355 		xmod = pdev->width % ppb[ldepth];
356 		if(xmod && ldepth<3)
357 			p[(x-1)/ppb[ldepth]] <<= ((ppb[ldepth]-xmod)*bpp[ldepth]);
358 
359 		if(writeimageblock(w, p, bpl) == ERROR) {
360 			gs_free(buf, gsbpl, 1, "plan9_print_page");
361 			return_error(gs_error_Fatal);
362 		}
363 	}
364 	if(writeimageblock(w, nil, 0) == ERROR) {
365 		gs_free(buf, gsbpl, 1, "plan9_print_page");
366 		return_error(gs_error_Fatal);
367 	}
368 
369 	gs_free(buf, gsbpl, 1, "plan9_print_page");
370 	return 0;
371 }
372 
373 /*
374  * this is a modified version of the image compressor
375  * from fb/bit2enc.  it is modified only in that it
376  * now compiles as part of gs.
377  */
378 
379 /*
380  * Compressed image file parameters
381  */
382 #define	NMATCH	3		/* shortest match possible */
383 #define	NRUN	(NMATCH+31)	/* longest match possible */
384 #define	NMEM	1024		/* window size */
385 #define	NDUMP	128		/* maximum length of dump */
386 #define	NCBLOCK	6000		/* size of compressed blocks */
387 
388 #define	HSHIFT	3	/* HSHIFT==5 runs slightly faster, but hash table is 64x bigger */
389 #define	NHASH	(1<<(HSHIFT*NMATCH))
390 #define	HMASK	(NHASH-1)
391 #define	hupdate(h, c)	((((h)<<HSHIFT)^(c))&HMASK)
392 
393 typedef struct Dump	Dump;
394 typedef struct Hlist Hlist;
395 
396 struct Hlist{
397 	ulong p;
398 	Hlist *next, *prev;
399 };
400 
401 struct Dump {
402 	int ndump;
403 	uchar *dumpbuf;
404 	uchar buf[1+NDUMP];
405 };
406 
407 struct WImage {
408 	FILE *f;
409 
410 	/* image attributes */
411 	Rectangle origr, r;
412 	int bpl;
413 
414 	/* output buffer */
415 	uchar outbuf[NCBLOCK], *outp, *eout, *loutp;
416 
417 	/* sliding input window */
418 	/*
419 	 * ibase is the pointer to where the beginning of
420 	 * the input "is" in memory.  whenever we "slide" the
421 	 * buffer N bytes, what we are actually doing is
422 	 * decrementing ibase by N.
423 	 * the ulongs in the Hlist structures are just
424 	 * pointers relative to ibase.
425 	 */
426 	uchar *inbuf;	/* inbuf should be at least NMEM+NRUN+NMATCH long */
427 	uchar *ibase;
428 	int minbuf;	/* size of inbuf (malloc'ed bytes) */
429 	int ninbuf;	/* size of inbuf (filled bytes) */
430 	ulong line;	/* the beginning of the line we are currently encoding,
431 			 * relative to inbuf (NOT relative to ibase) */
432 
433 	/* raw dump buffer */
434 	Dump dump;
435 
436 	/* hash tables */
437 	Hlist hash[NHASH];
438 	Hlist chain[NMEM], *cp;
439 	int h;
440 	int needhash;
441 };
442 
443 private void
444 zerohash(WImage *w)
445 {
446 	memset(w->hash, 0, sizeof(w->hash));
447 	memset(w->chain, 0, sizeof(w->chain));
448 	w->cp=w->chain;
449 	w->needhash = 1;
450 }
451 
452 private int
453 addbuf(WImage *w, uchar *buf, int nbuf)
454 {
455 	int n;
456 	if(buf == nil || w->outp+nbuf > w->eout) {
457 		if(w->loutp==w->outbuf){	/* can't really happen -- we checked line length above */
458 			fprintf(stderr, "buffer too small for line\n");
459 			return ERROR;
460 		}
461 		n=w->loutp-w->outbuf;
462 		fprintf(w->f, "%11d %11d ", w->r.max.y, n);
463 		fwrite(w->outbuf, 1, n, w->f);
464 		w->r.min.y=w->r.max.y;
465 		w->outp=w->outbuf;
466 		w->loutp=w->outbuf;
467 		zerohash(w);
468 		return -1;
469 	}
470 
471 	memmove(w->outp, buf, nbuf);
472 	w->outp += nbuf;
473 	return nbuf;
474 }
475 
476 /* return 0 on success, -1 if buffer is full */
477 private int
478 flushdump(WImage *w)
479 {
480 	int n = w->dump.ndump;
481 
482 	if(n == 0)
483 		return 0;
484 
485 	w->dump.buf[0] = 0x80|(n-1);
486 	if((n=addbuf(w, w->dump.buf, n+1)) == ERROR)
487 		return ERROR;
488 	if(n < 0)
489 		return -1;
490 	w->dump.ndump = 0;
491 	return 0;
492 }
493 
494 private void
495 updatehash(WImage *w, uchar *p, uchar *ep)
496 {
497 	uchar *q;
498 	Hlist *cp;
499 	Hlist *hash;
500 	int h;
501 
502 	hash = w->hash;
503 	cp = w->cp;
504 	h = w->h;
505 	for(q=p; q<ep; q++) {
506 		if(cp->prev)
507 			cp->prev->next = cp->next;
508 		cp->next = hash[h].next;
509 		cp->prev = &hash[h];
510 		cp->prev->next = cp;
511 		if(cp->next)
512 			cp->next->prev = cp;
513 		cp->p = q - w->ibase;
514 		if(++cp == w->chain+NMEM)
515 			cp = w->chain;
516 		if(&q[NMATCH] < &w->inbuf[w->ninbuf])
517 			h = hupdate(h, q[NMATCH]);
518 	}
519 	w->cp = cp;
520 	w->h = h;
521 }
522 
523 /*
524  * attempt to process a line of input,
525  * returning the number of bytes actually processed.
526  *
527  * if the output buffer needs to be flushed, we flush
528  * the buffer and return 0.
529  * otherwise we return bpl
530  */
531 private int
532 gobbleline(WImage *w)
533 {
534 	int runlen, n, offs;
535 	uchar *eline, *es, *best, *p, *s, *t;
536 	Hlist *hp;
537 	uchar buf[2];
538 	int rv;
539 
540 	if(w->needhash) {
541 		w->h = 0;
542 		for(n=0; n!=NMATCH; n++)
543 			w->h = hupdate(w->h, w->inbuf[w->line+n]);
544 		w->needhash = 0;
545 	}
546 	w->dump.ndump=0;
547 	eline=w->inbuf+w->line+w->bpl;
548 	for(p=w->inbuf+w->line;p!=eline;){
549 		es = (eline < p+NRUN) ? eline : p+NRUN;
550 
551 		best=nil;
552 		runlen=0;
553 		/* hash table lookup */
554 		for(hp=w->hash[w->h].next;hp;hp=hp->next){
555 			/*
556 			 * the next block is an optimization of
557 			 * for(s=p, t=w->ibase+hp->p; s<es && *s == *t; s++, t++)
558 			 * 	;
559 			 */
560 
561 			{	uchar *ss, *tt;
562 				s = p+runlen;
563 				t = w->ibase+hp->p+runlen;
564 				for(ss=s, tt=t; ss>=p && *ss == *tt; ss--, tt--)
565 					;
566 				if(ss < p)
567 					while(s<es && *s == *t)
568 						s++, t++;
569 			}
570 
571 			n = s-p;
572 
573 			if(n > runlen) {
574 				runlen = n;
575 				best = w->ibase+hp->p;
576 				if(p+runlen == es)
577 					break;
578 			}
579 		}
580 
581 		/*
582 		 * if we didn't find a long enough run, append to
583 		 * the raw dump buffer
584 		 */
585 		if(runlen<NMATCH){
586 			if(w->dump.ndump==NDUMP) {
587 				if((rv = flushdump(w)) == ERROR)
588 					return ERROR;
589 				if(rv < 0)
590 					return 0;
591 			}
592 			w->dump.dumpbuf[w->dump.ndump++]=*p;
593 			runlen=1;
594 		}else{
595 		/*
596 		 * otherwise, assuming the dump buffer is empty,
597 		 * add the compressed rep.
598 		 */
599 			if((rv = flushdump(w)) == ERROR)
600 				return ERROR;
601 			if(rv < 0)
602 				return 0;
603 			offs=p-best-1;
604 			buf[0] = ((runlen-NMATCH)<<2)|(offs>>8);
605 			buf[1] = offs&0xff;
606 			if(addbuf(w, buf, 2) < 0)
607 				return 0;
608 		}
609 
610 		/*
611 		 * add to hash tables what we just encoded
612 		 */
613 		updatehash(w, p, p+runlen);
614 		p += runlen;
615 	}
616 
617 	if((rv = flushdump(w)) == ERROR)
618 		return ERROR;
619 	if(rv < 0)
620 		return 0;
621 	w->line += w->bpl;
622 	w->loutp=w->outp;
623 	w->r.max.y++;
624 	return w->bpl;
625 }
626 
627 private uchar*
628 shiftwindow(WImage *w, uchar *data, uchar *edata)
629 {
630 	int n, m;
631 
632 	/* shift window over */
633 	if(w->line > NMEM) {
634 		n = w->line-NMEM;
635 		memmove(w->inbuf, w->inbuf+n, w->ninbuf-n);
636 		w->line -= n;
637 		w->ibase -= n;
638 		w->ninbuf -= n;
639 	}
640 
641 	/* fill right with data if available */
642 	if(w->minbuf > w->ninbuf && edata > data) {
643 		m = w->minbuf - w->ninbuf;
644 		if(edata-data < m)
645 			m = edata-data;
646 		memmove(w->inbuf+w->ninbuf, data, m);
647 		data += m;
648 		w->ninbuf += m;
649 	}
650 
651 	return data;
652 }
653 
654 private WImage*
655 initwriteimage(FILE *f, Rectangle r, char *chanstr, int depth)
656 {
657 	WImage *w;
658 	int n, bpl;
659 
660 	bpl = bytesperline(r, depth);
661 	if(r.max.y <= r.min.y || r.max.x <= r.min.x || bpl <= 0) {
662 		fprintf(stderr, "bad rectangle, ldepth");
663 		return nil;
664 	}
665 
666 	n = NMEM+NMATCH+NRUN+bpl*2;
667 	w = malloc(n+sizeof(*w));
668 	if(w == nil)
669 		return nil;
670 	w->inbuf = (uchar*) &w[1];
671 	w->ibase = w->inbuf;
672 	w->line = 0;
673 	w->minbuf = n;
674 	w->ninbuf = 0;
675 	w->origr = r;
676 	w->r = r;
677 	w->r.max.y = w->r.min.y;
678 	w->eout = w->outbuf+sizeof(w->outbuf);
679 	w->outp = w->loutp = w->outbuf;
680 	w->bpl = bpl;
681 	w->f = f;
682 	w->dump.dumpbuf = w->dump.buf+1;
683 	w->dump.ndump = 0;
684 	zerohash(w);
685 
686 	fprintf(f, "compressed\n%11s %11d %11d %11d %11d ",
687 		chanstr, r.min.x, r.min.y, r.max.x, r.max.y);
688 	return w;
689 }
690 
691 private int
692 writeimageblock(WImage *w, uchar *data, int ndata)
693 {
694 	uchar *edata;
695 
696 	if(data == nil) {	/* end of data, flush everything */
697 		while(w->line < w->ninbuf)
698 			if(gobbleline(w) == ERROR)
699 				return ERROR;
700 		addbuf(w, nil, 0);
701 		if(w->r.min.y != w->origr.max.y) {
702 			fprintf(stderr, "not enough data supplied to writeimage\n");
703 		}
704 		free(w);
705 		return 0;
706 	}
707 
708 	edata = data+ndata;
709 	data = shiftwindow(w, data, edata);
710 	while(w->ninbuf >= w->line+w->bpl+NMATCH) {
711 		if(gobbleline(w) == ERROR)
712 			return ERROR;
713 		data = shiftwindow(w, data, edata);
714 	}
715 	if(data != edata) {
716 		fprintf(w->f, "data != edata.  uh oh\n");
717 		return ERROR; /* can't happen */
718 	}
719 	return 0;
720 }
721 
722 /*
723  * functions from the Plan9/Brazil drawing libraries
724  */
725 static
726 int
727 unitsperline(Rectangle r, int d, int bitsperunit)
728 {
729 	ulong l, t;
730 
731 	if(d <= 0 || d > 32)	/* being called wrong.  d is image depth. */
732 		abort();
733 
734 	if(r.min.x >= 0){
735 		l = (r.max.x*d+bitsperunit-1)/bitsperunit;
736 		l -= (r.min.x*d)/bitsperunit;
737 	}else{			/* make positive before divide */
738 		t = (-r.min.x*d+bitsperunit-1)/bitsperunit;
739 		l = t+(r.max.x*d+bitsperunit-1)/bitsperunit;
740 	}
741 	return l;
742 }
743 
744 int
745 wordsperline(Rectangle r, int d)
746 {
747 	return unitsperline(r, d, 8*sizeof(ulong));
748 }
749 
750 int
751 bytesperline(Rectangle r, int d)
752 {
753 	return unitsperline(r, d, 8);
754 }
755