Harbor

branch main
showing the latest snapshot on main
raster.odin 12.6 KB · Plain text
raster.odin 0644 Raw
package font

import "core:mem"
import "core:math"

@(private)
ifloor :: #force_inline proc(x: f32) -> i32 {
	return i32(math.floor(x))
}

@(private)
iceil :: #force_inline proc(x: f32) -> i32 {
	return i32(math.ceil(x))
}

// Coverage-accumulation rasterizer
@(private)
rasterize_glyph :: proc(result: ^Bitmap, flatness_in_pixels: f32, vertices: [^]Vertex, num_verts: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, x_off: i32, y_off: i32, invert: i32) {
	w := int(result.w)
	h := int(result.h)
	if w == 0 || h == 0 do return

	STACK_SIZE :: 4096
	stack_buf: [STACK_SIZE]f32
	buf: [^]f32
	buf_len := w * h + 3
	heap_buf: rawptr = nil

	if buf_len <= STACK_SIZE {
		mem.zero(&stack_buf[0], buf_len * size_of(f32))
		buf = &stack_buf[0]
	} else {
		ptr, _ := mem.alloc(buf_len * size_of(f32))
		if ptr == nil do return
		heap_buf = ptr
		buf = ([^]f32)(ptr)
		mem.zero(ptr, buf_len * size_of(f32))
	}
	defer if heap_buf != nil do mem.free(heap_buf)

	scale := min(scale_x, scale_y)
	flatness := flatness_in_pixels / scale
	flatness_sq := flatness * flatness
	y_dir: f32 = -1.0 if invert != 0 else 1.0

	cx, cy: f32
	#no_bounds_check for vi in 0..<num_verts {
		v := vertices[vi]
		vx := f32(v.x) * scale_x + shift_x - f32(x_off)
		vy := (f32(v.y) * scale_y * y_dir + shift_y) - f32(y_off)

		switch v.type {
		case VMOVE:
			cx = vx; cy = vy
		case VLINE:
			raster_line(buf, i32(w), i32(h), cx, cy, vx, vy)
			cx = vx; cy = vy
		case VCURVE:
			qcx := f32(v.cx) * scale_x + shift_x - f32(x_off)
			qcy := (f32(v.cy) * scale_y * y_dir + shift_y) - f32(y_off)
			raster_quad(buf, i32(w), i32(h), cx, cy, qcx, qcy, vx, vy, flatness_sq)
			cx = vx; cy = vy
		case VCUBIC:
			c1x := f32(v.cx) * scale_x + shift_x - f32(x_off)
			c1y := (f32(v.cy) * scale_y * y_dir + shift_y) - f32(y_off)
			c2x := f32(v.cx1) * scale_x + shift_x - f32(x_off)
			c2y := (f32(v.cy1) * scale_y * y_dir + shift_y) - f32(y_off)
			raster_cubic(buf, i32(w), i32(h), cx, cy, c1x, c1y, c2x, c2y, vx, vy, flatness_sq, 0)
			cx = vx; cy = vy
		}
	}

	#no_bounds_check for row in 0..<i32(h) {
		coverage: f32 = 0
		row_start := row * result.stride
		row_buf := int(row) * w
		for col in 0..<i32(w) {
			coverage += buf[row_buf + int(col)]
			val := abs(coverage) * 255.0 + 0.5
			result.pixels[row_start + col] = u8(min(i32(val), 255))
		}
	}
}

@(private)
raster_line :: #force_inline proc(buf: [^]f32, w: i32, h: i32, x0_in: f32, y0_in: f32, x1_in: f32, y1_in: f32) {
	if y0_in == y1_in do return

	x0, y0, x1, y1: f32
	sign: f32
	if y0_in < y1_in {
		x0 = x0_in; y0 = y0_in; x1 = x1_in; y1 = y1_in; sign = 1.0
	} else {
		x0 = x1_in; y0 = y1_in; x1 = x0_in; y1 = y0_in; sign = -1.0
	}

	if y1 <= 0 || y0 >= f32(h) do return

	dxdy := (x1 - x0) / (y1 - y0)

	row_start := max(i32(y0), 0)
	row_end := min(i32(y1), h - 1)

	#no_bounds_check for row := row_start; row <= row_end; row += 1 {
		rt := f32(row)
		yt := max(y0, rt)
		yb := min(y1, rt + 1.0)
		if yt >= yb do continue

		height := (yb - yt) * sign
		xt := x0 + (yt - y0) * dxdy
		xb := x0 + (yb - y0) * dxdy
		mid := (xt + xb) * 0.5

		col := clamp(i32(mid), 0, w - 1)
		frac := clamp(mid - f32(col), 0, 1)

		idx := int(row) * int(w) + int(col)
		m := height * frac
		buf[idx] += height - m
		if col + 1 < w {
			buf[idx + 1] += m
		}
	}
}

