diff --git a/backend/softwarebackend/fill.go b/backend/softwarebackend/fill.go index 224b767..1ea800a 100644 --- a/backend/softwarebackend/fill.go +++ b/backend/softwarebackend/fill.go @@ -20,8 +20,6 @@ func (b *SoftwareBackend) Clear(pts [4][2]float64) { } func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { - b.clearMask() - if lg := style.LinearGradient; lg != nil { lg := lg.(*LinearGradient) from := [2]float64{style.Gradient.X0, style.Gradient.Y0} @@ -97,14 +95,14 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { func (b *SoftwareBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4][2]float64) { mw := float64(mask.Bounds().Dx()) mh := float64(mask.Bounds().Dy()) - b.fillQuad(pts, func(x, y int, sx2, sy2 float64) { + b.fillQuad(pts, func(x, y, sx2, sy2 float64) color.RGBA { sxi := int(mw * sx2) syi := int(mh * sy2) a := mask.AlphaAt(sxi, syi) if a.A == 0 { - return + return color.RGBA{} } - b.Image.SetRGBA(x, y, alphaColor(style.Color, a)) + return alphaColor(style.Color, a) }) } diff --git a/backend/softwarebackend/images.go b/backend/softwarebackend/images.go index dfadce9..1f9c2bf 100644 --- a/backend/softwarebackend/images.go +++ b/backend/softwarebackend/images.go @@ -76,13 +76,12 @@ func (b *SoftwareBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float sw *= mipScaleX sh *= mipScaleY - b.fillQuad(pts, func(x, y int, sx2, sy2 float64) { - imgx := sx + sw*sx2 - imgy := sy + sh*sy2 + b.fillQuad(pts, func(x, y, tx, ty float64) color.RGBA { + imgx := sx + sw*tx + imgy := sy + sh*ty imgxf := math.Floor(imgx) imgyf := math.Floor(imgy) - c := mip.At(int(imgxf), int(imgyf)) - b.Image.Set(x, y, c) + return toRGBA(mip.At(int(imgxf), int(imgyf))) // rx := imgx - imgxf // ry := imgy - imgyf diff --git a/backend/softwarebackend/triangles.go b/backend/softwarebackend/triangles.go index 994e8e0..6b567e0 100644 --- a/backend/softwarebackend/triangles.go +++ b/backend/softwarebackend/triangles.go @@ -91,6 +91,7 @@ func (b *SoftwareBackend) fillTriangleNoAA(tri [][2]float64, fn func(x, y int)) type msaaPixel struct { ix, iy int fx, fy float64 + tx, ty float64 } func (b *SoftwareBackend) fillTriangleMSAA(tri [][2]float64, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int)) []msaaPixel { @@ -199,7 +200,7 @@ func quadArea(quad [4][2]float64) float64 { return math.Abs(leftv[0]*topv[1] - leftv[1]*topv[0]) } -func (b *SoftwareBackend) fillQuad(quad [4][2]float64, fn func(x, y int, sx, sy float64)) { +func (b *SoftwareBackend) fillQuadNoAA(quad [4][2]float64, fn func(x, y int, tx, ty float64)) { minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1])))) maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1])))) if minY < 0 { @@ -246,29 +247,229 @@ func (b *SoftwareBackend) fillQuad(quad [4][2]float64, fn func(x, y int, sx, sy continue } - sfy := float64(y) + 0.5 - quad[0][1] + tfy := float64(y) + 0.5 - quad[0][1] fl, cr := int(math.Floor(l)), int(math.Ceil(r)) for x := fl; x <= cr; x++ { fx := float64(x) + 0.5 if fx < l || fx >= r { continue } - sfx := fx - quad[0][0] + tfx := fx - quad[0][0] - var sx, sy float64 + var tx, ty float64 if math.Abs(leftv[0]) > math.Abs(leftv[1]) { - sx = (sfy - sfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0])) - sy = (sfx - topv[0]*sx) / leftv[0] + tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0])) + ty = (tfx - topv[0]*tx) / leftv[0] } else { - sx = (sfx - sfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1])) - sy = (sfy - topv[1]*sx) / leftv[1] + tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1])) + ty = (tfy - topv[1]*tx) / leftv[1] } - fn(x, y, sx/topLen, sy/leftLen) + fn(x, y, tx/topLen, ty/leftLen) } } } +func (b *SoftwareBackend) fillQuadMSAA(quad [4][2]float64, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int, tx, ty float64)) []msaaPixel { + msaaStep := 1.0 / float64(msaaLevel+1) + + minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1])))) + maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1])))) + if minY < 0 { + minY = 0 + } else if minY >= b.h { + return msaaPixels + } + if maxY < 0 { + return msaaPixels + } else if maxY >= b.h { + maxY = b.h - 1 + } + + leftv := [2]float64{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]} + leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1]) + leftv[0] /= leftLen + leftv[1] /= leftLen + topv := [2]float64{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]} + topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1]) + topv[0] /= topLen + topv[1] /= topLen + + tri1 := [3][2]float64{quad[0], quad[1], quad[2]} + tri2 := [3][2]float64{quad[0], quad[2], quad[3]} + for y := minY; y <= maxY; y++ { + var l, r [5]float64 + allOut := true + minL, maxR := math.MaxFloat64, 0.0 + + sy := float64(y) + msaaStep*0.5 + for step := 0; step <= msaaLevel; step++ { + lf1, rf1, out1 := triangleLR(tri1[:], sy) + lf2, rf2, out2 := triangleLR(tri2[:], sy) + l[step] = math.Min(lf1, lf2) + r[step] = math.Max(rf1, rf2) + out := out1 || out2 + + if l[step] < 0 { + l[step] = 0 + } else if l[step] > float64(b.w) { + l[step] = float64(b.w) + out = true + } + if r[step] < 0 { + r[step] = 0 + out = true + } else if r[step] > float64(b.w) { + r[step] = float64(b.w) + } + if r[step] <= l[step] { + out = true + } + if !out { + allOut = false + minL = math.Min(minL, l[step]) + maxR = math.Max(maxR, r[step]) + } + sy += msaaStep + } + + if allOut { + continue + } + + fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR)) + for x := fl; x <= cr; x++ { + sy = float64(y) + msaaStep*0.5 + allIn := true + check: + for stepy := 0; stepy <= msaaLevel; stepy++ { + sx := float64(x) + msaaStep*0.5 + for stepx := 0; stepx <= msaaLevel; stepx++ { + if sx < l[stepy] || sx >= r[stepy] { + allIn = false + break check + } + sx += msaaStep + } + sy += msaaStep + } + + if allIn { + tfx := float64(x) + 0.5 - quad[0][0] + tfy := float64(y) + 0.5 - quad[0][1] + + var tx, ty float64 + if math.Abs(leftv[0]) > math.Abs(leftv[1]) { + tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0])) + ty = (tfx - topv[0]*tx) / leftv[0] + } else { + tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1])) + ty = (tfy - topv[1]*tx) / leftv[1] + } + + fn(x, y, tx/topLen, ty/leftLen) + continue + } + + sy = float64(y) + msaaStep*0.5 + for stepy := 0; stepy <= msaaLevel; stepy++ { + sx := float64(x) + msaaStep*0.5 + for stepx := 0; stepx <= msaaLevel; stepx++ { + if sx >= l[stepy] && sx < r[stepy] { + tfx := sx - quad[0][0] + tfy := sy - quad[0][1] + + var tx, ty float64 + if math.Abs(leftv[0]) > math.Abs(leftv[1]) { + tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0])) + ty = (tfx - topv[0]*tx) / leftv[0] + } else { + tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1])) + ty = (tfy - topv[1]*tx) / leftv[1] + } + + msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy, tx: tx / topLen, ty: ty / leftLen}) + } + sx += msaaStep + } + sy += msaaStep + } + } + } + + return msaaPixels +} + +func (b *SoftwareBackend) fillQuad(pts [4][2]float64, fn func(x, y, tx, ty float64) color.RGBA) { + b.clearMask() + + if b.MSAA > 0 { + var msaaPixelBuf [500]msaaPixel + msaaPixels := msaaPixelBuf[:0] + + msaaPixels = b.fillQuadMSAA(pts, b.MSAA, msaaPixels, func(x, y int, tx, ty float64) { + if b.clip.AlphaAt(x, y).A == 0 { + return + } + if b.mask.AlphaAt(x, y).A > 0 { + return + } + b.mask.SetAlpha(x, y, color.Alpha{A: 255}) + col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty) + if col.A > 0 { + b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + } + }) + + samples := (b.MSAA + 1) * (b.MSAA + 1) + + for i, px := range msaaPixels { + if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.mask.AlphaAt(px.ix, px.iy).A > 0 { + continue + } + b.mask.SetAlpha(px.ix, px.iy, color.Alpha{A: 255}) + + var mr, mg, mb, ma int + for j, px2 := range msaaPixels[i:] { + if px2.ix != px.ix || px2.iy != px.iy { + continue + } + + col := fn(px2.fx, px2.fy, px2.tx, px2.ty) + mr += int(col.R) + mg += int(col.G) + mb += int(col.B) + ma += int(col.A) + + msaaPixels[i+j].ix = -1 + } + + combined := color.RGBA{ + R: uint8(mr / samples), + G: uint8(mg / samples), + B: uint8(mb / samples), + A: uint8(ma / samples), + } + b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy))) + } + + } else { + b.fillQuadNoAA(pts, func(x, y int, tx, ty float64) { + if b.clip.AlphaAt(x, y).A == 0 { + return + } + if b.mask.AlphaAt(x, y).A > 0 { + return + } + b.mask.SetAlpha(x, y, color.Alpha{A: 255}) + col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty) + if col.A > 0 { + b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y))) + } + }) + } +} + func iterateTriangles(pts [][2]float64, fn func(tri [][2]float64)) { if len(pts) == 4 { var buf [3][2]float64 @@ -358,6 +559,8 @@ func (b *SoftwareBackend) fillTrianglesMSAA(pts [][2]float64, msaaLevel int, fn } func (b *SoftwareBackend) fillTriangles(pts [][2]float64, fn func(x, y float64) color.RGBA) { + b.clearMask() + if b.MSAA > 0 { b.fillTrianglesMSAA(pts, b.MSAA, fn) } else {