| // 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" |
| |
| "golang.org/x/image/math/f32" |
| "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 |
| |
| firstStartPath bool |
| prevSmoothType uint8 |
| prevSmoothPoint f32.Vec2 |
| |
| fill image.Image |
| flatColor color.RGBA |
| flatImage image.Uniform |
| |
| cReg [64]color.RGBA |
| nReg [64]float32 |
| } |
| |
| // 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.prevSmoothPoint = f32.Vec2{} |
| 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 |
| // TODO: check the LODs against z.r.Dy(). |
| } |
| |
| 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) f32.Vec2 { |
| return f32.Vec2{z.absX(x), z.absY(y)} |
| } |
| |
| func (z *Rasterizer) relVec2(x, y float32) f32.Vec2 { |
| pen := z.z.Pen() |
| return f32.Vec2{pen[0] + z.relX(x), pen[1] + 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) f32.Vec2 { |
| pen := z.z.Pen() |
| if z.prevSmoothType != thisSmoothType { |
| return pen |
| } |
| return f32.Vec2{ |
| 2*pen[0] - z.prevSmoothPoint[0], |
| 2*pen[1] - z.prevSmoothPoint[1], |
| } |
| } |
| |
| func (z *Rasterizer) StartPath(adj uint8, x, y float32) { |
| // TODO: gradient fills, not just flat colors. |
| z.flatColor = z.cReg[(z.cSel-adj)&0x3f] |
| z.flatImage.C = &z.flatColor |
| z.fill = &z.flatImage |
| |
| z.z.Reset(z.r.Dx(), z.r.Dy()) |
| 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() { |
| 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) { |
| z.prevSmoothType = smoothTypeNone |
| z.z.ClosePath() |
| z.z.MoveTo(z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) ClosePathRelMoveTo(x, y float32) { |
| z.prevSmoothType = smoothTypeNone |
| z.z.ClosePath() |
| z.z.MoveTo(z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsHLineTo(x float32) { |
| z.prevSmoothType = smoothTypeNone |
| pen := z.z.Pen() |
| z.z.LineTo(f32.Vec2{z.absX(x), pen[1]}) |
| } |
| |
| func (z *Rasterizer) RelHLineTo(x float32) { |
| z.prevSmoothType = smoothTypeNone |
| pen := z.z.Pen() |
| z.z.LineTo(f32.Vec2{pen[0] + z.relX(x), pen[1]}) |
| } |
| |
| func (z *Rasterizer) AbsVLineTo(y float32) { |
| z.prevSmoothType = smoothTypeNone |
| pen := z.z.Pen() |
| z.z.LineTo(f32.Vec2{pen[0], z.absY(y)}) |
| } |
| |
| func (z *Rasterizer) RelVLineTo(y float32) { |
| z.prevSmoothType = smoothTypeNone |
| pen := z.z.Pen() |
| z.z.LineTo(f32.Vec2{pen[0], pen[1] + z.relY(y)}) |
| } |
| |
| func (z *Rasterizer) AbsLineTo(x, y float32) { |
| z.prevSmoothType = smoothTypeNone |
| z.z.LineTo(z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) RelLineTo(x, y float32) { |
| z.prevSmoothType = smoothTypeNone |
| z.z.LineTo(z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsSmoothQuadTo(x, y float32) { |
| z.prevSmoothType = smoothTypeQuad |
| z.prevSmoothPoint = z.implicitSmoothPoint(smoothTypeQuad) |
| z.z.QuadTo(z.prevSmoothPoint, z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) RelSmoothQuadTo(x, y float32) { |
| z.prevSmoothType = smoothTypeQuad |
| z.prevSmoothPoint = z.implicitSmoothPoint(smoothTypeQuad) |
| z.z.QuadTo(z.prevSmoothPoint, z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32) { |
| z.prevSmoothType = smoothTypeQuad |
| z.prevSmoothPoint = z.absVec2(x1, y1) |
| z.z.QuadTo(z.prevSmoothPoint, z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32) { |
| z.prevSmoothType = smoothTypeQuad |
| z.prevSmoothPoint = z.relVec2(x1, y1) |
| z.z.QuadTo(z.prevSmoothPoint, z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32) { |
| p1 := z.implicitSmoothPoint(smoothTypeCube) |
| z.prevSmoothType = smoothTypeCube |
| z.prevSmoothPoint = z.absVec2(x2, y2) |
| z.z.CubeTo(p1, z.prevSmoothPoint, z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32) { |
| p1 := z.implicitSmoothPoint(smoothTypeCube) |
| z.prevSmoothType = smoothTypeCube |
| z.prevSmoothPoint = z.relVec2(x2, y2) |
| z.z.CubeTo(p1, z.prevSmoothPoint, z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32) { |
| z.prevSmoothType = smoothTypeCube |
| z.prevSmoothPoint = z.absVec2(x2, y2) |
| z.z.CubeTo(z.absVec2(x1, y1), z.prevSmoothPoint, z.absVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32) { |
| z.prevSmoothType = smoothTypeCube |
| z.prevSmoothPoint = z.relVec2(x2, y2) |
| z.z.CubeTo(z.relVec2(x1, y1), z.prevSmoothPoint, z.relVec2(x, y)) |
| } |
| |
| func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) { |
| z.prevSmoothType = smoothTypeNone |
| // TODO: implement. |
| } |
| |
| func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) { |
| z.prevSmoothType = smoothTypeNone |
| // TODO: implement. |
| } |