123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- // 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.
- package iconvg
- import (
- "image"
- "image/color"
- "image/draw"
- "math"
- "golang.org/x/exp/shiny/iconvg/internal/gradient"
- "golang.org/x/image/math/f64"
- "golang.org/x/image/vector"
- )
- const (
- smoothTypeNone = iota
- smoothTypeQuad
- smoothTypeCube
- )
- // Rasterizer is a Destination that draws an IconVG graphic onto a raster
- // image.
- //
- // The zero value is usable, in that it has no raster image to draw onto, so
- // that calling Decode with this Destination is a no-op (other than checking
- // the encoded form for errors in the byte code). Call SetDstImage to change
- // the raster image, before calling Decode or between calls to Decode.
- type Rasterizer struct {
- z vector.Rasterizer
- dst draw.Image
- r image.Rectangle
- drawOp draw.Op
- // scale and bias transforms the metadata.ViewBox rectangle to the (0, 0) -
- // (r.Dx(), r.Dy()) rectangle.
- scaleX float32
- biasX float32
- scaleY float32
- biasY float32
- metadata Metadata
- lod0 float32
- lod1 float32
- cSel uint8
- nSel uint8
- disabled bool
- firstStartPath bool
- prevSmoothType uint8
- prevSmoothPointX float32
- prevSmoothPointY float32
- fill image.Image
- flatColor color.RGBA
- flatImage image.Uniform
- gradient gradient.Gradient
- cReg [64]color.RGBA
- nReg [64]float32
- stops [64]gradient.Stop
- }
- // SetDstImage sets the Rasterizer to draw onto a destination image, given by
- // dst and r, with the given compositing operator.
- //
- // The IconVG graphic (which does not have a fixed size in pixels) will be
- // scaled in the X and Y dimensions to fit the rectangle r. The scaling factors
- // may differ in the two dimensions.
- func (z *Rasterizer) SetDstImage(dst draw.Image, r image.Rectangle, drawOp draw.Op) {
- z.dst = dst
- if r.Empty() {
- r = image.Rectangle{}
- }
- z.r = r
- z.drawOp = drawOp
- z.recalcTransform()
- }
- // Reset resets the Rasterizer for the given Metadata.
- func (z *Rasterizer) Reset(m Metadata) {
- z.metadata = m
- z.lod0 = 0
- z.lod1 = positiveInfinity
- z.cSel = 0
- z.nSel = 0
- z.firstStartPath = true
- z.prevSmoothType = smoothTypeNone
- z.prevSmoothPointX = 0
- z.prevSmoothPointY = 0
- z.cReg = m.Palette
- z.nReg = [64]float32{}
- z.recalcTransform()
- }
- func (z *Rasterizer) recalcTransform() {
- z.scaleX = float32(z.r.Dx()) / (z.metadata.ViewBox.Max[0] - z.metadata.ViewBox.Min[0])
- z.biasX = -z.metadata.ViewBox.Min[0]
- z.scaleY = float32(z.r.Dy()) / (z.metadata.ViewBox.Max[1] - z.metadata.ViewBox.Min[1])
- z.biasY = -z.metadata.ViewBox.Min[1]
- }
- func (z *Rasterizer) SetCSel(cSel uint8) { z.cSel = cSel & 0x3f }
- func (z *Rasterizer) SetNSel(nSel uint8) { z.nSel = nSel & 0x3f }
- func (z *Rasterizer) SetCReg(adj uint8, incr bool, c Color) {
- z.cReg[(z.cSel-adj)&0x3f] = c.Resolve(&z.metadata.Palette, &z.cReg)
- if incr {
- z.cSel++
- }
- }
- func (z *Rasterizer) SetNReg(adj uint8, incr bool, f float32) {
- z.nReg[(z.nSel-adj)&0x3f] = f
- if incr {
- z.nSel++
- }
- }
- func (z *Rasterizer) SetLOD(lod0, lod1 float32) {
- z.lod0, z.lod1 = lod0, lod1
- }
- func (z *Rasterizer) unabsX(x float32) float32 { return x/z.scaleX - z.biasX }
- func (z *Rasterizer) unabsY(y float32) float32 { return y/z.scaleY - z.biasY }
- func (z *Rasterizer) absX(x float32) float32 { return z.scaleX * (x + z.biasX) }
- func (z *Rasterizer) absY(y float32) float32 { return z.scaleY * (y + z.biasY) }
- func (z *Rasterizer) relX(x float32) float32 { return z.scaleX * x }
- func (z *Rasterizer) relY(y float32) float32 { return z.scaleY * y }
- func (z *Rasterizer) absVec2(x, y float32) (zx, zy float32) {
- return z.absX(x), z.absY(y)
- }
- func (z *Rasterizer) relVec2(x, y float32) (zx, zy float32) {
- px, py := z.z.Pen()
- return px + z.relX(x), py + z.relY(y)
- }
- // implicitSmoothPoint returns the implicit control point for smooth-quadratic
- // and smooth-cubic Bézier curves.
- //
- // https://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands says, "The first
- // control point is assumed to be the reflection of the second control point on
- // the previous command relative to the current point. (If there is no previous
- // command or if the previous command was not [a quadratic or cubic command],
- // assume the first control point is coincident with the current point.)"
- func (z *Rasterizer) implicitSmoothPoint(thisSmoothType uint8) (zx, zy float32) {
- px, py := z.z.Pen()
- if z.prevSmoothType != thisSmoothType {
- return px, py
- }
- return 2*px - z.prevSmoothPointX, 2*py - z.prevSmoothPointY
- }
- func (z *Rasterizer) initGradient(rgba color.RGBA) (ok bool) {
- nStops := int(rgba.R & 0x3f)
- cBase := int(rgba.G & 0x3f)
- nBase := int(rgba.B & 0x3f)
- prevN := negativeInfinity
- for i := 0; i < nStops; i++ {
- c := z.cReg[(cBase+i)&0x3f]
- if !validAlphaPremulColor(c) {
- return false
- }
- n := z.nReg[(nBase+i)&0x3f]
- if !(0 <= n && n <= 1) || !(n > prevN) {
- return false
- }
- prevN = n
- z.stops[i] = gradient.Stop{
- Offset: float64(n),
- RGBA64: color.RGBA64{
- R: uint16(c.R) * 0x101,
- G: uint16(c.G) * 0x101,
- B: uint16(c.B) * 0x101,
- A: uint16(c.A) * 0x101,
- },
- }
- }
- // The affine transformation matrix in the IconVG graphic, stored in 6
- // contiguous NREG registers, goes from graphic coordinate space (i.e. the
- // metadata viewBox) to the gradient coordinate space. We need it to start
- // in pixel space, not graphic coordinate space.
- invZSX := 1 / float64(z.scaleX)
- invZSY := 1 / float64(z.scaleY)
- zBX := float64(z.biasX)
- zBY := float64(z.biasY)
- a := float64(z.nReg[(nBase-6)&0x3f])
- b := float64(z.nReg[(nBase-5)&0x3f])
- c := float64(z.nReg[(nBase-4)&0x3f])
- d := float64(z.nReg[(nBase-3)&0x3f])
- e := float64(z.nReg[(nBase-2)&0x3f])
- f := float64(z.nReg[(nBase-1)&0x3f])
- pix2Grad := f64.Aff3{
- a * invZSX,
- b * invZSY,
- c - a*zBX - b*zBY,
- d * invZSX,
- e * invZSY,
- f - d*zBX - e*zBY,
- }
- shape := gradient.ShapeLinear
- if (rgba.B>>6)&0x01 != 0 {
- shape = gradient.ShapeRadial
- }
- z.gradient.Init(
- shape,
- gradient.Spread(rgba.G>>6),
- pix2Grad,
- z.stops[:nStops],
- )
- return true
- }
- func (z *Rasterizer) StartPath(adj uint8, x, y float32) {
- z.flatColor = z.cReg[(z.cSel-adj)&0x3f]
- if validAlphaPremulColor(z.flatColor) {
- z.flatImage.C = &z.flatColor
- z.fill = &z.flatImage
- z.disabled = z.flatColor.A == 0
- } else if z.flatColor.A == 0x00 && z.flatColor.B&0x80 != 0 {
- z.fill = &z.gradient
- z.disabled = !z.initGradient(z.flatColor)
- } else {
- z.fill = nil
- z.disabled = true
- }
- width, height := z.r.Dx(), z.r.Dy()
- h := float32(height)
- z.disabled = z.disabled || !(z.lod0 <= h && h < z.lod1)
- if z.disabled {
- return
- }
- z.z.Reset(width, height)
- if z.firstStartPath {
- z.firstStartPath = false
- z.z.DrawOp = z.drawOp
- }
- z.prevSmoothType = smoothTypeNone
- z.z.MoveTo(z.absVec2(x, y))
- }
- func (z *Rasterizer) ClosePathEndPath() {
- if z.disabled {
- return
- }
- z.z.ClosePath()
- if z.dst == nil {
- return
- }
- z.z.Draw(z.dst, z.r, z.fill, image.Point{})
- }
- func (z *Rasterizer) ClosePathAbsMoveTo(x, y float32) {
- if z.disabled {
- return
- }
- z.prevSmoothType = smoothTypeNone
- z.z.ClosePath()
- z.z.MoveTo(z.absVec2(x, y))
- }
- func (z *Rasterizer) ClosePathRelMoveTo(x, y float32) {
- if z.disabled {
- return
- }
- z.prevSmoothType = smoothTypeNone
- z.z.ClosePath()
- z.z.MoveTo(z.relVec2(x, y))
- }
- func (z *Rasterizer) AbsHLineTo(x float32) {
- if z.disabled {
- return
- }
- _, py := z.z.Pen()
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(z.absX(x), py)
- }
- func (z *Rasterizer) RelHLineTo(x float32) {
- if z.disabled {
- return
- }
- px, py := z.z.Pen()
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(px+z.relX(x), py)
- }
- func (z *Rasterizer) AbsVLineTo(y float32) {
- if z.disabled {
- return
- }
- px, _ := z.z.Pen()
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(px, z.absY(y))
- }
- func (z *Rasterizer) RelVLineTo(y float32) {
- if z.disabled {
- return
- }
- px, py := z.z.Pen()
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(px, py+z.relY(y))
- }
- func (z *Rasterizer) AbsLineTo(x, y float32) {
- if z.disabled {
- return
- }
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(z.absVec2(x, y))
- }
- func (z *Rasterizer) RelLineTo(x, y float32) {
- if z.disabled {
- return
- }
- z.prevSmoothType = smoothTypeNone
- z.z.LineTo(z.relVec2(x, y))
- }
- func (z *Rasterizer) AbsSmoothQuadTo(x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
- x, y = z.absVec2(x, y)
- z.prevSmoothType = smoothTypeQuad
- z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
- z.z.QuadTo(x1, y1, x, y)
- }
- func (z *Rasterizer) RelSmoothQuadTo(x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 := z.implicitSmoothPoint(smoothTypeQuad)
- x, y = z.relVec2(x, y)
- z.prevSmoothType = smoothTypeQuad
- z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
- z.z.QuadTo(x1, y1, x, y)
- }
- func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 = z.absVec2(x1, y1)
- x, y = z.absVec2(x, y)
- z.prevSmoothType = smoothTypeQuad
- z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
- z.z.QuadTo(x1, y1, x, y)
- }
- func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 = z.relVec2(x1, y1)
- x, y = z.relVec2(x, y)
- z.prevSmoothType = smoothTypeQuad
- z.prevSmoothPointX, z.prevSmoothPointY = x1, y1
- z.z.QuadTo(x1, y1, x, y)
- }
- func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
- x2, y2 = z.absVec2(x2, y2)
- x, y = z.absVec2(x, y)
- z.prevSmoothType = smoothTypeCube
- z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
- z.z.CubeTo(x1, y1, x2, y2, x, y)
- }
- func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 := z.implicitSmoothPoint(smoothTypeCube)
- x2, y2 = z.relVec2(x2, y2)
- x, y = z.relVec2(x, y)
- z.prevSmoothType = smoothTypeCube
- z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
- z.z.CubeTo(x1, y1, x2, y2, x, y)
- }
- func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 = z.absVec2(x1, y1)
- x2, y2 = z.absVec2(x2, y2)
- x, y = z.absVec2(x, y)
- z.prevSmoothType = smoothTypeCube
- z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
- z.z.CubeTo(x1, y1, x2, y2, x, y)
- }
- func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32) {
- if z.disabled {
- return
- }
- x1, y1 = z.relVec2(x1, y1)
- x2, y2 = z.relVec2(x2, y2)
- x, y = z.relVec2(x, y)
- z.prevSmoothType = smoothTypeCube
- z.prevSmoothPointX, z.prevSmoothPointY = x2, y2
- z.z.CubeTo(x1, y1, x2, y2, x, y)
- }
- func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
- if z.disabled {
- return
- }
- z.prevSmoothType = smoothTypeNone
- // We follow the "Conversion from endpoint to center parameterization"
- // algorithm as per
- // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
- // There seems to be a bug in the spec's "implementation notes".
- //
- // Actual implementations, such as
- // - https://git.gnome.org/browse/librsvg/tree/rsvg-path.c
- // - http://svn.apache.org/repos/asf/xmlgraphics/batik/branches/svg11/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java
- // - https://java.net/projects/svgsalamander/sources/svn/content/trunk/svg-core/src/main/java/com/kitfox/svg/pathcmd/Arc.java
- // - https://github.com/millermedeiros/SVGParser/blob/master/com/millermedeiros/geom/SVGArc.as
- // do something slightly different (marked with a †).
- // (†) The Abs isn't part of the spec. Neither is checking that Rx and Ry
- // are non-zero (and non-NaN).
- Rx := math.Abs(float64(rx))
- Ry := math.Abs(float64(ry))
- if !(Rx > 0 && Ry > 0) {
- z.z.LineTo(x, y)
- return
- }
- // We work in IconVG coordinates (e.g. from -32 to +32 by default), rather
- // than destination image coordinates (e.g. the width of the dst image),
- // since the rx and ry radii also need to be scaled, but their scaling
- // factors can be different, and aren't trivial to calculate due to
- // xAxisRotation.
- //
- // We convert back to destination image coordinates via absX and absY calls
- // later, during arcSegmentTo.
- penX, penY := z.z.Pen()
- x1 := float64(z.unabsX(penX))
- y1 := float64(z.unabsY(penY))
- x2 := float64(x)
- y2 := float64(y)
- phi := 2 * math.Pi * float64(xAxisRotation)
- // Step 1: Compute (x1′, y1′)
- halfDx := (x1 - x2) / 2
- halfDy := (y1 - y2) / 2
- cosPhi := math.Cos(phi)
- sinPhi := math.Sin(phi)
- x1Prime := +cosPhi*halfDx + sinPhi*halfDy
- y1Prime := -sinPhi*halfDx + cosPhi*halfDy
- // Step 2: Compute (cx′, cy′)
- rxSq := Rx * Rx
- rySq := Ry * Ry
- x1PrimeSq := x1Prime * x1Prime
- y1PrimeSq := y1Prime * y1Prime
- // (†) Check that the radii are large enough.
- radiiCheck := x1PrimeSq/rxSq + y1PrimeSq/rySq
- if radiiCheck > 1 {
- c := math.Sqrt(radiiCheck)
- Rx *= c
- Ry *= c
- rxSq = Rx * Rx
- rySq = Ry * Ry
- }
- denom := rxSq*y1PrimeSq + rySq*x1PrimeSq
- step2 := 0.0
- if a := rxSq*rySq/denom - 1; a > 0 {
- step2 = math.Sqrt(a)
- }
- if largeArc == sweep {
- step2 = -step2
- }
- cxPrime := +step2 * Rx * y1Prime / Ry
- cyPrime := -step2 * Ry * x1Prime / Rx
- // Step 3: Compute (cx, cy) from (cx′, cy′)
- cx := +cosPhi*cxPrime - sinPhi*cyPrime + (x1+x2)/2
- cy := +sinPhi*cxPrime + cosPhi*cyPrime + (y1+y2)/2
- // Step 4: Compute θ1 and Δθ
- ax := (+x1Prime - cxPrime) / Rx
- ay := (+y1Prime - cyPrime) / Ry
- bx := (-x1Prime - cxPrime) / Rx
- by := (-y1Prime - cyPrime) / Ry
- theta1 := angle(1, 0, ax, ay)
- deltaTheta := angle(ax, ay, bx, by)
- if sweep {
- if deltaTheta < 0 {
- deltaTheta += 2 * math.Pi
- }
- } else {
- if deltaTheta > 0 {
- deltaTheta -= 2 * math.Pi
- }
- }
- // This ends the
- // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
- // algorithm. What follows below is specific to this implementation.
- // We approximate an arc by one or more cubic Bézier curves.
- n := int(math.Ceil(math.Abs(deltaTheta) / (math.Pi/2 + 0.001)))
- for i := 0; i < n; i++ {
- z.arcSegmentTo(cx, cy,
- theta1+deltaTheta*float64(i+0)/float64(n),
- theta1+deltaTheta*float64(i+1)/float64(n),
- Rx, Ry, cosPhi, sinPhi,
- )
- }
- }
- // arcSegmentTo approximates an arc by a cubic Bézier curve. The mathematical
- // formulae for the control points are the same as that used by librsvg.
- func (z *Rasterizer) arcSegmentTo(cx, cy, theta1, theta2, rx, ry, cosPhi, sinPhi float64) {
- halfDeltaTheta := (theta2 - theta1) * 0.5
- q := math.Sin(halfDeltaTheta * 0.5)
- t := (8 * q * q) / (3 * math.Sin(halfDeltaTheta))
- cos1 := math.Cos(theta1)
- sin1 := math.Sin(theta1)
- cos2 := math.Cos(theta2)
- sin2 := math.Sin(theta2)
- x1 := rx * (+cos1 - t*sin1)
- y1 := ry * (+sin1 + t*cos1)
- x2 := rx * (+cos2 + t*sin2)
- y2 := ry * (+sin2 - t*cos2)
- x3 := rx * (+cos2)
- y3 := ry * (+sin2)
- z.z.CubeTo(
- z.absX(float32(cx+cosPhi*x1-sinPhi*y1)),
- z.absY(float32(cy+sinPhi*x1+cosPhi*y1)),
- z.absX(float32(cx+cosPhi*x2-sinPhi*y2)),
- z.absY(float32(cy+sinPhi*x2+cosPhi*y2)),
- z.absX(float32(cx+cosPhi*x3-sinPhi*y3)),
- z.absY(float32(cy+sinPhi*x3+cosPhi*y3)),
- )
- }
- func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
- ax, ay := z.relVec2(x, y)
- z.AbsArcTo(rx, ry, xAxisRotation, largeArc, sweep, z.unabsX(ax), z.unabsY(ay))
- }
- // angle returns the angle between the u and v vectors.
- func angle(ux, uy, vx, vy float64) float64 {
- uNorm := math.Sqrt(ux*ux + uy*uy)
- vNorm := math.Sqrt(vx*vx + vy*vy)
- norm := uNorm * vNorm
- cos := (ux*vx + uy*vy) / norm
- ret := 0.0
- if cos <= -1 {
- ret = math.Pi
- } else if cos >= +1 {
- ret = 0
- } else {
- ret = math.Acos(cos)
- }
- if ux*vy < uy*vx {
- return -ret
- }
- return +ret
- }
|