xref: /plan9/sys/src/cmd/unix/drawterm/libmemdraw/line.c (revision 8ccd4a6360d974db7bd7bbd4f37e7018419ea908)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <memdraw.h>
5 #include <memlayer.h>
6 
7 enum
8 {
9 	Arrow1 = 8,
10 	Arrow2 = 10,
11 	Arrow3 = 3,
12 };
13 
14 #ifdef NOT
15 static
16 int
lmin(int a,int b)17 lmin(int a, int b)
18 {
19 	if(a < b)
20 		return a;
21 	return b;
22 }
23 #endif
24 
25 static
26 int
lmax(int a,int b)27 lmax(int a, int b)
28 {
29 	if(a > b)
30 		return a;
31 	return b;
32 }
33 
34 #ifdef NOTUSED
35 /*
36  * Rather than line clip, we run the Bresenham loop over the full line,
37  * and clip on each pixel.  This is more expensive but means that
38  * lines look the same regardless of how the windowing has tiled them.
39  * For speed, we check for clipping outside the loop and make the
40  * test easy when possible.
41  */
42 
43 static
44 void
horline1(Memimage * dst,Point p0,Point p1,int srcval,Rectangle clipr)45 horline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
46 {
47 	int x, y, dy, deltay, deltax, maxx;
48 	int dd, easy, e, bpp, m, m0;
49 	uchar *d;
50 
51 	deltax = p1.x - p0.x;
52 	deltay = p1.y - p0.y;
53 	dd = dst->width*sizeof(ulong);
54 	dy = 1;
55 	if(deltay < 0){
56 		dd = -dd;
57 		deltay = -deltay;
58 		dy = -1;
59 	}
60 	maxx = lmin(p1.x, clipr.max.x-1);
61 	bpp = dst->depth;
62 	m0 = 0xFF^(0xFF>>bpp);
63 	m = m0 >> (p0.x&(7/dst->depth))*bpp;
64 	easy = ptinrect(p0, clipr) && ptinrect(p1, clipr);
65 	e = 2*deltay - deltax;
66 	y = p0.y;
67 	d = byteaddr(dst, p0);
68 	deltay *= 2;
69 	deltax = deltay - 2*deltax;
70 	for(x=p0.x; x<=maxx; x++){
71 		if(easy || (clipr.min.x<=x && clipr.min.y<=y && y<clipr.max.y))
72 			*d ^= (*d^srcval) & m;
73 		if(e > 0){
74 			y += dy;
75 			d += dd;
76 			e += deltax;
77 		}else
78 			e += deltay;
79 		d++;
80 		m >>= bpp;
81 		if(m == 0)
82 			m = m0;
83 	}
84 }
85 
86 static
87 void
verline1(Memimage * dst,Point p0,Point p1,int srcval,Rectangle clipr)88 verline1(Memimage *dst, Point p0, Point p1, int srcval, Rectangle clipr)
89 {
90 	int x, y, deltay, deltax, maxy;
91 	int easy, e, bpp, m, m0, dd;
92 	uchar *d;
93 
94 	deltax = p1.x - p0.x;
95 	deltay = p1.y - p0.y;
96 	dd = 1;
97 	if(deltax < 0){
98 		dd = -1;
99 		deltax = -deltax;
100 	}
101 	maxy = lmin(p1.y, clipr.max.y-1);
102 	bpp = dst->depth;
103 	m0 = 0xFF^(0xFF>>bpp);
104 	m = m0 >> (p0.x&(7/dst->depth))*bpp;
105 	easy = ptinrect(p0, clipr) && ptinrect(p1, clipr);
106 	e = 2*deltax - deltay;
107 	x = p0.x;
108 	d = byteaddr(dst, p0);
109 	deltax *= 2;
110 	deltay = deltax - 2*deltay;
111 	for(y=p0.y; y<=maxy; y++){
112 		if(easy || (clipr.min.y<=y && clipr.min.x<=x && x<clipr.max.x))
113 			*d ^= (*d^srcval) & m;
114 		if(e > 0){
115 			x += dd;
116 			d += dd;
117 			e += deltay;
118 		}else
119 			e += deltax;
120 		d += dst->width*sizeof(ulong);
121 		m >>= bpp;
122 		if(m == 0)
123 			m = m0;
124 	}
125 }
126 
127 static
128 void
horliner(Memimage * dst,Point p0,Point p1,Memimage * src,Point dsrc,Rectangle clipr)129 horliner(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
130 {
131 	int x, y, sx, sy, deltay, deltax, minx, maxx;
132 	int bpp, m, m0;
133 	uchar *d, *s;
134 
135 	deltax = p1.x - p0.x;
136 	deltay = p1.y - p0.y;
137 	sx = drawreplxy(src->r.min.x, src->r.max.x, p0.x+dsrc.x);
138 	minx = lmax(p0.x, clipr.min.x);
139 	maxx = lmin(p1.x, clipr.max.x-1);
140 	bpp = dst->depth;
141 	m0 = 0xFF^(0xFF>>bpp);
142 	m = m0 >> (minx&(7/dst->depth))*bpp;
143 	for(x=minx; x<=maxx; x++){
144 		y = p0.y + (deltay*(x-p0.x)+deltax/2)/deltax;
145 		if(clipr.min.y<=y && y<clipr.max.y){
146 			d = byteaddr(dst, Pt(x, y));
147 			sy = drawreplxy(src->r.min.y, src->r.max.y, y+dsrc.y);
148 			s = byteaddr(src, Pt(sx, sy));
149 			*d ^= (*d^*s) & m;
150 		}
151 		if(++sx >= src->r.max.x)
152 			sx = src->r.min.x;
153 		m >>= bpp;
154 		if(m == 0)
155 			m = m0;
156 	}
157 }
158 
159 static
160 void
verliner(Memimage * dst,Point p0,Point p1,Memimage * src,Point dsrc,Rectangle clipr)161 verliner(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
162 {
163 	int x, y, sx, sy, deltay, deltax, miny, maxy;
164 	int bpp, m, m0;
165 	uchar *d, *s;
166 
167 	deltax = p1.x - p0.x;
168 	deltay = p1.y - p0.y;
169 	sy = drawreplxy(src->r.min.y, src->r.max.y, p0.y+dsrc.y);
170 	miny = lmax(p0.y, clipr.min.y);
171 	maxy = lmin(p1.y, clipr.max.y-1);
172 	bpp = dst->depth;
173 	m0 = 0xFF^(0xFF>>bpp);
174 	for(y=miny; y<=maxy; y++){
175 		if(deltay == 0)	/* degenerate line */
176 			x = p0.x;
177 		else
178 			x = p0.x + (deltax*(y-p0.y)+deltay/2)/deltay;
179 		if(clipr.min.x<=x && x<clipr.max.x){
180 			m = m0 >> (x&(7/dst->depth))*bpp;
181 			d = byteaddr(dst, Pt(x, y));
182 			sx = drawreplxy(src->r.min.x, src->r.max.x, x+dsrc.x);
183 			s = byteaddr(src, Pt(sx, sy));
184 			*d ^= (*d^*s) & m;
185 		}
186 		if(++sy >= src->r.max.y)
187 			sy = src->r.min.y;
188 	}
189 }
190 
191 static
192 void
horline(Memimage * dst,Point p0,Point p1,Memimage * src,Point dsrc,Rectangle clipr)193 horline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
194 {
195 	int x, y, deltay, deltax, minx, maxx;
196 	int bpp, m, m0;
197 	uchar *d, *s;
198 
199 	deltax = p1.x - p0.x;
200 	deltay = p1.y - p0.y;
201 	minx = lmax(p0.x, clipr.min.x);
202 	maxx = lmin(p1.x, clipr.max.x-1);
203 	bpp = dst->depth;
204 	m0 = 0xFF^(0xFF>>bpp);
205 	m = m0 >> (minx&(7/dst->depth))*bpp;
206 	for(x=minx; x<=maxx; x++){
207 		y = p0.y + (deltay*(x-p0.x)+deltay/2)/deltax;
208 		if(clipr.min.y<=y && y<clipr.max.y){
209 			d = byteaddr(dst, Pt(x, y));
210 			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
211 			*d ^= (*d^*s) & m;
212 		}
213 		m >>= bpp;
214 		if(m == 0)
215 			m = m0;
216 	}
217 }
218 
219 static
220 void
verline(Memimage * dst,Point p0,Point p1,Memimage * src,Point dsrc,Rectangle clipr)221 verline(Memimage *dst, Point p0, Point p1, Memimage *src, Point dsrc, Rectangle clipr)
222 {
223 	int x, y, deltay, deltax, miny, maxy;
224 	int bpp, m, m0;
225 	uchar *d, *s;
226 
227 	deltax = p1.x - p0.x;
228 	deltay = p1.y - p0.y;
229 	miny = lmax(p0.y, clipr.min.y);
230 	maxy = lmin(p1.y, clipr.max.y-1);
231 	bpp = dst->depth;
232 	m0 = 0xFF^(0xFF>>bpp);
233 	for(y=miny; y<=maxy; y++){
234 		if(deltay == 0)	/* degenerate line */
235 			x = p0.x;
236 		else
237 			x = p0.x + deltax*(y-p0.y)/deltay;
238 		if(clipr.min.x<=x && x<clipr.max.x){
239 			m = m0 >> (x&(7/dst->depth))*bpp;
240 			d = byteaddr(dst, Pt(x, y));
241 			s = byteaddr(src, addpt(dsrc, Pt(x, y)));
242 			*d ^= (*d^*s) & m;
243 		}
244 	}
245 }
246 #endif /* NOTUSED */
247 
248 static Memimage*
membrush(int radius)249 membrush(int radius)
250 {
251 	static Memimage *brush;
252 	static int brushradius;
253 
254 	if(brush==nil || brushradius!=radius){
255 		freememimage(brush);
256 		brush = allocmemimage(Rect(0, 0, 2*radius+1, 2*radius+1), memopaque->chan);
257 		if(brush != nil){
258 			memfillcolor(brush, DTransparent);	/* zeros */
259 			memellipse(brush, Pt(radius, radius), radius, radius, -1, memopaque, Pt(radius, radius), S);
260 		}
261 		brushradius = radius;
262 	}
263 	return brush;
264 }
265 
266 static
267 void
discend(Point p,int radius,Memimage * dst,Memimage * src,Point dsrc,int op)268 discend(Point p, int radius, Memimage *dst, Memimage *src, Point dsrc, int op)
269 {
270 	Memimage *disc;
271 	Rectangle r;
272 
273 	disc = membrush(radius);
274 	if(disc != nil){
275 		r.min.x = p.x - radius;
276 		r.min.y = p.y - radius;
277 		r.max.x = p.x + radius+1;
278 		r.max.y = p.y + radius+1;
279 		memdraw(dst, r, src, addpt(r.min, dsrc), disc, Pt(0,0), op);
280 	}
281 }
282 
283 static
284 void
arrowend(Point tip,Point * pp,int end,int sin,int cos,int radius)285 arrowend(Point tip, Point *pp, int end, int sin, int cos, int radius)
286 {
287 	int x1, x2, x3;
288 
289 	/* before rotation */
290 	if(end == Endarrow){
291 		x1 = Arrow1;
292 		x2 = Arrow2;
293 		x3 = Arrow3;
294 	}else{
295 		x1 = (end>>5) & 0x1FF;	/* distance along line from end of line to tip */
296 		x2 = (end>>14) & 0x1FF;	/* distance along line from barb to tip */
297 		x3 = (end>>23) & 0x1FF;	/* distance perpendicular from edge of line to barb */
298 	}
299 
300 	/* comments follow track of right-facing arrowhead */
301 	pp->x = tip.x+((2*radius+1)*sin/2-x1*cos);		/* upper side of shaft */
302 	pp->y = tip.y-((2*radius+1)*cos/2+x1*sin);
303 	pp++;
304 	pp->x = tip.x+((2*radius+2*x3+1)*sin/2-x2*cos);		/* upper barb */
305 	pp->y = tip.y-((2*radius+2*x3+1)*cos/2+x2*sin);
306 	pp++;
307 	pp->x = tip.x;
308 	pp->y = tip.y;
309 	pp++;
310 	pp->x = tip.x+(-(2*radius+2*x3+1)*sin/2-x2*cos);	/* lower barb */
311 	pp->y = tip.y-(-(2*radius+2*x3+1)*cos/2+x2*sin);
312 	pp++;
313 	pp->x = tip.x+(-(2*radius+1)*sin/2-x1*cos);		/* lower side of shaft */
314 	pp->y = tip.y+((2*radius+1)*cos/2-x1*sin);
315 }
316 
317 void
_memimageline(Memimage * dst,Point p0,Point p1,int end0,int end1,int radius,Memimage * src,Point sp,Rectangle clipr,int op)318 _memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, Rectangle clipr, int op)
319 {
320 	/*
321 	 * BUG: We should really really pick off purely horizontal and purely
322 	 * vertical lines and handle them separately with calls to memimagedraw
323 	 * on rectangles.
324 	 */
325 
326 	int hor;
327 	int sin, cos, dx, dy, t;
328 	Rectangle oclipr, r;
329 	Point q, pts[10], *pp, d;
330 
331 	if(radius < 0)
332 		return;
333 	if(rectclip(&clipr, dst->r) == 0)
334 		return;
335 	if(rectclip(&clipr, dst->clipr) == 0)
336 		return;
337 	d = subpt(sp, p0);
338 	if(rectclip(&clipr, rectsubpt(src->clipr, d)) == 0)
339 		return;
340 	if((src->flags&Frepl)==0 && rectclip(&clipr, rectsubpt(src->r, d))==0)
341 		return;
342 	/* this means that only verline() handles degenerate lines (p0==p1) */
343 	hor = (abs(p1.x-p0.x) > abs(p1.y-p0.y));
344 	/*
345 	 * Clipping is a little peculiar.  We can't use Sutherland-Cohen
346 	 * clipping because lines are wide.  But this is probably just fine:
347 	 * we do all math with the original p0 and p1, but clip when deciding
348 	 * what pixels to draw.  This means the layer code can call this routine,
349 	 * using clipr to define the region being written, and get the same set
350 	 * of pixels regardless of the dicing.
351 	 */
352 	if((hor && p0.x>p1.x) || (!hor && p0.y>p1.y)){
353 		q = p0;
354 		p0 = p1;
355 		p1 = q;
356 		t = end0;
357 		end0 = end1;
358 		end1 = t;
359 	}
360 
361 	if((p0.x == p1.x || p0.y == p1.y) && (end0&0x1F) == Endsquare && (end1&0x1F) == Endsquare){
362 		r.min = p0;
363 		r.max = p1;
364 		if(p0.x == p1.x){
365 			r.min.x -= radius;
366 			r.max.x += radius+1;
367 		}
368 		else{
369 			r.min.y -= radius;
370 			r.max.y += radius+1;
371 		}
372 		oclipr = dst->clipr;
373 		dst->clipr = clipr;
374 		memimagedraw(dst, r, src, sp, memopaque, sp, op);
375 		dst->clipr = oclipr;
376 		return;
377 	}
378 
379 /*    Hard: */
380 	/* draw thick line using polygon fill */
381 	icossin2(p1.x-p0.x, p1.y-p0.y, &cos, &sin);
382 	dx = (sin*(2*radius+1))/2;
383 	dy = (cos*(2*radius+1))/2;
384 	pp = pts;
385 	oclipr = dst->clipr;
386 	dst->clipr = clipr;
387 	q.x = ICOSSCALE*p0.x+ICOSSCALE/2-cos/2;
388 	q.y = ICOSSCALE*p0.y+ICOSSCALE/2-sin/2;
389 	switch(end0 & 0x1F){
390 	case Enddisc:
391 		discend(p0, radius, dst, src, d, op);
392 		/* fall through */
393 	case Endsquare:
394 	default:
395 		pp->x = q.x-dx;
396 		pp->y = q.y+dy;
397 		pp++;
398 		pp->x = q.x+dx;
399 		pp->y = q.y-dy;
400 		pp++;
401 		break;
402 	case Endarrow:
403 		arrowend(q, pp, end0, -sin, -cos, radius);
404 		_memfillpolysc(dst, pts, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
405 		pp[1] = pp[4];
406 		pp += 2;
407 	}
408 	q.x = ICOSSCALE*p1.x+ICOSSCALE/2+cos/2;
409 	q.y = ICOSSCALE*p1.y+ICOSSCALE/2+sin/2;
410 	switch(end1 & 0x1F){
411 	case Enddisc:
412 		discend(p1, radius, dst, src, d, op);
413 		/* fall through */
414 	case Endsquare:
415 	default:
416 		pp->x = q.x+dx;
417 		pp->y = q.y-dy;
418 		pp++;
419 		pp->x = q.x-dx;
420 		pp->y = q.y+dy;
421 		pp++;
422 		break;
423 	case Endarrow:
424 		arrowend(q, pp, end1, sin, cos, radius);
425 		_memfillpolysc(dst, pp, 5, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 1, 10, 1, op);
426 		pp[1] = pp[4];
427 		pp += 2;
428 	}
429 	_memfillpolysc(dst, pts, pp-pts, ~0, src, addpt(pts[0], mulpt(d, ICOSSCALE)), 0, 10, 1, op);
430 	dst->clipr = oclipr;
431 	return;
432 }
433 
434 void
memimageline(Memimage * dst,Point p0,Point p1,int end0,int end1,int radius,Memimage * src,Point sp,int op)435 memimageline(Memimage *dst, Point p0, Point p1, int end0, int end1, int radius, Memimage *src, Point sp, int op)
436 {
437 	_memimageline(dst, p0, p1, end0, end1, radius, src, sp, dst->clipr, op);
438 }
439 
440 /*
441  * Simple-minded conservative code to compute bounding box of line.
442  * Result is probably a little larger than it needs to be.
443  */
444 static
445 void
addbbox(Rectangle * r,Point p)446 addbbox(Rectangle *r, Point p)
447 {
448 	if(r->min.x > p.x)
449 		r->min.x = p.x;
450 	if(r->min.y > p.y)
451 		r->min.y = p.y;
452 	if(r->max.x < p.x+1)
453 		r->max.x = p.x+1;
454 	if(r->max.y < p.y+1)
455 		r->max.y = p.y+1;
456 }
457 
458 int
memlineendsize(int end)459 memlineendsize(int end)
460 {
461 	int x3;
462 
463 	if((end&0x3F) != Endarrow)
464 		return 0;
465 	if(end == Endarrow)
466 		x3 = Arrow3;
467 	else
468 		x3 = (end>>23) & 0x1FF;
469 	return x3;
470 }
471 
472 Rectangle
memlinebbox(Point p0,Point p1,int end0,int end1,int radius)473 memlinebbox(Point p0, Point p1, int end0, int end1, int radius)
474 {
475 	Rectangle r, r1;
476 	int extra;
477 
478 	r.min.x = 10000000;
479 	r.min.y = 10000000;
480 	r.max.x = -10000000;
481 	r.max.y = -10000000;
482 	extra = lmax(memlineendsize(end0), memlineendsize(end1));
483 	r1 = insetrect(canonrect(Rpt(p0, p1)), -(radius+extra));
484 	addbbox(&r, r1.min);
485 	addbbox(&r, r1.max);
486 	return r;
487 }
488