// 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-SCANLINE import ( ) // 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 = 512 func lerp(, , , , float32) (, float32) { return + *(-), + *(-) } func clamp(, int32) uint { if < 0 { return 0 } if < { return uint() } return uint() } // 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. type Rasterizer struct { // 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 { return image.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[:] } if haveAccumulateSIMD { floatingAccumulateMaskSIMD(.bufU32, .bufF32) } else { floatingAccumulateMask(.bufU32, .bufF32) } } else { if haveAccumulateSIMD { 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 { if haveAccumulateSIMD { floatingAccumulateOpOverSIMD(.Pix, .bufF32) } else { floatingAccumulateOpOver(.Pix, .bufF32) } } else { if haveAccumulateSIMD { 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 { if haveAccumulateSIMD { floatingAccumulateOpSrcSIMD(.Pix, .bufF32) } else { floatingAccumulateOpSrc(.Pix, .bufF32) } } else { if haveAccumulateSIMD { 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+, ) } } }