raster.go 14.8 KB
Newer Older
N
Nigel Tao 已提交
1 2
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
3 4
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
N
Nigel Tao 已提交
5

6
// Package raster provides an anti-aliasing 2-D rasterizer.
N
Nigel Tao 已提交
7
//
8 9 10
// It is part of the larger Freetype suite of font-related packages, but the
// raster package is not specific to font rasterization, and can be used
// standalone without any other Freetype package.
N
Nigel Tao 已提交
11
//
12 13 14
// Rasterization is done by the same area/coverage accumulation algorithm as
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
// description of the area/coverage algorithm is at
N
Nigel Tao 已提交
15
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
N
Nigel Tao 已提交
16
package raster // import "github.com/golang/freetype/raster"
N
Nigel Tao 已提交
17 18 19

import (
	"strconv"
20 21

	"golang.org/x/image/math/fixed"
N
Nigel Tao 已提交
22 23
)

24
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
N
Nigel Tao 已提交
25 26 27 28 29 30 31 32 33 34 35
// area/coverage for the pixel at (xi, yi).
type cell struct {
	xi          int
	area, cover int
	next        int
}

type Rasterizer struct {
	// If false, the default behavior is to use the even-odd winding fill
	// rule during Rasterize.
	UseNonZeroWinding bool
36 37
	// An offset (in pixels) to the painted spans.
	Dx, Dy int
N
Nigel Tao 已提交
38 39 40

	// The width of the Rasterizer. The height is implicit in len(cellIndex).
	width int
41 42 43
	// splitScaleN is the scaling factor used to determine how many times
	// to decompose a quadratic or cubic segment into a linear approximation.
	splitScale2, splitScale3 int
N
Nigel Tao 已提交
44 45

	// The current pen position.
46
	a fixed.Point26_6
N
Nigel Tao 已提交
47 48 49 50 51 52 53 54 55 56 57
	// The current cell and its area/coverage being accumulated.
	xi, yi      int
	area, cover int

	// Saved cells.
	cell []cell
	// Linked list of cells, one per row.
	cellIndex []int
	// Buffers.
	cellBuf      [256]cell
	cellIndexBuf [64]int
58
	spanBuf      [64]Span
N
Nigel Tao 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
}

// findCell returns the index in r.cell for the cell corresponding to
// (r.xi, r.yi). The cell is created if necessary.
func (r *Rasterizer) findCell() int {
	if r.yi < 0 || r.yi >= len(r.cellIndex) {
		return -1
	}
	xi := r.xi
	if xi < 0 {
		xi = -1
	} else if xi > r.width {
		xi = r.width
	}
	i, prev := r.cellIndex[r.yi], -1
	for i != -1 && r.cell[i].xi <= xi {
		if r.cell[i].xi == xi {
			return i
		}
		i, prev = r.cell[i].next, i
	}
	c := len(r.cell)
	if c == cap(r.cell) {
		buf := make([]cell, c, 4*c)
		copy(buf, r.cell)
		r.cell = buf[0 : c+1]
	} else {
		r.cell = r.cell[0 : c+1]
	}
	r.cell[c] = cell{xi, 0, 0, i}
	if prev == -1 {
		r.cellIndex[r.yi] = c
	} else {
		r.cell[prev].next = c
	}
	return c
}

// saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi).
func (r *Rasterizer) saveCell() {
	if r.area != 0 || r.cover != 0 {
		i := r.findCell()
		if i != -1 {
			r.cell[i].area += r.area
			r.cell[i].cover += r.cover
		}
		r.area = 0
		r.cover = 0
	}
}

// setCell sets the (xi, yi) cell that r is accumulating area/coverage for.
func (r *Rasterizer) setCell(xi, yi int) {
	if r.xi != xi || r.yi != yi {
		r.saveCell()
		r.xi, r.yi = xi, yi
	}
}

