// Copyright 2010 The Freetype-Go Authors. All rights reserved.// Use of this source code is governed by your choice of either the// FreeType License or the GNU General Public License version 2 (or// any later version), both of which can be found in the LICENSE file.
// Package raster provides an anti-aliasing 2-D rasterizer.//// 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.//// 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// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
package raster // import "github.com/golang/freetype/raster"import ()// A cell is part of a linked list (for a given yi co-ordinate) of accumulated// area/coverage for the pixel at (xi, yi).type cell struct { xi int area, cover int next int}typeRasterizerstruct {// If false, the default behavior is to use the even-odd winding fill // rule during Rasterize. UseNonZeroWinding bool// An offset (in pixels) to the painted spans. Dx, Dy int// The width of the Rasterizer. The height is implicit in len(cellIndex). width int// 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// The current pen position. a fixed.Point26_6// 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 spanBuf [64]Span}// findCell returns the index in r.cell for the cell corresponding to// (r.xi, r.yi). The cell is created if necessary.func ( *Rasterizer) () int {if .yi < 0 || .yi >= len(.cellIndex) {return -1 } := .xiif < 0 { = -1 } elseif > .width { = .width } , := .cellIndex[.yi], -1for != -1 && .cell[].xi <= {if .cell[].xi == {return } , = .cell[].next, } := len(.cell)if == cap(.cell) { := make([]cell, , 4*)copy(, .cell) .cell = [0 : +1] } else { .cell = .cell[0 : +1] } .cell[] = cell{, 0, 0, }if == -1 { .cellIndex[.yi] = } else { .cell[].next = }return}// saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi).func ( *Rasterizer) () {if .area != 0 || .cover != 0 { := .findCell()if != -1 { .cell[].area += .area .cell[].cover += .cover } .area = 0 .cover = 0 }}// setCell sets the (xi, yi) cell that r is accumulating area/coverage for.func ( *Rasterizer) (, int) {if .xi != || .yi != { .saveCell() .xi, .yi = , }}// scan accumulates area/coverage for the yi'th scanline, going from// x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates)// and from y0f to y1f fractional vertical units within that scanline.func ( *Rasterizer) ( int, , , , fixed.Int26_6) {// Break the 26.6 fixed point X co-ordinates into integral and fractional parts. := int() / 64 := - fixed.Int26_6(64*) := int() / 64 := - fixed.Int26_6(64*)// A perfectly horizontal scan.if == { .setCell(, )return } , := -, -// A single cell scan.if == { .area += int(( + ) * ) .cover += int()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, // or 64 units in 26.6 fixed point format.var ( , , , fixed.Int26_6int )if > 0 { , = (64-)*, , , = 0, 64, 1 } else { , = *, - , , = 64, 0, -1 } , := /, %if < 0 { -= 1 += }// Do the first cell. , := , .area += int(( + ) * ) .cover += int() , = +, + .setCell(, )if != {// Do all the intermediate cells. = 64 * ( - + ) , := /, %if < 0 { -= 1 += } -= for != { = += if >= 0 { += 1 -= } .area += int(64 * ) .cover += int() , = +, + .setCell(, ) } }// Do the last cell. = - .area += int(( + ) * ) .cover += int()}// Start starts a new curve at the given point.func ( *Rasterizer) ( fixed.Point26_6) { .setCell(int(.X/64), int(.Y/64)) .a = }// Add1 adds a linear segment to the current curve.func ( *Rasterizer) ( fixed.Point26_6) { , := .a.X, .a.Y , := .X, .Y , := -, -// Break the 26.6 fixed point Y co-ordinates into integral and fractional // parts. := int() / 64 := - fixed.Int26_6(64*) := int() / 64 := - fixed.Int26_6(64*)if == {// There is only one scanline. .scan(, , , , ) } elseif == 0 {// This is a vertical line segment. We avoid calling r.scan and instead // manipulate r.area and r.cover directly.var ( , fixed.Int26_6int )if > 0 { , , = 0, 64, 1 } else { , , = 64, 0, -1 } , := int()/64, := (int() - (64 * )) * 2// Do the first pixel. := int( - ) := int( * ) .area += .cover += += .setCell(, )// Do all the intermediate pixels. = int( - ) = int( * )for != { .area += .cover += += .setCell(, ) }// Do the last pixel. = int( - ) = int( * ) .area += .cover += } else {// 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.var ( , , , fixed.Int26_6int )if > 0 { , = (64-)*, , , = 0, 64, 1 } else { , = *, - , , = 64, 0, -1 } , := /, %if < 0 { -= 1 += }// Do the first scanline. , := , .scan(, , , +, ) , = +, + .setCell(int()/64, )if != {// Do all the intermediate scanlines. = 64 * , := /, %if < 0 { -= 1 += } -= for != { = += if >= 0 { += 1 -= } .scan(, , , +, ) , = +, + .setCell(int()/64, ) } }// Do the last scanline. .scan(, , , , ) }// The next lineTo starts from b. .a = }// Add2 adds a quadratic segment to the current curve.func ( *Rasterizer) (, 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. := maxAbs(.a.X-2*.X+.X, .a.Y-2*.Y+.Y) / fixed.Int26_6(.splitScale2) := 0for > 0 { /= 4 ++ }// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit // is 16.const = 16if > {panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa()) }// Recursively decompose the curve nSplit levels deep.var ( [2* + 3]fixed.Point26_6 [ + 1]intint ) [0] = [0] = [1] = [2] = .afor >= 0 { := [] := [2*:]if > 0 {// 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. := [1].X [4].X = [2].X [3].X = ([4].X + ) / 2 [1].X = ([0].X + ) / 2 [2].X = ([1].X + [3].X) / 2 := [1].Y [4].Y = [2].Y [3].Y = ([4].Y + ) / 2 [1].Y = ([0].Y + ) / 2 [2].Y = ([1].Y + [3].Y) / 2// The two shorter curves have one less split to do. [] = - 1 [+1] = - 1 ++ } else {// Replace the level-0 quadratic with a two-linear-piece // approximation. := ([0].X + 2*[1].X + [2].X) / 4 := ([0].Y + 2*[1].Y + [2].Y) / 4 .Add1(fixed.Point26_6{, }) .Add1([0]) -- } }}// Add3 adds a cubic segment to the current curve.func ( *Rasterizer) (, , fixed.Point26_6) {// Calculate nSplit (the number of recursive decompositions) based on how // 'curvy' it is. := maxAbs(.a.X-3*(.X+.X)+.X, .a.Y-3*(.Y+.Y)+.Y) / fixed.Int26_6(.splitScale2) := maxAbs(.a.X-2*.X+.X, .a.Y-2*.Y+.Y) / fixed.Int26_6(.splitScale3) := 0for > 0 || > 0 { /= 8 /= 4 ++ }// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so // maxNsplit is 16.const = 16if > {panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa()) }// Recursively decompose the curve nSplit levels deep.var ( [3* + 4]fixed.Point26_6 [ + 1]intint ) [0] = [0] = [1] = [2] = [3] = .afor >= 0 { := [] := [3*:]if > 0 {// 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. := ([0].X + [1].X) / 2 := ([1].X + [2].X) / 2 := ([2].X + [3].X) / 2 [6].X = [3].X [5].X = [1].X = [2].X = ( + ) / 2 [4].X = ( + ) / 2 [3].X = ([2].X + [4].X) / 2 := ([0].Y + [1].Y) / 2 := ([1].Y + [2].Y) / 2 := ([2].Y + [3].Y) / 2 [6].Y = [3].Y [5].Y = [1].Y = [2].Y = ( + ) / 2 [4].Y = ( + ) / 2 [3].Y = ([2].Y + [4].Y) / 2// The two shorter curves have one less split to do. [] = - 1 [+1] = - 1 ++ } else {// Replace the level-0 cubic with a two-linear-piece approximation. := ([0].X + 3*([1].X+[2].X) + [3].X) / 8 := ([0].Y + 3*([1].Y+[2].Y) + [3].Y) / 8 .Add1(fixed.Point26_6{, }) .Add1([0]) -- } }}// AddPath adds the given Path.func ( *Rasterizer) ( Path) {for := 0; < len(); {switch [] {case0: .Start(fixed.Point26_6{[+1], [+2]}, ) += 4case1: .Add1(fixed.Point26_6{[+1], [+2]}, ) += 4case2: .Add2(fixed.Point26_6{[+1], [+2]},fixed.Point26_6{[+3], [+4]}, ) += 6case3: .Add3(fixed.Point26_6{[+1], [+2]},fixed.Point26_6{[+3], [+4]},fixed.Point26_6{[+5], [+6]}, ) += 8default:panic("freetype/raster: bad path") } }}// AddStroke adds a stroked Path.func ( *Rasterizer) ( Path, fixed.Int26_6, Capper, Joiner) {Stroke(, , , , )}// 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 0xffff. The// conversion of area values greater than this depends on the winding rule:// even-odd or non-zero.func ( *Rasterizer) ( int) uint32 {// 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 16-bit // alpha. := ( + 1) >> 1if < 0 { = - } := uint32()if .UseNonZeroWinding {if > 0x0fff { = 0x0fff } } else { &= 0x1fffif > 0x1000 { = 0x2000 - } elseif == 0x1000 { = 0x0fff } }// alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to // 16-bit alpha.return <<4 | >>8}// 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.func ( *Rasterizer) ( Painter) { .saveCell() := 0for := 0; < len(.cellIndex); ++ { , := 0, 0for := .cellIndex[]; != -1; = .cell[].next {if != 0 && .cell[].xi > { := .areaToAlpha( * 64 * 2)if != 0 { , := , .cell[].xiif < 0 { = 0 }if >= .width { = .width }if < { .spanBuf[] = Span{ + .Dy, + .Dx, + .Dx, } ++ } } } += .cell[].cover := .areaToAlpha(*64*2 - .cell[].area) = .cell[].xi + 1if != 0 { , := .cell[].xi, if < 0 { = 0 }if >= .width { = .width }if < { .spanBuf[] = Span{ + .Dy, + .Dx, + .Dx, } ++ } }if > len(.spanBuf)-2 { .Paint(.spanBuf[:], false) = 0 } } } .Paint(.spanBuf[:], true)}// Clear cancels any previous calls to r.Start or r.AddXxx.func ( *Rasterizer) () { .a = fixed.Point26_6{} .xi = 0 .yi = 0 .area = 0 .cover = 0 .cell = .cell[:0]for := 0; < len(.cellIndex); ++ { .cellIndex[] = -1 }}// SetBounds sets the maximum width and height of the rasterized image and// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.func ( *Rasterizer) (, int) {if < 0 { = 0 }if < 0 { = 0 }// Use the same ssN heuristic as the C Freetype (version 2.4.0) // implementation. , := 32, 16if > 24 || > 24 { , = 2*, 2*if > 120 || > 120 { , = 2*, 2* } } .width = .splitScale2 = .splitScale3 = .cell = .cellBuf[:0]if > len(.cellIndexBuf) { .cellIndex = make([]int, ) } else { .cellIndex = .cellIndexBuf[:] } .Clear()}// NewRasterizer creates a new Rasterizer with the given bounds.func (, int) *Rasterizer { := new(Rasterizer) .SetBounds(, )return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.