Change from blend func to interface

- Combine all previous blend modes into one blend method
- Optimize BlendMethodMedian
This commit is contained in:
David Vogel 2022-08-11 11:47:18 +02:00
parent 3a73e13fb7
commit df6c27924b
3 changed files with 54 additions and 44 deletions

View File

@ -11,10 +11,19 @@ import (
"sort" "sort"
) )
// BlendFuncMedian takes the given tiles and median blends them into destImage. // BlendMethodMedian takes the given tiles and median blends them into destImage.
func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) { type BlendMethodMedian struct {
LimitToNew int // If larger than 0, limits median blending to the `LimitToNew` newest tiles by file modification time.
}
func (b BlendMethodMedian) Draw(tiles []*ImageTile, destImage *image.RGBA) {
bounds := destImage.Bounds() bounds := destImage.Bounds()
if b.LimitToNew > 0 {
// Sort tiles by date.
sort.Slice(tiles, func(i, j int) bool { return tiles[i].modTime.After(tiles[j].modTime) })
}
// List of images corresponding with every tile. // List of images corresponding with every tile.
// Can contain empty/nil entries for images that failed to load. // Can contain empty/nil entries for images that failed to load.
images := []*image.RGBA{} images := []*image.RGBA{}
@ -29,7 +38,7 @@ func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) {
for ix := bounds.Min.X; ix < bounds.Max.X; ix++ { for ix := bounds.Min.X; ix < bounds.Max.X; ix++ {
rList, gList, bList := rListEmpty, gListEmpty, bListEmpty rList, gList, bList := rListEmpty, gListEmpty, bListEmpty
point := image.Point{ix, iy} point := image.Point{ix, iy}
found := false count := 0
// Iterate through all images and create a list of colors. // Iterate through all images and create a list of colors.
for _, img := range images { for _, img := range images {
@ -37,43 +46,39 @@ func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) {
if point.In(img.Bounds()) { if point.In(img.Bounds()) {
col := img.RGBAAt(point.X, point.Y) col := img.RGBAAt(point.X, point.Y)
rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B)) rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B))
found = true count++
// Limit number of tiles to median blend.
// Will be ignored if LimitToNew is 0.
if count == b.LimitToNew {
break
}
} }
} }
} }
// If there were no images to get data from, ignore the pixel. // If there were no images to get data from, ignore the pixel.
if !found { if count == 0 {
continue continue
} }
// Sort colors. // Sort colors. Not needed if there is only one color.
if count > 1 {
sort.Ints(rList) sort.Ints(rList)
sort.Ints(gList) sort.Ints(gList)
sort.Ints(bList) sort.Ints(bList)
}
// Take the middle element of each color. // Take the middle element of each color.
var r, g, b uint8 var r, g, b uint8
if l := len(rList); l%2 == 0 { switch count % 2 {
// Even. case 0: // Even.
r = uint8((rList[l/2-1] + rList[l/2]) / 2) r = uint8((rList[count/2-1] + rList[count/2]) / 2)
} else { g = uint8((gList[count/2-1] + gList[count/2]) / 2)
// Odd. b = uint8((bList[count/2-1] + bList[count/2]) / 2)
r = uint8(rList[(l-1)/2]) default: // Odd.
} r = uint8(rList[(count-1)/2])
if l := len(gList); l%2 == 0 { g = uint8(gList[(count-1)/2])
// Even. b = uint8(bList[(count-1)/2])
g = uint8((gList[l/2-1] + gList[l/2]) / 2)
} else {
// Odd.
g = uint8(gList[(l-1)/2])
}
if l := len(bList); l%2 == 0 {
// Even.
b = uint8((bList[l/2-1] + bList[l/2]) / 2)
} else {
// Odd.
b = uint8(bList[(l-1)/2])
} }
destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255}) destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
@ -81,7 +86,7 @@ func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) {
} }
} }
// BlendNewestPixel takes the given tiles and only draws the newest pixel (based on file modification time) of any overlapping tiles. /*// BlendNewestPixel takes the given tiles and only draws the newest pixel (based on file modification time) of any overlapping tiles.
func BlendNewestPixel(tiles []*ImageTile, destImage *image.RGBA) { func BlendNewestPixel(tiles []*ImageTile, destImage *image.RGBA) {
bounds := destImage.Bounds() bounds := destImage.Bounds()
@ -197,4 +202,4 @@ func BlendNewestPixelsMedian(tiles []*ImageTile, destImage *image.RGBA) {
destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255}) destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
} }
} }
} }*/