// scan accumulates area/coverage for the yi'th scanline, going from
// x0 to x1 in the horizontal direction (in 24.8 fixed point co-ordinates)
// and from y0f to y1f fractional vertical units within that scanline.
121 122 123 124 125 126
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) {
	// Break the 26.6 fixed point X co-ordinates into integral and fractional parts.
	x0i := int(x0) / 64
	x0f := x0 - fixed.Int26_6(64*x0i)
	x1i := int(x1) / 64
	x1f := x1 - fixed.Int26_6(64*x1i)
N
Nigel Tao 已提交
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

	// A perfectly horizontal scan.
	if y0f == y1f {
		r.setCell(x1i, yi)
		return
	}
	dx, dy := x1-x0, y1f-y0f
	// A single cell scan.
	if x0i == x1i {
		r.area += int((x0f + x1f) * dy)
		r.cover += int(dy)
		return
	}
	// There are at least two cells. Apart from the first and last cells,
	// all intermediate cells go through the full width of the cell,
142
	// or 64 units in 26.6 fixed point format.
N
Nigel Tao 已提交
143
	var (
144
		p, q, edge0, edge1 fixed.Int26_6
N
Nigel Tao 已提交
145 146 147
		xiDelta            int
	)
	if dx > 0 {
148 149
		p, q = (64-x0f)*dy, dx
		edge0, edge1, xiDelta = 0, 64, 1
N
Nigel Tao 已提交
150 151
	} else {
		p, q = x0f*dy, -dx
152
		edge0, edge1, xiDelta = 64, 0, -1
N
Nigel Tao 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166
	}
	yDelta, yRem := p/q, p%q
	if yRem < 0 {
		yDelta -= 1
		yRem += q
	}
	// Do the first cell.
	xi, y := x0i, y0f
	r.area += int((x0f + edge1) * yDelta)
	r.cover += int(yDelta)
	xi, y = xi+xiDelta, y+yDelta
	r.setCell(xi, yi)
	if xi != x1i {
		// Do all the intermediate cells.
167
		p = 64 * (y1f - y + yDelta)
N
Nigel Tao 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180
		fullDelta, fullRem := p/q, p%q
		if fullRem < 0 {
			fullDelta -= 1
			fullRem += q
		}
		yRem -= q
		for xi != x1i {
			yDelta = fullDelta
			yRem += fullRem
			if yRem >= 0 {
				yDelta += 1
				yRem -= q
			}
181
			r.area += int(64 * yDelta)
N
Nigel Tao 已提交
182 183 184 185 186 187 188 189 190 191 192 193
			r.cover += int(yDelta)
			xi, y = xi+xiDelta, y+yDelta
			r.setCell(xi, yi)
		}
	}
	// Do the last cell.
	yDelta = y1f - y
	r.area += int((edge0 + x1f) * yDelta)
	r.cover += int(yDelta)
}

// Start starts a new curve at the given point.
194 195
func (r *Rasterizer) Start(a fixed.Point26_6) {
	r.setCell(int(a.X/64), int(a.Y/64))
N
Nigel Tao 已提交
196 197 198
	r.a = a
}

199
// Add1 adds a linear segment to the current curve.
200
func (r *Rasterizer) Add1(b fixed.Point26_6) {
N
Nigel Tao 已提交
201 202 203
	x0, y0 := r.a.X, r.a.Y
	x1, y1 := b.X, b.Y
	dx, dy := x1-x0, y1-y0
204 205 206 207 208 209
	// Break the 26.6 fixed point Y co-ordinates into integral and fractional
	// parts.
	y0i := int(y0) / 64
	y0f := y0 - fixed.Int26_6(64*y0i)
	y1i := int(y1) / 64
	y1f := y1 - fixed.Int26_6(64*y1i)
N
Nigel Tao 已提交
210 211 212 213

	if y0i == y1i {
		// There is only one scanline.
		r.scan(y0i, x0, y0f, x1, y1f)
214 215 216 217 218

	} else if dx == 0 {
		// This is a vertical line segment. We avoid calling r.scan and instead
		// manipulate r.area and r.cover directly.
		var (
219
			edge0, edge1 fixed.Int26_6
220 221 222
			yiDelta      int
		)
		if dy > 0 {
223
			edge0, edge1, yiDelta = 0, 64, 1
224
		} else {
225
			edge0, edge1, yiDelta = 64, 0, -1
226
		}
227 228
		x0i, yi := int(x0)/64, y0i
		x0fTimes2 := (int(x0) - (64 * x0i)) * 2
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
		// Do the first pixel.
		dcover := int(edge1 - y0f)
		darea := int(x0fTimes2 * dcover)
		r.area += darea
		r.cover += dcover
		yi += yiDelta
		r.setCell(x0i, yi)
		// Do all the intermediate pixels.
		dcover = int(edge1 - edge0)
		darea = int(x0fTimes2 * dcover)
		for yi != y1i {
			r.area += darea
			r.cover += dcover
			yi += yiDelta
			r.setCell(x0i, yi)
		}
		// Do the last pixel.
		dcover = int(y1f - edge0)
		darea = int(x0fTimes2 * dcover)
		r.area += darea
		r.cover += dcover

N
Nigel Tao 已提交
251
	} else {
252 253 254
		// There are at least two scanlines. Apart from the first and last
		// scanlines, all intermediate scanlines go through the full height of
		// the row, or 64 units in 26.6 fixed point format.
N
Nigel Tao 已提交
255
		var (
256
			p, q, edge0, edge1 fixed.Int26_6
N
Nigel Tao 已提交
257 258 259
			yiDelta            int
		)
		if dy > 0 {
260 261
			p, q = (64-y0f)*dx, dy
			edge0, edge1, yiDelta = 0, 64, 1
N
Nigel Tao 已提交
262 263
		} else {
			p, q = y0f*dx, -dy
264
			edge0, edge1, yiDelta = 64, 0, -1
N
Nigel Tao 已提交
265 266 267 268 269 270 271 272 273 274
		}
		xDelta, xRem := p/q, p%q
		if xRem < 0 {
			xDelta -= 1
			xRem += q
		}
		// Do the first scanline.
		x, yi := x0, y0i
		r.scan(yi, x, y0f, x+xDelta, edge1)
		x, yi = x+xDelta, yi+yiDelta
275
		r.setCell(int(x)/64, yi)
N
Nigel Tao 已提交
276 277
		if yi != y1i {
			// Do all the intermediate scanlines.
278
			p = 64 * dx
N
Nigel Tao 已提交
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
			fullDelta, fullRem := p/q, p%q
			if fullRem < 0 {
				fullDelta -= 1
				fullRem += q
			}
			xRem -= q
			for yi != y1i {
				xDelta = fullDelta
				xRem += fullRem
				if xRem >= 0 {
					xDelta += 1
					xRem -= q
				}
				r.scan(yi, x, edge0, x+xDelta, edge1)
				x, yi = x+xDelta, yi+yiDelta
294
				r.setCell(int(x)/64, yi)
N
Nigel Tao 已提交
295 296 297 298 299 300 301 302 303
			}
		}
		// Do the last scanline.
		r.scan(yi, x, edge0, x1, y1f)
	}
	// The next lineTo starts from b.
	r.a = b
}

