Implement pointrect.js: addpt, etc. Use pointrect functions where appropriate. Implement drawclip and use it to implement a robust drawmasked. Use drawmasked where appropriate. Convert graphics functions from doing gross clipping on dst to drawing on a mask. Probably includes a few old changes I forgot to submit before. . Reference: /n/atom/patch/applied/wd-point-mask-etc Date: Wed Jun 4 21:15:29 CES 2014 Signed-off-by: root@davidrhoskin.com --- /usr/web/9wd/js/util/array.js Wed Jun 4 21:12:40 2014 +++ /usr/web/9wd/js/util/array.js Wed Jun 4 21:12:42 2014 @@ -70,17 +70,19 @@ } ArrayIterator.prototype.getPoint = function(){ - return { - x: this.getLong(), - y: this.getLong() - } + var that = this; + return new Point( + that.getLong(), + that.getLong() + ); } ArrayIterator.prototype.getRect = function(){ - return { - min: this.getPoint(), - max: this.getPoint() - } + var that = this; + return new Rect( + that.getPoint(), + that.getPoint() + ); } ArrayIterator.prototype.strtoul = function(){ --- /usr/web/9wd/js/draw/data.js Wed Jun 4 21:12:45 2014 +++ /usr/web/9wd/js/draw/data.js Wed Jun 4 21:12:47 2014 @@ -128,9 +128,7 @@ if(mask == undefined){ throw("invalid image id"); } - /* XXX should be drawmasked() */ - /* XXX calling unintentionally global draw(). */ - draw(dst, dstr, src, srcp, conn.op); + drawmasked(dst, dstr, src, srcp, mask, maskp, conn.op); }, "D": function(conn, offset, ai){ try{ @@ -245,8 +243,7 @@ left: left, width: width }; - /* XXX draw() is meant to be private to Memdraw! */ - draw(cache, r, src, sp, Memdraw.Opdefs.SoverD.key); + drawmasked(cache, r, src, sp, undefined, undefined, Memdraw.Opdefs.SoverD.key); }, "L": function(conn, offset, ai){ try{ @@ -336,12 +333,12 @@ var srcid = ai.getLong(); var sp = ai.getPoint(); var dp = []; - var o = {x: 0, y: 0}; + var o = new Point(0, 0); for(var i = 0; i < n; ++i){ - dp[i] = { - x: drawcoord(ai, o.x), - y: drawcoord(ai, o.y) - } + dp[i] = new Point( + drawcoord(ai, o.x), + drawcoord(ai, o.y) + ); o = dp[i]; } }catch(e){ @@ -366,12 +363,12 @@ var srcid = ai.getLong(); var sp = ai.getPoint(); var dp = []; - var o = {x: 0, y: 0}; + var o = new Point(0, 0); for(var i = 0; i < n; ++i){ - var p = { - x: drawcoord(ai, o.x), - y: drawcoord(ai, o.y) - } + var p = new Point( + drawcoord(ai, o.x), + drawcoord(ai, o.y) + ); dp[i] = o = p; } }catch(e){ --- /usr/web/9wd/js/draw/image.js Wed Jun 4 21:12:50 2014 +++ /usr/web/9wd/js/draw/image.js Wed Jun 4 21:12:51 2014 @@ -1,3 +1,26 @@ +CtxWrap = function(ctx){ + ctx.rrect = function(r){ + return ctx.rect(r.min.x, r.min.y, r.max.x, r.max.y); + } + ctx.pdrawImage = function(canvas, p){ + return ctx.drawImage(canvas, p.x, p.y); + } + ctx.rclearRect = function(r){ + return ctx.clearRect(r.min.x, r.min.y,r.max.x, r.max.y); + } + ctx.pmoveTo = function(p){ + return ctx.moveTo(p.x, p.y); + } + ctx.plineTo = function(p){ + return ctx.lineTo(p.x, p.y); + } + ctx.ptranslate = function(p){ + return ctx.translate(p.x, p.y); + } + + return ctx; +} + Draw9p.Image = function(refresh, chan, repl, r, clipr, color){ this.refresh = refresh; this.chan = chan; @@ -7,7 +30,7 @@ this.canvas = document.createElement("canvas"); this.canvas.width = r.max.x - r.min.x; this.canvas.height = r.max.y - r.min.y; - this.ctx = this.canvas.getContext("2d"); + this.ctx = CtxWrap(this.canvas.getContext("2d")); var red = (color >> 24) & 0xFF; var green = (color >> 16) & 0xFF; @@ -45,7 +68,7 @@ /* XXX We should create a backing store, depending on refresh value. */ this.canvas = this.screen.backimg.canvas; - this.ctx = this.canvas.getContext("2d"); + this.ctx = CtxWrap(this.canvas.getContext("2d")); this.ctx.beginPath(); this.ctx.moveTo(r.min.x, r.min.y); this.ctx.lineTo(r.max.x, r.min.y); @@ -67,7 +90,7 @@ 0xFFFFFFFF); image.canvas = Draw9p.rootcanvas; - image.ctx = image.canvas.getContext("2d"); + image.ctx = CtxWrap(image.canvas.getContext("2d")); return image; } --- /usr/web/9wd/js/draw/memdraw.js Wed Jun 4 21:12:54 2014 +++ /usr/web/9wd/js/draw/memdraw.js Wed Jun 4 21:12:56 2014 @@ -7,24 +7,126 @@ } } -/* XXX Seems to misbehave on non-(0,0) src.r.min. */ +/* See: /sys/src/libdraw/drawrepl.c */ +var drawreplxy = function(min, max, x){ + var sx; + + sx = (x - min) % (max - min); + if(sx < 0) + sx += max - min; + + return sx + min; +} + +var drawrepl = function(r, p){ + return new Point( + drawreplxy(r.min.x, r.max.x, p.x), + drawreplxy(r.min.y, r.max.y, p.y) + ); +} + +/* See: /sys/src/libmemdraw/draw.c:/^drawclip */ +var drawclip = function(dst, ior, src, iop0, mask, iop1, iosr, iomr){ + var r, p0, p1, sr, mr; + var rmin, delta; + var splitcoords; + var omr; + + r = ior; + p0 = iop0; + p1 = iop1; + sr = iosr; + mr = iomr; + + if(r.min.x >= r.max.x || r.min.y >= r.max.y) + return false; + splitcoords = !eqpt(p0, p1); + + /* clip to destination */ + rmin = Point.copy(r.min); + if(!rectclip(r, dst.r) || !rectclip(r, dst.clipr)) + return false; + + /* move mask point */ + p1 = addpt(p1, subpt(r.min, rmin)); + + /* move source point */ + p0 = addpt(p0, subpt(r.min, rmin)); + + /* map destination rectangle into source */ + sr.min = Point.copy(p0); + sr.max = addpt(p0, Dxy(r)); + + /* sr is r in source coordinates; clip to source */ + if(!src.repl && !rectclip(sr, src.r)) + return false; + if(!rectclip(sr, src.clipr)) + return false; + + /* compute and clip rectangle in mask */ + if(splitcoords){ + /* move mask point with source */ + p1 = addpt(p1, subpt(sr.min, p0)); + mr.min = Point.copy(p1); + mr.max = addpt(p1, Dxy(sr)); + omr = Rect.copy(mr); + + /* mr is now rectangle in mask; clip it */ + if(!mask.repl && !rectclip(mr, mask.r)) + return false; + if(!rectclip(mr, mask.clipr)) + return false; + + /* reflect any clips back to source */ + sr.min = addpt(sr.min, subpt(mr.min, omr.min)); + sr.max = addpt(sr.max, subpt(mr.max, omr.max)); + p1 = Point.copy(mr.min); + }else{ + if(!mask.repl && !rectclip(sr, mask.r)) + return false; + if(!rectclip(sr, mask.clipr)) + return false; + p1 = Point.copy(sr.min); + } + + /* move source clipping back to destination */ + delta = subpt(r.min, p0); + r = rectaddpt(sr, delta); + + /* move source rectangle so sr->min is in src->r */ + if(src.repl){ + delta = subpt(drawrepl(src.r, sr.min), sr.min); + sr = rectaddpt(sr, delta); + } + p0 = sr.min; + + /* move mask point so it is in mask->r */ + p1 = drawrepl(mask.r, p1); + mr.min = p1; + mr.max = addpt(p1, Dxy(sr)); + + Rect.copyTo(ior, r); + Point.copyTo(iop0, p0); + Point.copyTo(iop1, p1); + Rect.copyTo(iosr, sr); + Rect.copyTo(iomr, mr); + return true; +} + +/* XXX repl=1 images will not be aligned. Works fine for simple colour fill though. */ var draw = function(dst, r, src, sp, op){ dst.ctx.save(); dst.ctx.globalCompositeOperation = Memdraw.Ops[op]; - /* XXX what about clipping on src? */ - dst.ctx.beginPath(); - dst.ctx.rect(r.min.x-dst.r.min.x, r.min.y-dst.r.min.y, - r.max.x-r.min.x, r.max.y-r.min.y); + dst.ctx.rrect(new Rect(subpt(r.min, dst.r.min), subpt(r.max, r.min))); dst.ctx.clip(); if(src.repl){ dst.ctx.fillStyle = dst.ctx.createPattern(src.canvas, "repeat"); dst.ctx.fill(); }else{ - dst.ctx.drawImage(src.canvas, r.min.x-sp.x+src.r.min.x-dst.r.min.x, - r.min.y-sp.y+src.r.min.y-dst.r.min.y); + dst.ctx.pdrawImage(src.canvas, addpt(subpt(r.min, dst.r.min), subpt(src.r.min, sp))); } dst.ctx.restore(); return; @@ -38,29 +140,39 @@ img.ctx.putImageData(data, 0, 0); } -var drawmasked = function(dst, r, src, sp, mask, mp, op){ - if(r.max.x == r.min.x || r.max.y == r.min.y){ - return; +memopaque = new Draw9p.Image( + 0, Chan.fmts.GREY1, 1, + new Rect(new Point(0, 0), new Point(1, 1)), + new Rect(new Point(-0x3FFFFFF, -0x3FFFFFF), new Point(0x3FFFFFF, 0x3FFFFFF)), + 0xFFFFFFFF +); + +var drawmasked = function(dst, ior, src, iosp, mask, iomp, op){ + var r, sr, mr, sp, mp; + var img; + + if(mask == undefined){ + mask = memopaque; + iomp = new Point(0, 0); } - /* XXX Hack: draw() doesn't seem to handle offset images properly. */ - var rdelta = { - min: {x: 0, y: 0}, - max: { - x: r.max.x - r.min.x, - y: r.max.y - r.min.y - } - } - var img = new Draw9p.Image(0, Chan.fmts.CMAP8, 0, rdelta, rdelta, 0xFF00FFFF); + r = Rect.copy(ior); + sr = new Rect(new Point(0, 0), new Point(0, 0)); + mr = new Rect(new Point(0, 0), new Point(0, 0)); + sp = Point.copy(iosp); + mp = Point.copy(iomp); + + /* XXX modifies r, sp, mp, sr, mr */ + if(!drawclip(dst, r, src, sp, mask, mp, sr, mr)) + return false; - /* XXX Hack; we should have a way to create blank images. */ - img.ctx.clearRect(0, 0, r.max.x, r.max.y); - - draw(img, rdelta, mask, mp, Memdraw.Opdefs.SoverD.key); + img = new Draw9p.Image(0, Chan.fmts.RGBA32, 0, r, r, 0x00000000); + draw(img, r, mask, mp, Memdraw.Opdefs.SoverD.key); maskalpha(img); - draw(img, rdelta, src, sp, Memdraw.Opdefs.SinD.key); - //document.body.appendChild(img.canvas); - draw(dst, r, img, {x: 0, y: 0}, op); + + draw(img, r, src, sp, Memdraw.Opdefs.SinD.key); + draw(dst, r, img, r.min, op); + return true; } var load = function(dst, r, data, iscompressed){ @@ -137,7 +249,7 @@ } if(bg){ - draw(dst, r, bg, bp, op); + drawmasked(dst, r, bg, bp, undefined, undefined, op); } drawmasked(dst, r, src, sp1, font, fc.r.min, op); p.x += fc.width; @@ -204,31 +316,42 @@ }, /* XXX behaves incorrectly for incomplete (non 2pi) ellipses. */ fillellipse: function(dst, c, horiz, vert, alpha, phi, src, sp, op){ - dst.ctx.save(); - dst.ctx.save(); - dst.ctx.beginPath(); - dst.ctx.translate(c.x, c.y); - dst.ctx.scale(horiz, vert); - dst.ctx.arc(0, 0, 1, alpha, phi, false); - dst.ctx.restore(); - dst.ctx.clip(); - draw(dst, dst.clipr, src, sp, op); - dst.ctx.restore(); + var r, dxy; + var mask; + + dxy = new Point(horiz, vert); + r = new Rect(subpt(c, dxy), addpt(c, dxy)); + mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000); + mask.ctx.beginPath(); + mask.ctx.ptranslate(subpt(c, r.min)); + mask.ctx.scale(horiz, vert); + mask.ctx.arc(0, 0, 1, alpha, phi, true); + mask.ctx.fillStyle = "white"; + mask.ctx.fill(); + + drawmasked(dst, r, src, sp, mask, r.min, op); }, + /* XXX ignores w (winding rule) */ fillpoly: function(dst, vertices, w, src, sp, op){ + var r; + var mask; if(vertices.length < 1){ return; } - dst.ctx.save(); - dst.ctx.beginPath(); - dst.ctx.moveTo(vertices[0].x, vertices[0].y); + r = new Rect(vertices[0], vertices[0]); + for(var i = 0; i < vertices.length; ++i){ + rcombinept(r, vertices[i]); + } + mask = new Draw9p.Image(0, Chan.fmts.GREY1, 0, r, r, 0x00000000); + mask.ctx.fillStyle = "white"; + mask.ctx.beginPath(); + mask.ctx.pmoveTo(subpt(vertices[0], r.min)); for(var i = 1; i < vertices.length; ++i){ - dst.ctx.lineTo(vertices[i].x, vertices[i].y); + mask.ctx.plineTo(subpt(vertices[i], r.min)); } - dst.ctx.clip(); - /* XXX fill background here */ - draw(dst, {min: {x: 0, y: 0}, max: {x: 500, y: 500}}, src, sp, op); - dst.ctx.restore(); + mask.ctx.plineTo(subpt(vertices[0], r.min)); + mask.ctx.fill(); + drawmasked(dst, r, src, sp, mask, r.min, op); return; }, poly: function(dst, points, end0, end1, radius, src, sp, op){ --- /usr/web/9wd/js/draw/pointrect.js Thu Jan 1 00:00:00 1970 +++ /usr/web/9wd/js/draw/pointrect.js Wed Jun 4 21:12:58 2014 @@ -0,0 +1,221 @@ +/* See /sys/src/libdraw/arith.c */ + +var Point = function(x, y){ + this.x = x; + this.y = y; +} + +Point.copy = function(p){ + return new Point(p.x, p.y); +} + +Point.copyTo = function(p, q){ + p.x = q.x; + p.y = q.y; +} + +Point.prototype.add = function(p){ + this.x += p.x; + this.y += p.y; + return this; +} + +Point.prototype.sub = function(p){ + this.x -= p.x; + this.y -= p.y; + return this; +} + +Point.prototype.div = function(n){ + this.x /= n; + this.y /= n; + return this; +} + +Point.prototype.mul = function(n){ + this.x *= n; + this.y *= n; + return this; +} + +var Rect = function(min, max){ + this.min = Point.copy(min); + this.max = Point.copy(max); +} + +Rect.prototype.toString = function(){ + return "{(" + this.min.x + "," + this.min.y + "),(" + this.max.x + "," + this.max.y + ")}"; +} + +Rect.copy = function(r){ + var min = Point.copy(r.min); + var max = Point.copy(r.max); + return new Rect(min, max); +} + +Rect.copyTo = function(r, s){ + Point.copyTo(r.min, s.min); + Point.copyTo(r.max, s.max); +} + +Rect.prototype.inset = function(n){ + this.min.x += n; + this.min.y += n; + this.max.x -= n; + this.max.y -= n; + return this; +} + +Rect.prototype.subpt = function(p){ + this.min.x -= p.x; + this.min.y -= p.y; + this.max.x -= p.x; + this.max.y -= p.y; + return this; +} + +Rect.prototype.addpt = function(p){ + this.min.x += p.x; + this.min.y += p.y; + this.max.x += p.x; + this.max.y += p.y; + return this; +} + +var addpt = function(a, b){ + return Point.copy(a).add(b); +} + +var subpt = function(a, b){ + return Point.copy(a).sub(b); +} + +var insetrect = function(r, n){ + return Rect.copy(r).inset(n); +} + +var divpt = function(a, b){ + return Point.copy(a).div(b); +} + +var mulpt = function(a, b){ + return Point.copy(a).mul(b); +} + +var rectsubpt = function(r, p){ + return Rect.copy(r).subpt(p); +} + +var rectaddpt = function(r, p){ + return Rect.copy(r).addpt(p); +} + +var eqpt = function(p, q){ + return p.x == q.x && p.y == q.y; +} + +var eqrect = function(r, s){ + return ( + r.min.x == s.min.x && + r.max.x == s.max.x && + r.min.y == s.min.y && + r.max.y == s.max.y + ); +} + +var rectXrect = function(r, s){ + return ( + r.min.x < s.max.x && + s.min.x < r.max.x && + r.min.y < s.max.y && + s.min.y < r.max.y + ); +} + +var rectinrect = function(r, s){ + return ( + s.min.x <= r.min.x && + r.max.x <= s.max.x && + s.min.y <= r.min.y && + r.max.y <= s.max.y + ); +} + +var ptinrect = function(p, r){ + return ( + p.x >= r.min.x && + p.x < r.max.x && + p.y >= r.min.y && + p.y < r.max.y + ); +} + +var canonrect = function(r){ + var t; + r = Rect.copy(r); + if(r.max.x < r.min.x){ + t = r.min.x; + r.min.x = r.max.x; + r.max.x = t; + } + if(r.max.y < r.min.y){ + t = r.min.y; + r.min.y = r.max.y; + r.max.y = t; + } + return r; +} + +var combinerect = function(r1, r2){ + if(r1.min.x > r2.min.x) + r1.min.x = r2.min.x; + if(r1.min.y > r2.min.y) + r1.min.y = r2.min.y; + if(r1.max.x < r2.max.x) + r1.max.x = r2.max.x; + if(r1.max.y < r2.max.y) + r1.max.y = r2.max.y; +} + +var rcombinept = function(r, p){ + if(r.min.x > p.x) + r.min.x = p.x; + if(r.min.y > p.y) + r.min.y = p.y; + if(r.max.x < p.x) + r.max.x = p.x; + if(r.max.y < p.y) + r.max.y = p.y; + return r; +} + +/* See /sys/src/libdraw/rectclip.c */ + +var rectclip = function(r, b){ + + /* They must overlap */ + if(rectXrect(r, b) == false) + return false; + + if(r.min.x < b.min.x) + r.min.x = b.min.x; + if(r.min.y < b.min.y) + r.min.y = b.min.y; + if(r.max.x > b.max.x) + r.max.x = b.max.x; + if(r.max.y > b.max.y) + r.max.y = b.max.y; + return true; +} + +var Dx = function(r){ + return r.max.x - r.min.x; +} + +var Dy = function(r){ + return r.max.y - r.min.y; +} + +var Dxy = function(r){ + return new Point(Dx(r), Dy(r)); +}