View File

@ -274,7 +274,11 @@ func main() {
var wg sync.WaitGroup var wg sync.WaitGroup
done := make(chan struct{}) done := make(chan struct{})
outputImage, err := NewStitchedImage(tiles, outputRect, BlendNewestPixelsMedian, 512, overlays) blendMethod := BlendMethodMedian{
LimitToNew: 1, // Limit median blending to the n newest tiles by file modification time.
}
outputImage, err := NewStitchedImage(tiles, outputRect, blendMethod, 512, overlays)
if err != nil { if err != nil {
log.Panicf("NewStitchedImage() failed: %v", err) log.Panicf("NewStitchedImage() failed: %v", err)
} }

View File

@ -17,9 +17,10 @@ import (
// TODO: Find optimal grid size that works good for tiles with lots and few overlap // TODO: Find optimal grid size that works good for tiles with lots and few overlap
var StitchedImageCacheGridSize = 512 var StitchedImageCacheGridSize = 512
// StitchedImageBlendFunc implements how all the tiles are blended together. // StitchedImageBlendMethod defines how tiles are blended together.
// This is called when a new cache image needs to be generated. type StitchedImageBlendMethod interface {
type StitchedImageBlendFunc func(tiles []*ImageTile, destImage *image.RGBA) Draw(tiles []*ImageTile, destImage *image.RGBA) // Draw is called when a new cache image is generated.
}
type StitchedImageOverlay interface { type StitchedImageOverlay interface {
Draw(*image.RGBA) Draw(*image.RGBA)
@ -30,7 +31,7 @@ type StitchedImageOverlay interface {
type StitchedImage struct { type StitchedImage struct {
tiles []ImageTile tiles []ImageTile
bounds image.Rectangle bounds image.Rectangle
blendFunc StitchedImageBlendFunc blendMethod StitchedImageBlendMethod
overlays []StitchedImageOverlay overlays []StitchedImageOverlay
cacheHeight int cacheHeight int
@ -40,12 +41,12 @@ type StitchedImage struct {
} }
// NewStitchedImage creates a new image from several single image tiles. // NewStitchedImage creates a new image from several single image tiles.
func NewStitchedImage(tiles []ImageTile, bounds image.Rectangle, blendFunc StitchedImageBlendFunc, cacheHeight int, overlays []StitchedImageOverlay) (*StitchedImage, error) { func NewStitchedImage(tiles []ImageTile, bounds image.Rectangle, blendMethod StitchedImageBlendMethod, cacheHeight int, overlays []StitchedImageOverlay) (*StitchedImage, error) {
if bounds.Empty() { if bounds.Empty() {
return nil, fmt.Errorf("given boundaries are empty") return nil, fmt.Errorf("given boundaries are empty")
} }
if blendFunc == nil { if blendMethod == nil {
return nil, fmt.Errorf("no blending function given") return nil, fmt.Errorf("no blending method given")
} }
if cacheHeight <= 0 { if cacheHeight <= 0 {
return nil, fmt.Errorf("invalid cache height of %d pixels", cacheHeight) return nil, fmt.Errorf("invalid cache height of %d pixels", cacheHeight)
@ -54,7 +55,7 @@ func NewStitchedImage(tiles []ImageTile, bounds image.Rectangle, blendFunc Stitc
return &StitchedImage{ return &StitchedImage{
tiles: tiles, tiles: tiles,
bounds: bounds, bounds: bounds,
blendFunc: blendFunc, blendMethod: blendMethod,
overlays: overlays, overlays: overlays,
cacheHeight: cacheHeight, cacheHeight: cacheHeight,
cacheImage: &image.RGBA{}, cacheImage: &image.RGBA{},
@ -148,7 +149,7 @@ func (si *StitchedImage) regenerateCache(rect image.Rectangle) {
} }
// Blend tiles into image at the workload rectangle. // Blend tiles into image at the workload rectangle.
si.blendFunc(workloadTiles, cacheImage.SubImage(workload).(*image.RGBA)) si.blendMethod.Draw(workloadTiles, cacheImage.SubImage(workload).(*image.RGBA))
} }
}() }()
} }