304
// Add2 adds a quadratic segment to the current curve.
305 306 307 308 309
func (r *Rasterizer) Add2(b, c fixed.Point26_6) {
	// Calculate nSplit (the number of recursive decompositions) based on how
	// `curvy' it is. Specifically, how much the middle point b deviates from
	// (a+c)/2.
	dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2)
N
Nigel Tao 已提交
310
	nsplit := 0
311 312
	for dev > 0 {
		dev /= 4
N
Nigel Tao 已提交
313 314
		nsplit++
	}
315
	// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit is 16.
N
Nigel Tao 已提交
316 317
	const maxNsplit = 16
	if nsplit > maxNsplit {
318
		panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit))
N
Nigel Tao 已提交
319 320 321
	}
	// Recursively decompose the curve nSplit levels deep.
	var (
322
		pStack [2*maxNsplit + 3]fixed.Point26_6
N
Nigel Tao 已提交
323 324 325 326 327 328 329 330 331
		sStack [maxNsplit + 1]int
		i      int
	)
	sStack[0] = nsplit
	pStack[0] = c
	pStack[1] = b
	pStack[2] = r.a
	for i >= 0 {
		s := sStack[i]
332
		p := pStack[2*i:]
N
Nigel Tao 已提交
333
		if s > 0 {
334 335
			// Split the quadratic curve p[:3] into an equivalent set of two shorter curves:
			// p[:3] and p[2:5]. The new p[4] is the old p[2], and p[0] is unchanged.
336 337 338 339 340 341 342 343 344 345
			mx := p[1].X
			p[4].X = p[2].X
			p[3].X = (p[4].X + mx) / 2
			p[1].X = (p[0].X + mx) / 2
			p[2].X = (p[1].X + p[3].X) / 2
			my := p[1].Y
			p[4].Y = p[2].Y
			p[3].Y = (p[4].Y + my) / 2
			p[1].Y = (p[0].Y + my) / 2
			p[2].Y = (p[1].Y + p[3].Y) / 2
N
Nigel Tao 已提交
346 347 348 349 350 351
			// The two shorter curves have one less split to do.
			sStack[i] = s - 1
			sStack[i+1] = s - 1
			i++
		} else {
			// Replace the level-0 quadratic with a two-linear-piece approximation.
352 353
			midx := (p[0].X + 2*p[1].X + p[2].X) / 4
			midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4
354
			r.Add1(fixed.Point26_6{midx, midy})
355
			r.Add1(p[0])
N
Nigel Tao 已提交
356 357 358 359 360
			i--
		}
	}
}