@(private)
raster_quad :: proc(buf: [^]f32, w: i32, h: i32, x0: f32, y0: f32, cx: f32, cy: f32, x1: f32, y1: f32, flatness_sq: f32) {
	mx := (x0 + 2*cx + x1) / 4
	my := (y0 + 2*cy + y1) / 4
	dx := (x0+x1)/2 - mx
	dy := (y0+y1)/2 - my
	if dx*dx + dy*dy <= flatness_sq {
		raster_line(buf, w, h, x0, y0, x1, y1)
		return
	}
	cx01 := (x0 + cx) / 2; cy01 := (y0 + cy) / 2
	cx12 := (cx + x1) / 2; cy12 := (cy + y1) / 2
	mx2 := (cx01 + cx12) / 2; my2 := (cy01 + cy12) / 2
	raster_quad(buf, w, h, x0, y0, cx01, cy01, mx2, my2, flatness_sq)
	raster_quad(buf, w, h, mx2, my2, cx12, cy12, x1, y1, flatness_sq)
}

@(private)
raster_cubic :: proc(buf: [^]f32, w: i32, h: i32, x0: f32, y0: f32, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, flatness_sq: f32, depth: i32) {
	if depth > 16 do return
	dx := x3 - x0; dy := y3 - y0
	dx0 := x1 - x0; dy0 := y1 - y0
	dx1 := x2 - x1; dy1 := y2 - y1
	dx2 := x3 - x2; dy2 := y3 - y2
	longlen := math.sqrt(dx0*dx0+dy0*dy0) + math.sqrt(dx1*dx1+dy1*dy1) + math.sqrt(dx2*dx2+dy2*dy2)
	shortlen := math.sqrt(dx*dx+dy*dy)
	if (longlen - shortlen) * (longlen - shortlen) <= flatness_sq {
		raster_line(buf, w, h, x0, y0, x3, y3)
		return
	}
	x01 := (x0+x1)/2; y01 := (y0+y1)/2
	x12 := (x1+x2)/2; y12 := (y1+y2)/2
	x23 := (x2+x3)/2; y23 := (y2+y3)/2
	xa := (x01+x12)/2; ya := (y01+y12)/2
	xb := (x12+x23)/2; yb := (y12+y23)/2
	mx := (xa+xb)/2; my := (ya+yb)/2
	raster_cubic(buf, w, h, x0,y0, x01,y01, xa,ya, mx,my, flatness_sq, depth+1)
	raster_cubic(buf, w, h, mx,my, xb,yb, x23,y23, x3,y3, flatness_sq, depth+1)
}


// Phase 2.5 Functions - Public API

get_glyph_bitmap_box_subpixel :: proc(font: ^Font_Info, glyph: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) {
	x0, y0, x1, y1: i32 = 0, 0, 0, 0

	if !get_glyph_box(font, glyph, &x0, &y0, &x1, &y1) {
		// e.g. space character
		if ix0 != nil do ix0^ = 0
		if iy0 != nil do iy0^ = 0
		if ix1 != nil do ix1^ = 0
		if iy1 != nil do iy1^ = 0
	} else {
		// move to integral bboxes (treating pixels as little squares, what pixels get touched)?
		if ix0 != nil do ix0^ = ifloor(f32(x0) * scale_x + shift_x)
		if iy0 != nil do iy0^ = ifloor(f32(-y1) * scale_y + shift_y)
		if ix1 != nil do ix1^ = iceil(f32(x1) * scale_x + shift_x)
		if iy1 != nil do iy1^ = iceil(f32(-y0) * scale_y + shift_y)
	}
}

