/** * Rasterization module for converting shapes to pixels/rects * Used for software rendering of complex shapes */ var math = use('math') var rasterize = {} function within_wedge(dx, dy, start, end, full_circle) { if (full_circle) return true var ang = Math.atan2(dy, dx) if (ang < 0) ang += Math.PI * 2 var t = ang / (Math.PI * 2) if (start <= end) return t >= start && t <= end return t >= start || t <= end } rasterize.ellipse = function ellipse(pos, radii, opt) { opt = opt || {} var rx = radii[0], ry = radii[1] if (rx <= 0 || ry <= 0) return [] var cx = pos[0], cy = pos[1] var raw_start = opt.start || 0 var raw_end = opt.end || 1 var full_circle = Math.abs(raw_end - raw_start) >= 1 - 1e-9 var start = (raw_start % 1 + 1) % 1 var end = (raw_end % 1 + 1) % 1 var thickness = Math.max(1, opt.thickness || 1) var rx_i = rx - thickness, ry_i = ry - thickness var hole = (rx_i > 0 && ry_i > 0) if (!hole && thickness === 1) { var points = [] var rx_sq = rx * rx, ry_sq = ry * ry var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1 var x = 0, y = ry, px = 0, py = two_rx_sq * y var p = ry_sq - rx_sq * ry + 0.25 * rx_sq function add_pts(x, y) { var pts = [ [cx + x, cy + y], [cx - x, cy + y], [cx + x, cy - y], [cx - x, cy - y] ].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle)) points = points.concat(pts) } while (px < py) { add_pts(x, y) ++x; px += two_ry_sq if (p < 0) p += ry_sq + px else { --y; py -= two_rx_sq; p += ry_sq + px - py } } p = ry_sq*(x+.5)*(x+.5) + rx_sq*(y-1)*(y-1) - rx_sq*ry_sq while (y >= 0) { add_pts(x, y) --y; py -= two_rx_sq if (p > 0) p += rx_sq - py else { ++x; px += two_ry_sq; p += rx_sq - py + px } } return {type: 'points', data: points} } var strips = [] var rx_sq = rx * rx, ry_sq = ry * ry var rx_i_sq = rx_i * rx_i, ry_i_sq = ry_i * ry_i for (var dy = -ry; dy <= ry; ++dy) { var yy = dy * dy var x_out = Math.floor(rx * Math.sqrt(1 - yy / ry_sq)) var y_screen = cy + dy var x_in = hole ? Math.floor(rx_i * Math.sqrt(1 - yy / ry_i_sq)) : -1 var run_start = null for (var dx = -x_out; dx <= x_out; ++dx) { if (hole && Math.abs(dx) <= x_in) { run_start = null; continue } if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue } if (run_start === null) run_start = cx + dx var last = (dx === x_out) var next_in_ring = !last && !(hole && Math.abs(dx+1) <= x_in) && within_wedge(dx+1, dy, start, end, full_circle) if (last || !next_in_ring) { strips.push({ x: run_start, y: y_screen, width: (cx + dx) - run_start + 1, height: 1 }) run_start = null } } } return {type: 'rects', data: strips} } rasterize.circle = function circle(pos, radius, opt) { return rasterize.ellipse(pos, [radius, radius], opt) } rasterize.outline_rect = function outline_rect(rect, thickness) { if (thickness <= 0) { return {type: 'rect', data: rect} } if ((thickness << 1) >= rect.width || (thickness << 1) >= rect.height) { return {type: 'rect', data: rect} } var x0 = rect.x, y0 = rect.y, x1 = rect.x + rect.width, y1 = rect.y + rect.height return {type: 'rects', data: [ { x:x0, y:y0, width:rect.width, height:thickness }, { x:x0, y:y1-thickness, width:rect.width, height:thickness }, { x:x0, y:y0+thickness, width:thickness, height:rect.height - (thickness<<1) }, { x:x1-thickness, y:y0+thickness, width:thickness, height:rect.height - (thickness<<1) } ]} } rasterize.round_rect = function round_rect(rect, radius, thickness) { thickness = thickness || 1 if (thickness <= 0) { return rasterize.fill_round_rect(rect, radius) } radius = Math.min(radius, rect.width >> 1, rect.height >> 1) if ((thickness << 1) >= rect.width || (thickness << 1) >= rect.height || thickness >= radius) { return rasterize.fill_round_rect(rect, radius) } var x0 = rect.x, y0 = rect.y, x1 = rect.x + rect.width - 1, y1 = rect.y + rect.height - 1 var cx_l = x0 + radius, cx_r = x1 - radius var cy_t = y0 + radius, cy_b = y1 - radius var r_out = radius var r_in = radius - thickness var rects = [ { x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:thickness }, { x:x0 + radius, y:y1 - thickness + 1, width:rect.width - (radius << 1), height:thickness }, { x:x0, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) }, { x:x1 - thickness + 1, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) } ] var strips = [] for (var dy = 0; dy < radius; ++dy) { var dy_sq = dy * dy var dx_out = Math.floor(Math.sqrt(r_out * r_out - dy_sq)) var dx_in = (r_in > 0 && dy < r_in) ? Math.floor(Math.sqrt(r_in * r_in - dy_sq)) : -1 var w = dx_out - dx_in if (w <= 0) continue strips.push( { x:cx_l - dx_out, y:cy_t - dy, width:w, height:1 }, { x:cx_r + dx_in + 1, y:cy_t - dy, width:w, height:1 }, { x:cx_l - dx_out, y:cy_b + dy, width:w, height:1 }, { x:cx_r + dx_in + 1, y:cy_b + dy, width:w, height:1 } ) } return {type: 'rects', data: rects.concat(strips)} } rasterize.fill_round_rect = function fill_round_rect(rect, radius) { radius = Math.min(radius, rect.width >> 1, rect.height >> 1) var x0 = rect.x, y0 = rect.y, x1 = rect.x + rect.width - 1, y1 = rect.y + rect.height - 1 var rects = [ { x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:rect.height }, { x:x0, y:y0 + radius, width:radius, height:rect.height - (radius << 1) }, { x:x1 - radius + 1, y:y0 + radius, width:radius, height:rect.height - (radius << 1) } ] var cx_l = x0 + radius, cx_r = x1 - radius var cy_t = y0 + radius, cy_b = y1 - radius var caps = [] for (var dy = 0; dy < radius; ++dy) { var dx = Math.floor(Math.sqrt(radius * radius - dy * dy)) var w = (dx << 1) + 1 caps.push( { x:cx_l - dx, y:cy_t - dy, width:w, height:1 }, { x:cx_r - dx, y:cy_t - dy, width:w, height:1 }, { x:cx_l - dx, y:cy_b + dy, width:w, height:1 }, { x:cx_r - dx, y:cy_b + dy, width:w, height:1 } ) } return {type: 'rects', data: rects.concat(caps)} } return rasterize