361
// Add3 adds a cubic segment to the current curve.
362
func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) {
363
	// Calculate nSplit (the number of recursive decompositions) based on how `curvy' it is.
364 365
	dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2)
	dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3)
366 367 368 369 370 371 372 373 374 375 376 377 378
	nsplit := 0
	for dev2 > 0 || dev3 > 0 {
		dev2 /= 8
		dev3 /= 4
		nsplit++
	}
	// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit is 16.
	const maxNsplit = 16
	if nsplit > maxNsplit {
		panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit))
	}
	// Recursively decompose the curve nSplit levels deep.
	var (
379
		pStack [3*maxNsplit + 4]fixed.Point26_6
380 381 382 383 384 385 386 387 388 389 390 391
		sStack [maxNsplit + 1]int
		i      int
	)
	sStack[0] = nsplit
	pStack[0] = d
	pStack[1] = c
	pStack[2] = b
	pStack[3] = r.a
	for i >= 0 {
		s := sStack[i]
		p := pStack[3*i:]
		if s > 0 {
392 393
			// Split the cubic curve p[:4] into an equivalent set of two shorter curves:
			// p[:4] and p[3:7]. The new p[6] is the old p[3], and p[0] is unchanged.
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
			m01x := (p[0].X + p[1].X) / 2
			m12x := (p[1].X + p[2].X) / 2
			m23x := (p[2].X + p[3].X) / 2
			p[6].X = p[3].X
			p[5].X = m23x
			p[1].X = m01x
			p[2].X = (m01x + m12x) / 2
			p[4].X = (m12x + m23x) / 2
			p[3].X = (p[2].X + p[4].X) / 2
			m01y := (p[0].Y + p[1].Y) / 2
			m12y := (p[1].Y + p[2].Y) / 2
			m23y := (p[2].Y + p[3].Y) / 2
			p[6].Y = p[3].Y
			p[5].Y = m23y
			p[1].Y = m01y
			p[2].Y = (m01y + m12y) / 2
			p[4].Y = (m12y + m23y) / 2
			p[3].Y = (p[2].Y + p[4].Y) / 2
			// The two shorter curves have one less split to do.
			sStack[i] = s - 1
			sStack[i+1] = s - 1
			i++
		} else {
			// Replace the level-0 cubic with a two-linear-piece approximation.
			midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8
			midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8
420
			r.Add1(fixed.Point26_6{midx, midy})
421 422 423 424
			r.Add1(p[0])
			i--
		}
	}
N
Nigel Tao 已提交
425 426
}

427 428 429 430 431
// AddPath adds the given Path.
func (r *Rasterizer) AddPath(p Path) {
	for i := 0; i < len(p); {
		switch p[i] {
		case 0:
432 433 434
			r.Start(
				fixed.Point26_6{p[i+1], p[i+2]},
			)
435
			i += 4
436
		case 1:
437 438 439
			r.Add1(
				fixed.Point26_6{p[i+1], p[i+2]},
			)
440
			i += 4
441
		case 2:
442 443 444 445
			r.Add2(
				fixed.Point26_6{p[i+1], p[i+2]},
				fixed.Point26_6{p[i+3], p[i+4]},
			)
446
			i += 6
447
		case 3:
448 449 450 451 452
			r.Add3(
				fixed.Point26_6{p[i+1], p[i+2]},
				fixed.Point26_6{p[i+3], p[i+4]},
				fixed.Point26_6{p[i+5], p[i+6]},
			)
453
			i += 8
454 455 456 457 458 459 460
		default:
			panic("freetype/raster: bad path")
		}
	}
}

// AddStroke adds a stroked Path.
461
func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
462
	Stroke(r, q, width, cr, jr)
463 464
}

465 466
// areaToAlpha converts an area value to a uint32 alpha value. A completely
// filled pixel corresponds to an area of 64*64*2, and an alpha of 1<<32-1. The
N
Nigel Tao 已提交
467 468 469
// conversion of area values greater than this depends on the winding rule:
// even-odd or non-zero.
func (r *Rasterizer) areaToAlpha(area int) uint32 {
470 471 472 473
	// The C Freetype implementation (version 2.3.12) does "alpha := area>>1"
	// without the +1. Round-to-nearest gives a more symmetric result than
	// round-down. The C implementation also returns 8-bit alpha, not 32-bit
	// alpha.
N
Nigel Tao 已提交
474 475 476 477 478 479
	a := (area + 1) >> 1
	if a < 0 {
		a = -a
	}
	alpha := uint32(a)
	if r.UseNonZeroWinding {
480 481
		if alpha > 0x0fff {
			alpha = 0x0fff
N
Nigel Tao 已提交
482 483
		}
	} else {
484 485 486 487 488
		alpha &= 0x1fff
		if alpha > 0x1000 {
			alpha = 0x2000 - alpha
		} else if alpha == 0x1000 {
			alpha = 0x0fff
N
Nigel Tao 已提交
489 490
		}
	}
491 492
	alpha >>= 4
	return alpha * 0x01010101
N
Nigel Tao 已提交
493 494
}

