12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100 |
- // Copyright 2021 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 (
- "bytes"
- "errors"
- "image/color"
- "math"
- )
- // UpgradeToFileFormatVersion1Options are the options to the
- // UpgradeToFileFormatVersion1 function.
- type UpgradeToFileFormatVersion1Options struct {
- // ArcsExpandWithHighResolutionCoordinates is like the
- // Encoder.HighResolutionCoordinates field. It controls whether to favor
- // file size (false) or precision (true) when replacing File Format Version
- // 0's arcs with cubic Bézier curves.
- ArcsExpandWithHighResolutionCoordinates bool
- }
- // UpgradeToFileFormatVersion1 upgrades IconVG data from the 2016 experimental
- // "File Format Version 0" to the 2021 "File Format Version 1".
- //
- // This package (golang.org/x/exp/shiny/iconvg) holds a decoder for FFV0,
- // including this function to convert from FFV0 to FFV1. Different packages
- // (github.com/google/iconvg/src/go/*) decode FFV1.
- //
- // Amongst some new features and other clean-ups, FFV1 sets up the capability
- // for animated vector graphics, therefore removing some FFV0 features (such as
- // arc segments) that can be hard to animate smoothly. The IconvG FFV1 format
- // and its design decisions are discussed at
- // https://github.com/google/iconvg/issues/4#issuecomment-874105547
- func UpgradeToFileFormatVersion1(v0 []byte, opts *UpgradeToFileFormatVersion1Options) (v1 []byte, retErr error) {
- u := &upgrader{}
- if opts != nil {
- u.opts = *opts
- }
- for i := range u.creg {
- u.creg[i] = upgradeColor{
- typ: ColorTypePaletteIndex,
- paletteIndex: uint8(i),
- }
- }
- if !bytes.HasPrefix(v0, magicBytes) {
- return nil, errInvalidMagicIdentifier
- }
- v1 = append(v1, "\x8AIVG"...)
- v0 = v0[4:]
- v1, v0, retErr = u.upgradeMetadata(v1, v0)
- if retErr != nil {
- return nil, retErr
- }
- v1, _, retErr = u.upgradeBytecode(v1, v0)
- if retErr != nil {
- return nil, retErr
- }
- return v1, nil
- }
- const (
- upgradeVerbMoveTo = 0
- upgradeVerbLineTo = 1
- upgradeVerbQuadTo = 2
- upgradeVerbCubeTo = 3
- )
- type upgrader struct {
- opts UpgradeToFileFormatVersion1Options
- // These fields hold the current path's geometry.
- verbs []uint8
- args [][2]float32
- // These fields track most of the FFV0 virtual machine register state. The
- // FFV1 register model is different enough that we don't just translate
- // each FFV0 register-related opcode individually.
- creg [64]upgradeColor
- nreg [64]float32
- csel uint32
- nsel uint32
- fill uint32
- // These fields track the most recent color written to FFV1 register
- // REGS[SEL+7] (and SEL is kept at 56). As a file size optimization, we
- // don't have to emit the first half of "Set REGS[SEL+7] = etc; Use
- // REGS[SEL+7]" if the register already holds the "etc" value.
- regsSel7 color.RGBA
- hasRegsSel7 bool
- // calculatingJumpLOD is whether the upgrader.upgradeBytecode method is
- // being called recursively. FFV0 sets a Level-Of-Detail filter that
- // applies implicitly until the next SetLOD opcode (if any). FFV1 instead
- // explicitly gives the number of opcodes to skip if outside the LOD range.
- calculatingJumpLOD bool
- }
- func (u *upgrader) upgradeMetadata(v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
- nMetadataChunks, n := v0.decodeNatural()
- if n == 0 {
- return nil, nil, errInvalidNumberOfMetadataChunks
- }
- v1.encodeNaturalFFV1(nMetadataChunks)
- v0 = v0[n:]
- for ; nMetadataChunks > 0; nMetadataChunks-- {
- length, n := v0.decodeNatural()
- if n == 0 {
- return nil, nil, errInvalidMetadataChunkLength
- }
- v0 = v0[n:]
- if uint64(length) > uint64(len(v0)) {
- return nil, nil, errInvalidMetadataChunkLength
- }
- upgrade, err := u.upgradeMetadataChunk(v0[:length])
- if err != nil {
- return nil, nil, err
- }
- v1.encodeNaturalFFV1(uint32(len(upgrade)))
- v1 = append(v1, upgrade...)
- v0 = v0[length:]
- }
- return v1, v0, nil
- }
- func (u *upgrader) upgradeMetadataChunk(v0 buffer) (v1 buffer, retErr error) {
- mid, n := v0.decodeNatural()
- if n == 0 {
- return nil, errInvalidMetadataIdentifier
- }
- switch mid {
- case midViewBox:
- mid = ffv1MIDViewBox
- case midSuggestedPalette:
- mid = ffv1MIDSuggestedPalette
- default:
- return nil, errInvalidMetadataIdentifier
- }
- v1.encodeNaturalFFV1(mid)
- v0 = v0[n:]
- switch mid {
- case ffv1MIDViewBox:
- for i := 0; i < 4; i++ {
- x, n := v0.decodeNatural()
- if n == 0 {
- return nil, errInvalidViewBox
- }
- v1.encodeNaturalFFV1(x)
- v0 = v0[n:]
- }
- if len(v0) != 0 {
- return nil, errInvalidViewBox
- }
- case ffv1MIDSuggestedPalette:
- if len(v0) == 0 {
- return nil, errInvalidSuggestedPalette
- }
- numColors := 1 + int(v0[0]&0x3f)
- colorLength := 1 + int(v0[0]>>6)
- v1 = append(v1, uint8(numColors-1))
- v0 = v0[1:]
- for i := 0; i < numColors; i++ {
- c, n := Color{}, 0
- switch colorLength {
- case 1:
- c, n = v0.decodeColor1()
- case 2:
- c, n = v0.decodeColor2()
- case 3:
- c, n = v0.decodeColor3Direct()
- case 4:
- c, n = v0.decodeColor4()
- }
- if n == 0 {
- return nil, errInvalidSuggestedPalette
- } else if (c.typ == ColorTypeRGBA) && validAlphaPremulColor(c.data) {
- v1 = append(v1, c.data.R, c.data.G, c.data.B, c.data.A)
- } else {
- v1 = append(v1, 0x00, 0x00, 0x00, 0xff)
- }
- v0 = v0[n:]
- }
- if len(v0) != 0 {
- return nil, errInvalidSuggestedPalette
- }
- }
- return v1, nil
- }
- func (u *upgrader) upgradeBytecode(v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
- uf := upgradeFunc(upgradeStyling)
- for len(v0) > 0 {
- uf, v1, v0, retErr = uf(u, v1, v0)
- if retErr != nil {
- if retErr == errCalculatingJumpLOD {
- return v1, v0, nil
- }
- return nil, nil, retErr
- }
- }
- return v1, v0, nil
- }
- var errCalculatingJumpLOD = errors.New("iconvg: calculating JumpLOD")
- type upgradeFunc func(*upgrader, buffer, buffer) (upgradeFunc, buffer, buffer, error)
- func upgradeStyling(u *upgrader, v1 buffer, v0 buffer) (uf upgradeFunc, newV1 buffer, newV0 buffer, retErr error) {
- for len(v0) > 0 {
- switch opcode := v0[0]; {
- case opcode < 0x80: // "Set CSEL/NSEL"
- if opcode < 0x40 {
- u.csel = uint32(opcode & 63)
- } else {
- u.nsel = uint32(opcode & 63)
- }
- v0 = v0[1:]
- case opcode < 0xa8: // "Set CREG[etc] to an etc color"
- adj := uint32(opcode & 7)
- if adj == 7 {
- adj = 0
- }
- index := (u.csel - adj) & 63
- v0 = v0[1:]
- c, n := Color{}, 0
- switch (opcode - 0x80) >> 3 {
- case 0:
- c, n = v0.decodeColor1()
- case 1:
- c, n = v0.decodeColor2()
- case 2:
- c, n = v0.decodeColor3Direct()
- case 3:
- c, n = v0.decodeColor4()
- case 4:
- c, n = v0.decodeColor3Indirect()
- }
- if n == 0 {
- return nil, nil, nil, errInvalidColor
- }
- u.creg[index], retErr = u.resolve(c, false)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- v0 = v0[n:]
- if (opcode & 7) == 7 {
- u.csel = (u.csel + 1) & 63
- }
- case opcode < 0xc0: // "Set NREG[etc] to a real number"
- adj := uint32(opcode & 7)
- if adj == 7 {
- adj = 0
- }
- index := (u.nsel - adj) & 63
- v0 = v0[1:]
- f, n := float32(0), 0
- switch (opcode - 0x80) >> 3 {
- case 5:
- f, n = v0.decodeReal()
- case 6:
- f, n = v0.decodeCoordinate()
- case 7:
- f, n = v0.decodeZeroToOne()
- }
- if n == 0 {
- return nil, nil, nil, errInvalidNumber
- }
- u.nreg[index] = f
- v0 = v0[n:]
- if (opcode & 7) == 7 {
- u.nsel = (u.nsel + 1) & 63
- }
- case opcode < 0xc7: // Start path.
- adj := uint32(opcode & 7)
- u.fill = (u.csel - adj) & 63
- v1 = append(v1, 0x35) // FFV1 MoveTo.
- v0 = v0[1:]
- return upgradeDrawing, v1, v0, nil
- case opcode == 0xc7: // "Set LOD"
- if u.calculatingJumpLOD {
- u.calculatingJumpLOD = false
- return nil, v1, v0, errCalculatingJumpLOD
- }
- v0 = v0[1:]
- lod := [2]float32{}
- for i := range lod {
- f, n := v0.decodeReal()
- if n == 0 {
- return nil, nil, nil, errInvalidNumber
- }
- lod[i] = f
- v0 = v0[n:]
- }
- if (lod[0] == 0) && math.IsInf(float64(lod[1]), +1) {
- break
- }
- u.calculatingJumpLOD = true
- ifTrue := []byte(nil)
- if ifTrue, v0, retErr = u.upgradeBytecode(nil, v0); retErr != nil {
- return nil, nil, nil, retErr
- }
- nInstructions := countFFV1Instructions(ifTrue)
- if nInstructions >= (1 << 30) {
- return nil, nil, nil, errUnsupportedUpgrade
- }
- v1 = append(v1, 0x3a) // FFV1 JumpLOD.
- v1.encodeNaturalFFV1(uint32(nInstructions))
- v1.encodeCoordinateFFV1(lod[0])
- v1.encodeCoordinateFFV1(lod[1])
- v1 = append(v1, ifTrue...)
- default:
- return nil, nil, nil, errUnsupportedStylingOpcode
- }
- }
- return upgradeStyling, v1, v0, nil
- }
- func upgradeDrawing(u *upgrader, v1 buffer, v0 buffer) (uf upgradeFunc, newV1 buffer, newV0 buffer, retErr error) {
- u.verbs = u.verbs[:0]
- u.args = u.args[:0]
- coords := [3][2]float32{}
- pen := [2]float32{}
- prevSmoothType := smoothTypeNone
- prevSmoothPoint := [2]float32{}
- // Handle the implicit M after a "Start path" styling op.
- v0, retErr = decodeCoordinates(pen[:2], nil, v0)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- u.verbs = append(u.verbs, upgradeVerbMoveTo)
- u.args = append(u.args, pen)
- startingPoint := pen
- for len(v0) > 0 {
- switch opcode := v0[0]; {
- case opcode < 0xc0: // LineTo, QuadTo, CubeTo.
- nCoordPairs, nReps, relative, smoothType := 0, 1+int(opcode&0x0f), false, smoothTypeNone
- switch opcode >> 4 {
- case 0x00, 0x01: // "L (absolute lineTo)"
- nCoordPairs = 1
- nReps = 1 + int(opcode&0x1f)
- case 0x02, 0x03: // "l (relative lineTo)"
- nCoordPairs = 1
- nReps = 1 + int(opcode&0x1f)
- relative = true
- case 0x04: // "T (absolute smooth quadTo)"
- nCoordPairs = 1
- smoothType = smoothTypeQuad
- case 0x05: // "t (relative smooth quadTo)"
- nCoordPairs = 1
- relative = true
- smoothType = smoothTypeQuad
- case 0x06: // "Q (absolute quadTo)"
- nCoordPairs = 2
- case 0x07: // "q (relative quadTo)"
- nCoordPairs = 2
- relative = true
- case 0x08: // "S (absolute smooth cubeTo)"
- nCoordPairs = 2
- smoothType = smoothTypeCube
- case 0x09: // "s (relative smooth cubeTo)"
- nCoordPairs = 2
- relative = true
- smoothType = smoothTypeCube
- case 0x0a: // "C (absolute cubeTo)"
- nCoordPairs = 3
- case 0x0b: // "c (relative cubeTo)"
- nCoordPairs = 3
- relative = true
- }
- v0 = v0[1:]
- for i := 0; i < nReps; i++ {
- smoothIndex := 0
- if smoothType != smoothTypeNone {
- smoothIndex = 1
- if smoothType != prevSmoothType {
- coords[0][0] = pen[0]
- coords[0][1] = pen[1]
- } else {
- coords[0][0] = (2 * pen[0]) - prevSmoothPoint[0]
- coords[0][1] = (2 * pen[1]) - prevSmoothPoint[1]
- }
- }
- allCoords := coords[:smoothIndex+nCoordPairs]
- explicitCoords := allCoords[smoothIndex:]
- v0, retErr = decodeCoordinatePairs(explicitCoords, nil, v0)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- if relative {
- for c := range explicitCoords {
- explicitCoords[c][0] += pen[0]
- explicitCoords[c][1] += pen[1]
- }
- }
- u.verbs = append(u.verbs, uint8(len(allCoords)))
- u.args = append(u.args, allCoords...)
- pen = allCoords[len(allCoords)-1]
- if len(allCoords) == 2 {
- prevSmoothPoint = allCoords[0]
- prevSmoothType = smoothTypeQuad
- } else if len(allCoords) == 3 {
- prevSmoothPoint = allCoords[1]
- prevSmoothType = smoothTypeCube
- } else {
- prevSmoothType = smoothTypeNone
- }
- }
- case opcode < 0xe0: // ArcTo.
- v1, v0, retErr = u.upgradeArcs(&pen, v1, v0)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- prevSmoothType = smoothTypeNone
- default: // Other drawing opcodes.
- v0 = v0[1:]
- switch opcode {
- case 0xe1: // "z (closePath); end path"
- goto endPath
- case 0xe2, 0xe3: // "z (closePath); M (absolute/relative moveTo)"
- v0, retErr = decodeCoordinatePairs(coords[:1], nil, v0)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- if opcode == 0xe2 {
- pen[0] = coords[0][0]
- pen[1] = coords[0][1]
- } else {
- pen[0] += coords[0][0]
- pen[1] += coords[0][1]
- }
- u.verbs = append(u.verbs, upgradeVerbMoveTo)
- u.args = append(u.args, pen)
- default:
- tmp := [1]float32{}
- v0, retErr = decodeCoordinates(tmp[:1], nil, v0)
- if retErr != nil {
- return nil, nil, nil, retErr
- }
- switch opcode {
- case 0xe6: // "H (absolute horizontal lineTo)"
- pen[0] = tmp[0]
- case 0xe7: // "h (relative horizontal lineTo)"
- pen[0] += tmp[0]
- case 0xe8: // "V (absolute vertical lineTo)"
- pen[1] = tmp[0]
- case 0xe9: // "v (relative vertical lineTo)"
- pen[1] += tmp[0]
- default:
- return nil, nil, nil, errUnsupportedDrawingOpcode
- }
- u.verbs = append(u.verbs, upgradeVerbLineTo)
- u.args = append(u.args, pen)
- }
- prevSmoothType = smoothTypeNone
- }
- }
- endPath:
- v1, retErr = u.finishDrawing(v1, startingPoint)
- return upgradeStyling, v1, v0, retErr
- }
- func (u *upgrader) finishDrawing(v1 buffer, startingPoint [2]float32) (newV1 buffer, retErr error) {
- v1.encodeCoordinatePairFFV1(u.args[0])
- for i, j := 1, 1; i < len(u.verbs); {
- curr := u.args[j-1]
- runLength := u.computeRunLength(u.verbs[i:])
- verb := u.verbs[i]
- if verb == upgradeVerbMoveTo {
- v1 = append(v1, 0x35) // FFV1 MoveTo.
- v1.encodeCoordinatePairFFV1(u.args[j])
- i += 1
- j += 1
- continue
- }
- switch verb {
- case upgradeVerbLineTo:
- if ((runLength == 3) && ((j + 3) == len(u.args)) && u.looksLikeParallelogram3(&curr, u.args[j:], &startingPoint)) ||
- ((runLength == 4) && u.looksLikeParallelogram4(&curr, u.args[j:j+4])) {
- v1 = append(v1, 0x34) // FFV1 Parallelogram.
- v1.encodeCoordinatePairFFV1(u.args[j+0])
- v1.encodeCoordinatePairFFV1(u.args[j+1])
- i += 4
- j += 4 * 1
- continue
- }
- case upgradeVerbCubeTo:
- if (runLength == 4) && u.looksLikeEllipse(&curr, u.args[j:j+(4*3)]) {
- v1 = append(v1, 0x33) // FFV1 Ellipse (4 quarters).
- v1.encodeCoordinatePairFFV1(u.args[j+2])
- v1.encodeCoordinatePairFFV1(u.args[j+5])
- i += 4
- j += 4 * 3
- continue
- }
- }
- opcodeBase := 0x10 * (verb - 1) // FFV1 LineTo / QuadTo / CubeTo.
- if runLength < 16 {
- v1 = append(v1, opcodeBase|uint8(runLength))
- } else {
- v1 = append(v1, opcodeBase)
- v1.encodeNaturalFFV1(uint32(runLength) - 16)
- }
- args := u.args[j : j+(runLength*int(verb))]
- for _, arg := range args {
- v1.encodeCoordinatePairFFV1(arg)
- }
- i += runLength
- j += len(args)
- }
- return u.emitFill(v1)
- }
- func (u *upgrader) emitFill(v1 buffer) (newV1 buffer, retErr error) {
- switch c := u.creg[u.fill]; c.typ {
- case ColorTypeRGBA:
- if validAlphaPremulColor(c.rgba) {
- if !u.hasRegsSel7 || (u.regsSel7 != c.rgba) {
- u.hasRegsSel7, u.regsSel7 = true, c.rgba
- v1 = append(v1, 0x57, // FFV1 Set REGS[SEL+7].hi32.
- c.rgba.R, c.rgba.G, c.rgba.B, c.rgba.A)
- }
- v1 = append(v1, 0x87) // FFV1 Fill (flat color) with REGS[SEL+7].
- } else if (c.rgba.A == 0) && (c.rgba.B&0x80 != 0) {
- nStops := int(c.rgba.R & 63)
- cBase := int(c.rgba.G & 63)
- nBase := int(c.rgba.B & 63)
- if nStops < 2 {
- return nil, errInvalidColor
- } else if nStops > 17 {
- return nil, errUnsupportedUpgrade
- }
- v1 = append(v1, 0x70|uint8(nStops-2)) // FFV1 SEL -= N; Set REGS[SEL+1 .. SEL+1+N].
- for i := 0; i < nStops; i++ {
- if stopOffset := u.nreg[(nBase+i)&63]; stopOffset <= 0 {
- v1 = append(v1, 0x00, 0x00, 0x00, 0x00)
- } else if stopOffset < 1 {
- u := uint32(stopOffset * 0x10000)
- v1 = append(v1, uint8(u>>0), uint8(u>>8), uint8(u>>16), uint8(u>>24))
- } else {
- v1 = append(v1, 0x00, 0x00, 0x01, 0x00)
- }
- if stopColor := u.creg[(cBase+i)&63]; stopColor.typ != ColorTypeRGBA {
- return nil, errUnsupportedUpgrade
- } else {
- v1 = append(v1,
- stopColor.rgba.R,
- stopColor.rgba.G,
- stopColor.rgba.B,
- stopColor.rgba.A,
- )
- }
- }
- nMatrixElements := 0
- if c.rgba.B&0x40 == 0 {
- v1 = append(v1, 0x91, // FFV1 Fill (linear gradient) with REGS[SEL+1 .. SEL+1+N].
- (c.rgba.G&0xc0)|uint8(nStops-2))
- nMatrixElements = 3
- } else {
- v1 = append(v1, 0xa1, // FFV1 Fill (radial gradient) with REGS[SEL+1 .. SEL+1+N].
- (c.rgba.G&0xc0)|uint8(nStops-2))
- nMatrixElements = 6
- }
- for i := 0; i < nMatrixElements; i++ {
- u := math.Float32bits(u.nreg[(nBase+i-6)&63])
- v1 = append(v1, uint8(u>>0), uint8(u>>8), uint8(u>>16), uint8(u>>24))
- }
- v1 = append(v1, 0x36, // FFV1 SEL += N.
- uint8(nStops))
- } else {
- return nil, errInvalidColor
- }
- case ColorTypePaletteIndex:
- if c.paletteIndex < 7 {
- v1 = append(v1, 0x88+c.paletteIndex) // FFV1 Fill (flat color) with REGS[SEL+8+N].
- } else {
- v1 = append(v1, 0x56, // FFV1 Set REGS[SEL+6].hi32.
- 0x80|c.paletteIndex, 0, 0, 0,
- 0x86) // FFV1 Fill (flat color) with REGS[SEL+6].
- }
- case ColorTypeBlend:
- if c.color0.typ == ColorTypeRGBA {
- v1 = append(v1, 0x53, // FFV1 Set REGS[SEL+3].hi32.
- c.color0.rgba.R, c.color0.rgba.G, c.color0.rgba.B, c.color0.rgba.A)
- }
- if c.color1.typ == ColorTypeRGBA {
- v1 = append(v1, 0x54, // FFV1 Set REGS[SEL+4].hi32.
- c.color1.rgba.R, c.color1.rgba.G, c.color1.rgba.B, c.color1.rgba.A)
- }
- v1 = append(v1, 0x55, // FFV1 Set REGS[SEL+5].hi32.
- c.blend)
- if c.color0.typ == ColorTypeRGBA {
- v1 = append(v1, 0xfe)
- } else {
- v1 = append(v1, 0x80|c.color0.paletteIndex)
- }
- if c.color1.typ == ColorTypeRGBA {
- v1 = append(v1, 0xff)
- } else {
- v1 = append(v1, 0x80|c.color1.paletteIndex)
- }
- v1 = append(v1, 0, 0x85) // FFV1 Fill (flat color) with REGS[SEL+5].
- }
- return v1, nil
- }
- func (u *upgrader) computeRunLength(verbs []uint8) int {
- firstVerb := verbs[0]
- if firstVerb == 0 {
- return 1
- }
- n := 1
- for ; (n < len(verbs)) && (verbs[n] == firstVerb); n++ {
- }
- return n
- }
- // looksLikeParallelogram3 is like looksLikeParallelogram4 but the final point
- // (implied by the ClosePath op) is separate from the middle 3 args.
- func (u *upgrader) looksLikeParallelogram3(curr *[2]float32, args [][2]float32, final *[2]float32) bool {
- if len(args) != 3 {
- panic("unreachable")
- }
- return (*curr == *final) &&
- (curr[0] == (args[0][0] - args[1][0] + args[2][0])) &&
- (curr[1] == (args[0][1] - args[1][1] + args[2][1]))
- }
- // looksLikeParallelogram4 returns whether the 5 coordinate pairs (A, B, C, D,
- // E) form a parallelogram:
- //
- // E=A B
- //
- // o---------o
- // \ \
- // \ \
- // \ \
- // o---------o
- // D C
- //
- // Specifically, it checks that (A == E) and ((A - B) == (D - C)). That last
- // equation can be rearranged as (A == (B - C + D)).
- //
- // The motivation is that, if looksLikeParallelogram4 is true, then the 5 input
- // coordinate pairs can then be compressed to 3: A, B and C. Or, if the current
- // point A is implied by context then 4 input pairs can be compressed to 2.
- func (u *upgrader) looksLikeParallelogram4(curr *[2]float32, args [][2]float32) bool {
- if len(args) != 4 {
- panic("unreachable")
- }
- return (*curr == args[3]) &&
- (curr[0] == (args[0][0] - args[1][0] + args[2][0])) &&
- (curr[1] == (args[0][1] - args[1][1] + args[2][1]))
- }
- // looksLikeEllipse returns whether the 13 coordinate pairs (A, A+, B-, B, B+,
- // C- C, C+, D-, D, D+, A-, E) form a cubic Bézier approximation to an ellipse.
- // Let A± denote the two tangent vectors (A+ - A) and (A - A-) and likewise for
- // B±, C± and D±.
- //
- // A+ B-
- //
- // E=A o o B
- // A- o---------o B+
- //
- // o \ \ o
- // \ X \
- // o \ \ o
- // D+ o---------o C-
- // D o o C
- // D- C+
- //
- // See https://nigeltao.github.io/blog/2021/three-points-define-ellipse.html
- // for a better version of that ASCII art.
- //
- // Specifically, it checks that (A, B, C, D, E), also known as (*curr, args[2],
- // args[5], args[8] and args[11]), forms a parallelogram. If so, let X be the
- // parallelogram center and define two axis vectors: r = B-X and s = C-X.
- //
- // These axes define the parallelogram's or ellipse's shape but they are not
- // necessarily orthogonal and hence not necessarily the ellipse's major
- // (longest) and minor (shortest) axes. If s is a 90 degree rotation of r then
- // the parallelogram is a square and the ellipse is a circle.
- //
- // This function further checks that the A±, B± C± and D± tangents are
- // approximately equal to +λ×r, +λ×s, -λ×r and -λ×s, where λ = ((math.Sqrt2 -
- // 1) × 4 / 3) comes from the cubic Bézier approximation to a quarter-circle.
- //
- // The motivation is that, if looksLikeEllipse is true, then the 13 input
- // coordinate pairs can then be compressed to 3: A, B and C. Or, if the current
- // point A is implied by context then 12 input pairs can be compressed to 2.
- func (u *upgrader) looksLikeEllipse(curr *[2]float32, args [][2]float32) bool {
- if len(args) != 12 {
- panic("unreachable")
- }
- if (*curr != args[11]) ||
- (curr[0] != (args[2][0] - args[5][0] + args[8][0])) ||
- (curr[1] != (args[2][1] - args[5][1] + args[8][1])) {
- return false
- }
- center := [2]float32{
- (args[2][0] + args[8][0]) / 2,
- (args[2][1] + args[8][1]) / 2,
- }
- // 0.5522847498307933984022516322796 ≈ ((math.Sqrt2 - 1) × 4 / 3), the
- // tangent lengths (as a fraction of the radius) for a commonly used cubic
- // Bézier approximation to a circle. Multiplying that by 0.98 and 1.02
- // checks that we're within 2% of that fraction.
- //
- // This also covers the slightly different 0.551784777779014 constant,
- // recommended by https://pomax.github.io/bezierinfo/#circles_cubic
- const λMin = 0.98 * 0.5522847498307933984022516322796
- const λMax = 1.02 * 0.5522847498307933984022516322796
- // Check the first axis.
- r := [2]float32{
- args[2][0] - center[0],
- args[2][1] - center[1],
- }
- rMin := [2]float32{r[0] * λMin, r[1] * λMin}
- rMax := [2]float32{r[0] * λMax, r[1] * λMax}
- if rMin[0] > rMax[0] {
- rMin[0], rMax[0] = rMax[0], rMin[0]
- }
- if rMin[1] > rMax[1] {
- rMin[1], rMax[1] = rMax[1], rMin[1]
- }
- if !within(args[0][0]-curr[0], args[0][1]-curr[1], rMin, rMax) ||
- !within(args[4][0]-args[5][0], args[4][1]-args[5][1], rMin, rMax) ||
- !within(args[5][0]-args[6][0], args[5][1]-args[6][1], rMin, rMax) ||
- !within(args[11][0]-args[10][0], args[11][1]-args[10][1], rMin, rMax) {
- return false
- }
- // Check the second axis.
- s := [2]float32{
- args[5][0] - center[0],
- args[5][1] - center[1],
- }
- sMin := [2]float32{s[0] * λMin, s[1] * λMin}
- sMax := [2]float32{s[0] * λMax, s[1] * λMax}
- if sMin[0] > sMax[0] {
- sMin[0], sMax[0] = sMax[0], sMin[0]
- }
- if sMin[1] > sMax[1] {
- sMin[1], sMax[1] = sMax[1], sMin[1]
- }
- if !within(args[2][0]-args[1][0], args[2][1]-args[1][1], sMin, sMax) ||
- !within(args[3][0]-args[2][0], args[3][1]-args[2][1], sMin, sMax) ||
- !within(args[7][0]-args[8][0], args[7][1]-args[8][1], sMin, sMax) ||
- !within(args[8][0]-args[9][0], args[8][1]-args[9][1], sMin, sMax) {
- return false
- }
- return true
- }
- func within(v0 float32, v1 float32, min [2]float32, max [2]float32) bool {
- return (min[0] <= v0) && (v0 <= max[0]) && (min[1] <= v1) && (v1 <= max[1])
- }
- func (u *upgrader) upgradeArcs(pen *[2]float32, v1 buffer, v0 buffer) (newV1 buffer, newV0 buffer, retErr error) {
- coords := [6]float32{}
- largeArc, sweep := false, false
- opcode := v0[0]
- v0 = v0[1:]
- nReps := 1 + int(opcode&0x0f)
- for i := 0; i < nReps; i++ {
- v0, retErr = decodeCoordinates(coords[:2], nil, v0)
- if retErr != nil {
- return nil, nil, retErr
- }
- coords[2], v0, retErr = decodeAngle(nil, v0)
- if retErr != nil {
- return nil, nil, retErr
- }
- largeArc, sweep, v0, retErr = decodeArcToFlags(nil, v0)
- if retErr != nil {
- return nil, nil, retErr
- }
- v0, retErr = decodeCoordinates(coords[4:6], nil, v0)
- if retErr != nil {
- return nil, nil, retErr
- }
- if (opcode >> 4) == 0x0d {
- coords[4] += pen[0]
- coords[5] += pen[1]
- }
- u.upgradeArc(pen, coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
- pen[0] = coords[4]
- pen[1] = coords[5]
- }
- return v1, v0, nil
- }
- func (u *upgrader) upgradeArc(pen *[2]float32, rx, ry, xAxisRotation float32, largeArc, sweep bool, finalX, finalY float32) {
- // 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) {
- u.verbs = append(u.verbs, upgradeVerbLineTo)
- u.args = append(u.args, [2]float32{finalX, finalY})
- return
- }
- x1 := float64(pen[0])
- y1 := float64(pen[1])
- x2 := float64(finalX)
- y2 := float64(finalY)
- 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++ {
- u.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 (u *upgrader) 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)
- highResolutionCoordinates := u.opts.ArcsExpandWithHighResolutionCoordinates
- u.verbs = append(u.verbs, upgradeVerbCubeTo)
- u.args = append(u.args,
- [2]float32{
- quantize(float32(cx+cosPhi*x1-sinPhi*y1), highResolutionCoordinates),
- quantize(float32(cy+sinPhi*x1+cosPhi*y1), highResolutionCoordinates),
- },
- [2]float32{
- quantize(float32(cx+cosPhi*x2-sinPhi*y2), highResolutionCoordinates),
- quantize(float32(cy+sinPhi*x2+cosPhi*y2), highResolutionCoordinates),
- },
- [2]float32{
- quantize(float32(cx+cosPhi*x3-sinPhi*y3), highResolutionCoordinates),
- quantize(float32(cy+sinPhi*x3+cosPhi*y3), highResolutionCoordinates),
- },
- )
- }
- func countFFV1Instructions(src buffer) (ret uint64) {
- for len(src) > 0 {
- ret++
- opcode := src[0]
- src = src[1:]
- switch {
- case opcode < 0x40:
- switch {
- case opcode < 0x30:
- nReps := uint32(opcode & 15)
- if nReps == 0 {
- n := 0
- nReps, n = src.decodeNaturalFFV1()
- src = src[n:]
- nReps += 16
- }
- nCoords := 2 * (1 + int(opcode>>4))
- for ; nReps > 0; nReps-- {
- for i := 0; i < nCoords; i++ {
- _, n := src.decodeNaturalFFV1()
- src = src[n:]
- }
- }
- case opcode < 0x35:
- for i := 0; i < 4; i++ {
- _, n := src.decodeNaturalFFV1()
- src = src[n:]
- }
- case opcode == 0x35:
- for i := 0; i < 2; i++ {
- _, n := src.decodeNaturalFFV1()
- src = src[n:]
- }
- case opcode == 0x36:
- src = src[1:]
- case opcode == 0x37:
- // No-op.
- default:
- // upgradeBytecode (with calculatingJumpLOD set) will not emit
- // jump or call instructions.
- panic("unexpected FFV1 instruction")
- }
- case opcode < 0x80:
- switch (opcode >> 4) & 3 {
- case 0, 1:
- src = src[4:]
- case 2:
- src = src[8:]
- default:
- src = src[8*(2+int(opcode&15)):]
- }
- case opcode < 0xc0:
- switch (opcode >> 4) & 3 {
- case 0:
- // No-op.
- case 1:
- src = src[13:]
- case 2:
- src = src[25:]
- default:
- // upgradeBytecode (with calculatingJumpLOD set) will not emit
- // reserved instructions.
- panic("unexpected FFV1 instruction")
- }
- default:
- // upgradeBytecode (with calculatingJumpLOD set) will not emit
- // reserved instructions.
- panic("unexpected FFV1 instruction")
- }
- }
- return ret
- }
- type upgradeColor struct {
- typ ColorType
- paletteIndex uint8
- blend uint8
- rgba color.RGBA
- color0 *upgradeColor
- color1 *upgradeColor
- }
- func (u *upgrader) resolve(c Color, denyBlend bool) (upgradeColor, error) {
- switch c.typ {
- case ColorTypeRGBA:
- return upgradeColor{
- typ: ColorTypeRGBA,
- rgba: c.data,
- }, nil
- case ColorTypePaletteIndex:
- return upgradeColor{
- typ: ColorTypePaletteIndex,
- paletteIndex: c.paletteIndex(),
- }, nil
- case ColorTypeCReg:
- upgrade := u.creg[c.cReg()]
- if denyBlend && (upgrade.typ == ColorTypeBlend) {
- return upgradeColor{}, errUnsupportedUpgrade
- }
- return upgrade, nil
- }
- if denyBlend {
- return upgradeColor{}, errUnsupportedUpgrade
- }
- t, c0, c1 := c.blend()
- color0, err := u.resolve(decodeColor1(c0), true)
- if err != nil {
- return upgradeColor{}, err
- }
- color1, err := u.resolve(decodeColor1(c1), true)
- if err != nil {
- return upgradeColor{}, err
- }
- return upgradeColor{
- typ: ColorTypeBlend,
- blend: t,
- color0: &color0,
- color1: &color1,
- }, nil
- }
|