get_glyph_bitmap_box :: proc(font: ^Font_Info, glyph: i32, scale_x: f32, scale_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) {
	get_glyph_bitmap_box_subpixel(font, glyph, scale_x, scale_y, 0.0, 0.0, ix0, iy0, ix1, iy1)
}

get_codepoint_bitmap_box :: proc(font: ^Font_Info, codepoint: i32, scale_x: f32, scale_y: f32, ix0: ^i32, iy0: ^i32, ix1: ^i32, iy1: ^i32) {
	get_glyph_bitmap_box_subpixel(font, find_glyph_index(font, codepoint), scale_x, scale_y, 0.0, 0.0, ix0, iy0, ix1, iy1)
}

get_glyph_bitmap_subpixel :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, glyph: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 {
	// Use arena allocator for intermediate allocations (optimization)
	// Stack-based backing to avoid heap allocation overhead
	arena_backing: [256 * 1024]u8
	arena: mem.Arena
	mem.arena_init(&arena, arena_backing[:])
	context.allocator = mem.arena_allocator(&arena)

	ix0, iy0, ix1, iy1: i32
	gbm: Bitmap
	vertices_temp: ^Vertex = nil
	vertices: [^]Vertex = nil
	num_verts := get_glyph_shape(info, glyph, cast(^^Vertex)&vertices_temp)
	vertices = ([^]Vertex)(vertices_temp)

	scale_x := scale_x
	scale_y := scale_y

	if scale_x == 0 do scale_x = scale_y
	if scale_y == 0 {
		if scale_x == 0 {
			// vertices allocated from arena, will be freed with arena_backing
			return nil
		}
		scale_y = scale_x
	}

	get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, &ix1, &iy1)

	// now we get the size
	gbm.w = (ix1 - ix0)
	gbm.h = (iy1 - iy0)
	gbm.pixels = nil // in case we error

	if width != nil do width^ = gbm.w
	if height != nil do height^ = gbm.h
	if xoff != nil do xoff^ = ix0
	if yoff != nil do yoff^ = iy0

	if gbm.w != 0 && gbm.h != 0 {
		// Use caller's allocator for the returned bitmap
		ptr, _ := mem.alloc(int(gbm.w * gbm.h), allocator = allocator)
		gbm.pixels = ([^]u8)(ptr)
		if gbm.pixels != nil {
			gbm.stride = gbm.w
			rasterize_glyph(&gbm, 0.35, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1)
		}
	}
	// vertices allocated from arena, will be freed with arena_backing
	return gbm.pixels
}

get_glyph_bitmap :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, glyph: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 {
	return get_glyph_bitmap_subpixel(info, scale_x, scale_y, 0.0, 0.0, glyph, width, height, xoff, yoff, allocator)
}

get_codepoint_bitmap :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, codepoint: i32, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 {
	return get_glyph_bitmap_subpixel(info, scale_x, scale_y, 0.0, 0.0, find_glyph_index(info, codepoint), width, height, xoff, yoff, allocator)
}

// Render a variable glyph to bitmap at specific axis coordinates
get_glyph_bitmap_var :: proc(info: ^Font_Info, scale_x: f32, scale_y: f32, glyph: i32, coords: ^Var_Coords, width: ^i32, height: ^i32, xoff: ^i32, yoff: ^i32, allocator := context.allocator) -> [^]u8 {
	arena_backing: [256 * 1024]u8
	arena: mem.Arena
	mem.arena_init(&arena, arena_backing[:])
	context.allocator = mem.arena_allocator(&arena)

	vertices_temp: ^Vertex = nil
	num_verts := get_glyph_shape_var(info, glyph, coords, cast(^^Vertex)&vertices_temp)
	vertices := ([^]Vertex)(vertices_temp)

	scale_x := scale_x; scale_y := scale_y
	if scale_x == 0 do scale_x = scale_y
	if scale_y == 0 { if scale_x == 0 do return nil; scale_y = scale_x }

	ix0, iy0, ix1, iy1: i32
	get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, 0, 0, &ix0, &iy0, &ix1, &iy1)

	gbm: Bitmap
	gbm.w = ix1 - ix0; gbm.h = iy1 - iy0
	if width != nil  do width^  = gbm.w
	if height != nil do height^ = gbm.h
	if xoff != nil   do xoff^   = ix0
	if yoff != nil   do yoff^   = iy0

	if gbm.w != 0 && gbm.h != 0 {
		ptr, _ := mem.alloc(int(gbm.w * gbm.h), allocator = allocator)
		gbm.pixels = ([^]u8)(ptr)
		if gbm.pixels != nil {
			gbm.stride = gbm.w
			rasterize_glyph(&gbm, 0.35, vertices, num_verts, scale_x, scale_y, 0, 0, ix0, iy0, 1)
		}
	}
	return gbm.pixels
}