495 496 497 498
// Rasterize converts r's accumulated curves into Spans for p. The Spans passed
// to p are non-overlapping, and sorted by Y and then X. They all have non-zero
// width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final
// Span, which has Y, X0, X1 and A all equal to zero.
N
Nigel Tao 已提交
499 500
func (r *Rasterizer) Rasterize(p Painter) {
	r.saveCell()
501
	s := 0
N
Nigel Tao 已提交
502 503 504 505
	for yi := 0; yi < len(r.cellIndex); yi++ {
		xi, cover := 0, 0
		for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
			if cover != 0 && r.cell[c].xi > xi {
506
				alpha := r.areaToAlpha(cover * 64 * 2)
N
Nigel Tao 已提交
507 508 509 510 511 512 513 514 515
				if alpha != 0 {
					xi0, xi1 := xi, r.cell[c].xi
					if xi0 < 0 {
						xi0 = 0
					}
					if xi1 >= r.width {
						xi1 = r.width
					}
					if xi0 < xi1 {
516
						r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
517
						s++
N
Nigel Tao 已提交
518 519 520 521
					}
				}
			}
			cover += r.cell[c].cover
522
			alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area)
N
Nigel Tao 已提交
523 524 525 526 527 528 529 530 531 532
			xi = r.cell[c].xi + 1
			if alpha != 0 {
				xi0, xi1 := r.cell[c].xi, xi
				if xi0 < 0 {
					xi0 = 0
				}
				if xi1 >= r.width {
					xi1 = r.width
				}
				if xi0 < xi1 {
533
					r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
534
					s++
N
Nigel Tao 已提交
535 536
				}
			}
537
			if s > len(r.spanBuf)-2 {
538
				p.Paint(r.spanBuf[:s], false)
539 540
				s = 0
			}
N
Nigel Tao 已提交
541 542
		}
	}
543
	p.Paint(r.spanBuf[:s], true)
N
Nigel Tao 已提交
544 545
}

546
// Clear cancels any previous calls to r.Start or r.AddXxx.
N
Nigel Tao 已提交
547
func (r *Rasterizer) Clear() {
548
	r.a = fixed.Point26_6{}
N
Nigel Tao 已提交
549 550 551 552
	r.xi = 0
	r.yi = 0
	r.area = 0
	r.cover = 0
553
	r.cell = r.cell[:0]
N
Nigel Tao 已提交
554 555 556 557 558
	for i := 0; i < len(r.cellIndex); i++ {
		r.cellIndex[i] = -1
	}
}

559
// SetBounds sets the maximum width and height of the rasterized image and
560
// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
561
func (r *Rasterizer) SetBounds(width, height int) {
N
Nigel Tao 已提交
562 563 564 565 566 567
	if width < 0 {
		width = 0
	}
	if height < 0 {
		height = 0
	}
568 569 570
	// Use the same ssN heuristic as the C Freetype (version 2.4.0)
	// implementation.
	ss2, ss3 := 32, 16
N
Nigel Tao 已提交
571
	if width > 24 || height > 24 {
572
		ss2, ss3 = 2*ss2, 2*ss3
N
Nigel Tao 已提交
573
		if width > 120 || height > 120 {
574
			ss2, ss3 = 2*ss2, 2*ss3
N
Nigel Tao 已提交
575 576 577
		}
	}
	r.width = width
578 579
	r.splitScale2 = ss2
	r.splitScale3 = ss3
580
	r.cell = r.cellBuf[:0]
N
Nigel Tao 已提交
581 582 583
	if height > len(r.cellIndexBuf) {
		r.cellIndex = make([]int, height)
	} else {
584
		r.cellIndex = r.cellIndexBuf[:height]
N
Nigel Tao 已提交
585
	}
586 587 588 589 590 591 592
	r.Clear()
}

// NewRasterizer creates a new Rasterizer with the given bounds.
func NewRasterizer(width, height int) *Rasterizer {
	r := new(Rasterizer)
	r.SetBounds(width, height)
N
Nigel Tao 已提交
593 594
	return r
}