// Copyright 2016 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.//go:generate go run gen.go//go:generate asmfmt -w acc_amd64.s// asmfmt is https://github.com/klauspost/asmfmt
// Package vector provides a rasterizer for 2-D vector graphics.
package vector // import "golang.org/x/image/vector"// The rasterizer's design follows// https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445//// Proof of concept code is in// https://github.com/google/font-go//// See also:// http://nothings.org/gamedev/rasterize/// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm// https://people.gnome.org/~mathieu/libart/internals.html#INTERNALS-SCANLINEimport ()// floatingPointMathThreshold is the width or height above which the rasterizer// chooses to used floating point math instead of fixed point math.//// Both implementations of line segmentation rasterization (see raster_fixed.go// and raster_floating.go) implement the same algorithm (in ideal, infinite// precision math) but they perform differently in practice. The fixed point// math version is roughtly 1.25x faster (on GOARCH=amd64) on the benchmarks,// but at sufficiently large scales, the computations will overflow and hence// show rendering artifacts. The floating point math version has more// consistent quality over larger scales, but it is significantly slower.//// This constant determines when to use the faster implementation and when to// use the better quality implementation.//// The rationale for this particular value is that TestRasterizePolygon in// vector_test.go checks the rendering quality of polygon edges at various// angles, inscribed in a circle of diameter 512. It may be that a higher value// would still produce acceptable quality, but 512 seems to work.const floatingPointMathThreshold = 512func lerp(, , , , float32) (, float32) {return + *(-), + *(-)}func clamp(, int32) uint {if < 0 {return0 }if < {returnuint() }returnuint()}// NewRasterizer returns a new Rasterizer whose rendered mask image is bounded// by the given width and height.func (, int) *Rasterizer { := &Rasterizer{} .Reset(, )return}// Raster is a 2-D vector graphics rasterizer.//// The zero value is usable, in that it is a Rasterizer whose rendered mask// image has zero width and zero height. Call Reset to change its bounds.typeRasterizerstruct {// bufXxx are buffers of float32 or uint32 values, holding either the // individual or cumulative area values. // // We don't actually need both values at any given time, and to conserve // memory, the integration of the individual to the cumulative could modify // the buffer in place. In other words, we could use a single buffer, say // of type []uint32, and add some math.Float32bits and math.Float32frombits // calls to satisfy the compiler's type checking. As of Go 1.7, though, // there is a performance penalty between: // bufF32[i] += x // and // bufU32[i] = math.Float32bits(x + math.Float32frombits(bufU32[i])) // // See golang.org/issue/17220 for some discussion. bufF32 []float32 bufU32 []uint32 useFloatingPointMath bool size image.Point firstX float32 firstY float32 penX float32 penY float32// DrawOp is the operator used for the Draw method. // // The zero value is draw.Over. DrawOp draw.Op// TODO: an exported field equivalent to the mask point in the // draw.DrawMask function in the stdlib image/draw package?}// Reset resets a Rasterizer as if it was just returned by NewRasterizer.//// This includes setting z.DrawOp to draw.Over.func ( *Rasterizer) (, int) { .size = image.Point{, } .firstX = 0 .firstY = 0 .penX = 0 .penY = 0 .DrawOp = draw.Over .setUseFloatingPointMath( > floatingPointMathThreshold || > floatingPointMathThreshold)}func ( *Rasterizer) ( bool) { .useFloatingPointMath = // Make z.bufF32 or z.bufU32 large enough to hold width * height samples.if .useFloatingPointMath {if := .size.X * .size.Y; > cap(.bufF32) { .bufF32 = make([]float32, ) } else { .bufF32 = .bufF32[:]for := range .bufF32 { .bufF32[] = 0 } } } else {if := .size.X * .size.Y; > cap(.bufU32) { .bufU32 = make([]uint32, ) } else { .bufU32 = .bufU32[:]for := range .bufU32 { .bufU32[] = 0 } } }}// Size returns the width and height passed to NewRasterizer or Reset.func ( *Rasterizer) () image.Point {return .size}// Bounds returns the rectangle from (0, 0) to the width and height passed to// NewRasterizer or Reset.func ( *Rasterizer) () image.Rectangle {returnimage.Rectangle{Max: .size}}// Pen returns the location of the path-drawing pen: the last argument to the// most recent XxxTo call.func ( *Rasterizer) () (, float32) {return .penX, .penY}// ClosePath closes the current path.func ( *Rasterizer) () { .LineTo(.firstX, .firstY)}// MoveTo starts a new path and moves the pen to (ax, ay).//// The coordinates are allowed to be out of the Rasterizer's bounds.func ( *Rasterizer) (, float32) { .firstX = .firstY = .penX = .penY = }// LineTo adds a line segment, from the pen to (bx, by), and moves the pen to// (bx, by).//// The coordinates are allowed to be out of the Rasterizer's bounds.func ( *Rasterizer) (, float32) {if .useFloatingPointMath { .floatingLineTo(, ) } else { .fixedLineTo(, ) }}// QuadTo adds a quadratic Bézier segment, from the pen via (bx, by) to (cx,// cy), and moves the pen to (cx, cy).//// The coordinates are allowed to be out of the Rasterizer's bounds.func ( *Rasterizer) (, , , float32) { , := .penX, .penY := devSquared(, , , , , )if >= 0.333 {const = 3 := 1 + int(math.Sqrt(math.Sqrt(*float64()))) , := float32(0), 1/float32()for := 0; < -1; ++ { += , := lerp(, , , , ) , := lerp(, , , , ) .LineTo(lerp(, , , , )) } } .LineTo(, )}// CubeTo adds a cubic Bézier segment, from the pen via (bx, by) and (cx, cy)// to (dx, dy), and moves the pen to (dx, dy).//// The coordinates are allowed to be out of the Rasterizer's bounds.func ( *Rasterizer) (, , , , , float32) { , := .penX, .penY := devSquared(, , , , , )if := devSquared(, , , , , ); < { = }if >= 0.333 {const = 3 := 1 + int(math.Sqrt(math.Sqrt(*float64()))) , := float32(0), 1/float32()for := 0; < -1; ++ { += , := lerp(, , , , ) , := lerp(, , , , ) , := lerp(, , , , ) , := lerp(, , , , ) , := lerp(, , , , ) .LineTo(lerp(, , , , )) } } .LineTo(, )}// devSquared returns a measure of how curvy the sequence (ax, ay) to (bx, by)// to (cx, cy) is. It determines how many line segments will approximate a// Bézier curve segment.//// http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html// gives the rationale for this evenly spaced heuristic instead of a recursive// de Casteljau approach://// The reason for the subdivision by n is that I expect the "flatness"// computation to be semi-expensive (it's done once rather than on each// potential subdivision) and also because you'll often get fewer subdivisions.// Taking a circular arc as a simplifying assumption (ie a spherical cow),// where I get n, a recursive approach would get 2^⌈lg n⌉, which, if I haven't// made any horrible mistakes, is expected to be 33% more in the limit.func devSquared(, , , , , float32) float32 { := - 2* + := - 2* + return * + *}// Draw implements the Drawer interface from the standard library's image/draw// package.//// The vector paths previously added via the XxxTo calls become the mask for// drawing src onto dst.func ( *Rasterizer) ( draw.Image, image.Rectangle, image.Image, image.Point) {// TODO: adjust r and sp (and mp?) if src.Bounds() doesn't contain // r.Add(sp.Sub(r.Min)).if , := .(*image.Uniform); { , , , := .RGBA()switch dst := .(type) {case *image.Alpha:// Fast path for glyph rendering.if == 0xffff {if .DrawOp == draw.Over { .rasterizeDstAlphaSrcOpaqueOpOver(, ) } else { .rasterizeDstAlphaSrcOpaqueOpSrc(, ) }return }case *image.RGBA:if .DrawOp == draw.Over { .rasterizeDstRGBASrcUniformOpOver(, , , , , ) } else { .rasterizeDstRGBASrcUniformOpSrc(, , , , , ) }return } }if .DrawOp == draw.Over { .rasterizeOpOver(, , , ) } else { .rasterizeOpSrc(, , , ) }}func ( *Rasterizer) () {if .useFloatingPointMath {if := .size.X * .size.Y; > cap(.bufU32) { .bufU32 = make([]uint32, ) } else { .bufU32 = .bufU32[:] }ifhaveAccumulateSIMD {floatingAccumulateMaskSIMD(.bufU32, .bufF32) } else {floatingAccumulateMask(.bufU32, .bufF32) } } else {ifhaveAccumulateSIMD {fixedAccumulateMaskSIMD(.bufU32) } else {fixedAccumulateMask(.bufU32) } }}func ( *Rasterizer) ( *image.Alpha, image.Rectangle) {// TODO: non-zero vs even-odd winding?if == .Bounds() && == .Bounds() {// We bypass the z.accumulateMask step and convert straight from // z.bufF32 or z.bufU32 to dst.Pix.if .useFloatingPointMath {ifhaveAccumulateSIMD {floatingAccumulateOpOverSIMD(.Pix, .bufF32) } else {floatingAccumulateOpOver(.Pix, .bufF32) } } else {ifhaveAccumulateSIMD {fixedAccumulateOpOverSIMD(.Pix, .bufU32) } else {fixedAccumulateOpOver(.Pix, .bufU32) } }return } .accumulateMask() := .Pix[.PixOffset(.Min.X, .Min.Y):]for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { := .bufU32[*.size.X+] := *.Stride + // This formula is like rasterizeOpOver's, simplified for the // concrete dst type and opaque src assumption. := 0xffff - [] = uint8((uint32([])*0x101*/0xffff + ) >> 8) } }}func ( *Rasterizer) ( *image.Alpha, image.Rectangle) {// TODO: non-zero vs even-odd winding?if == .Bounds() && == .Bounds() {// We bypass the z.accumulateMask step and convert straight from // z.bufF32 or z.bufU32 to dst.Pix.if .useFloatingPointMath {ifhaveAccumulateSIMD {floatingAccumulateOpSrcSIMD(.Pix, .bufF32) } else {floatingAccumulateOpSrc(.Pix, .bufF32) } } else {ifhaveAccumulateSIMD {fixedAccumulateOpSrcSIMD(.Pix, .bufU32) } else {fixedAccumulateOpSrc(.Pix, .bufU32) } }return } .accumulateMask() := .Pix[.PixOffset(.Min.X, .Min.Y):]for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { := .bufU32[*.size.X+]// This formula is like rasterizeOpSrc's, simplified for the // concrete dst type and opaque src assumption. [*.Stride+] = uint8( >> 8) } }}func ( *Rasterizer) ( *image.RGBA, image.Rectangle, , , , uint32) { .accumulateMask() := .Pix[.PixOffset(.Min.X, .Min.Y):]for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { := .bufU32[*.size.X+]// This formula is like rasterizeOpOver's, simplified for the // concrete dst type and uniform src assumption. := 0xffff - ( * / 0xffff) := *.Stride + 4* [+0] = uint8(((uint32([+0])*0x101* + *) / 0xffff) >> 8) [+1] = uint8(((uint32([+1])*0x101* + *) / 0xffff) >> 8) [+2] = uint8(((uint32([+2])*0x101* + *) / 0xffff) >> 8) [+3] = uint8(((uint32([+3])*0x101* + *) / 0xffff) >> 8) } }}func ( *Rasterizer) ( *image.RGBA, image.Rectangle, , , , uint32) { .accumulateMask() := .Pix[.PixOffset(.Min.X, .Min.Y):]for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { := .bufU32[*.size.X+]// This formula is like rasterizeOpSrc's, simplified for the // concrete dst type and uniform src assumption. := *.Stride + 4* [+0] = uint8(( * / 0xffff) >> 8) [+1] = uint8(( * / 0xffff) >> 8) [+2] = uint8(( * / 0xffff) >> 8) [+3] = uint8(( * / 0xffff) >> 8) } }}func ( *Rasterizer) ( draw.Image, image.Rectangle, image.Image, image.Point) { .accumulateMask() := color.RGBA64{} := color.Color(&)for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { , , , := .At(.X+, .Y+).RGBA() := .bufU32[*.size.X+]// This algorithm comes from the standard library's image/draw // package. , , , := .At(.Min.X+, .Min.Y+).RGBA() := 0xffff - ( * / 0xffff) .R = uint16((* + *) / 0xffff) .G = uint16((* + *) / 0xffff) .B = uint16((* + *) / 0xffff) .A = uint16((* + *) / 0xffff) .Set(.Min.X+, .Min.Y+, ) } }}func ( *Rasterizer) ( draw.Image, image.Rectangle, image.Image, image.Point) { .accumulateMask() := color.RGBA64{} := color.Color(&)for , := 0, .Max.Y-.Min.Y; < ; ++ {for , := 0, .Max.X-.Min.X; < ; ++ { , , , := .At(.X+, .Y+).RGBA() := .bufU32[*.size.X+]// This algorithm comes from the standard library's image/draw // package. .R = uint16( * / 0xffff) .G = uint16( * / 0xffff) .B = uint16( * / 0xffff) .A = uint16( * / 0xffff) .Set(.Min.X+, .Min.Y+, ) } }}
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.