make_glyph_bitmap_subpixel :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, shift_x: f32, shift_y: f32, glyph: i32) {
	// Use arena allocator for intermediate allocations (optimization)
	// Stack-based backing to avoid heap allocation overhead
	arena_backing: [256 * 1024]u8
	arena: mem.Arena
	mem.arena_init(&arena, arena_backing[:])
	context.allocator = mem.arena_allocator(&arena)

	ix0, iy0: i32
	vertices_temp: ^Vertex = nil
	vertices: [^]Vertex = nil
	num_verts := get_glyph_shape(info, glyph, cast(^^Vertex)&vertices_temp)
	vertices = ([^]Vertex)(vertices_temp)
	gbm: Bitmap

	get_glyph_bitmap_box_subpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, nil, nil)
	gbm.pixels = output
	gbm.w = out_w
	gbm.h = out_h
	gbm.stride = out_stride

	if gbm.w != 0 && gbm.h != 0 {
		rasterize_glyph(&gbm, 0.35, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1)
	}
	// vertices allocated from arena, will be freed with arena_backing
}

make_glyph_bitmap :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, glyph: i32) {
	make_glyph_bitmap_subpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0, 0.0, glyph)
}

make_codepoint_bitmap :: proc(info: ^Font_Info, output: [^]u8, out_w: i32, out_h: i32, out_stride: i32, scale_x: f32, scale_y: f32, codepoint: i32) {
	make_glyph_bitmap_subpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0, 0.0, find_glyph_index(info, codepoint))
}

// Render glyph with oversampling and apply prefiltering for smoother results.
// The prefilter_x/y values are the oversample factors (1-8).
// sub_x/sub_y receive the subpixel shift values for proper glyph positioning.
make_glyph_bitmap_subpixel_prefilter :: proc(
	info: ^Font_Info,
	output: [^]u8,
	out_w: i32,
	out_h: i32,
	out_stride: i32,
	scale_x: f32,
	scale_y: f32,
	shift_x: f32,
	shift_y: f32,
	prefilter_x: i32,
	prefilter_y: i32,
	sub_x: ^f32,
	sub_y: ^f32,
	glyph: i32,
) {
	// Render at reduced dimensions (prefiltering expands back to full size)
	make_glyph_bitmap_subpixel(info,
		output,
		out_w - (prefilter_x - 1),
		out_h - (prefilter_y - 1),
		out_stride,
		scale_x,
		scale_y,
		shift_x,
		shift_y,
		glyph)

	// Apply horizontal prefilter
	if prefilter_x > 1 {
		h_prefilter(output, out_w, out_h, out_stride, u32(prefilter_x))
	}

	// Apply vertical prefilter
	if prefilter_y > 1 {
		v_prefilter(output, out_w, out_h, out_stride, u32(prefilter_y))
	}

	// Return computed subpixel offsets
	sub_x^ = oversample_shift(prefilter_x)
	sub_y^ = oversample_shift(prefilter_y)
}

make_codepoint_bitmap_subpixel_prefilter :: proc(
	info: ^Font_Info,
	output: [^]u8,
	out_w: i32,
	out_h: i32,
	out_stride: i32,
	scale_x: f32,
	scale_y: f32,
	shift_x: f32,
	shift_y: f32,
	prefilter_x: i32,
	prefilter_y: i32,
	sub_x: ^f32,
	sub_y: ^f32,
	codepoint: i32,
) {
	make_glyph_bitmap_subpixel_prefilter(info, output, out_w, out_h, out_stride,
		scale_x, scale_y, shift_x, shift_y, prefilter_x, prefilter_y,
		sub_x, sub_y, find_glyph_index(info, codepoint))
}

free_bitmap :: proc(bitmap: [^]u8) {
	mem.free(bitmap)
}