Files
prosperon/rasterize.cm
2025-11-22 09:43:51 -06:00

223 lines
6.4 KiB
Plaintext

/**
* 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