Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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