From df6c27924b7c2578fcd80e9620da013ed041fd0e Mon Sep 17 00:00:00 2001 From: David Vogel Date: Thu, 11 Aug 2022 11:47:18 +0200 Subject: [PATCH] Change from blend func to interface - Combine all previous blend modes into one blend method - Optimize BlendMethodMedian --- .../{blend-functions.go => blend-methods.go} | 67 ++++++++++--------- bin/stitch/main.go | 6 +- bin/stitch/stitched-image.go | 25 +++---- 3 files changed, 54 insertions(+), 44 deletions(-) rename bin/stitch/{blend-functions.go => blend-methods.go} (78%) diff --git a/bin/stitch/blend-functions.go b/bin/stitch/blend-methods.go similarity index 78% rename from bin/stitch/blend-functions.go rename to bin/stitch/blend-methods.go index e859ccb..1f200b8 100644 --- a/bin/stitch/blend-functions.go +++ b/bin/stitch/blend-methods.go @@ -11,10 +11,19 @@ import ( "sort" ) -// BlendFuncMedian takes the given tiles and median blends them into destImage. -func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) { +// BlendMethodMedian takes the given tiles and median blends them into destImage. +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() + 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. // Can contain empty/nil entries for images that failed to load. images := []*image.RGBA{} @@ -29,7 +38,7 @@ func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) { for ix := bounds.Min.X; ix < bounds.Max.X; ix++ { rList, gList, bList := rListEmpty, gListEmpty, bListEmpty point := image.Point{ix, iy} - found := false + count := 0 // Iterate through all images and create a list of colors. for _, img := range images { @@ -37,43 +46,39 @@ func BlendFuncMedian(tiles []*ImageTile, destImage *image.RGBA) { if point.In(img.Bounds()) { 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)) - 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 !found { + if count == 0 { continue } - // Sort colors. - sort.Ints(rList) - sort.Ints(gList) - sort.Ints(bList) + // Sort colors. Not needed if there is only one color. + if count > 1 { + sort.Ints(rList) + sort.Ints(gList) + sort.Ints(bList) + } // Take the middle element of each color. var r, g, b uint8 - if l := len(rList); l%2 == 0 { - // Even. - r = uint8((rList[l/2-1] + rList[l/2]) / 2) - } else { - // Odd. - r = uint8(rList[(l-1)/2]) - } - if l := len(gList); l%2 == 0 { - // Even. - 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]) + switch count % 2 { + case 0: // Even. + r = uint8((rList[count/2-1] + rList[count/2]) / 2) + g = uint8((gList[count/2-1] + gList[count/2]) / 2) + b = uint8((bList[count/2-1] + bList[count/2]) / 2) + default: // Odd. + r = uint8(rList[(count-1)/2]) + g = uint8(gList[(count-1)/2]) + b = uint8(bList[(count-1)/2]) } 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) { bounds := destImage.Bounds() @@ -197,4 +202,4 @@ func BlendNewestPixelsMedian(tiles []*ImageTile, destImage *image.RGBA) { destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255}) } } -} +}*/ diff --git a/bin/stitch/main.go b/bin/stitch/main.go index 84fc000..e3ea395 100644 --- a/bin/stitch/main.go +++ b/bin/stitch/main.go @@ -274,7 +274,11 @@ func main() { var wg sync.WaitGroup 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 { log.Panicf("NewStitchedImage() failed: %v", err) } diff --git a/bin/stitch/stitched-image.go b/bin/stitch/stitched-image.go index 743a3e7..fba0c6f 100644 --- a/bin/stitch/stitched-image.go +++ b/bin/stitch/stitched-image.go @@ -17,9 +17,10 @@ import ( // TODO: Find optimal grid size that works good for tiles with lots and few overlap var StitchedImageCacheGridSize = 512 -// StitchedImageBlendFunc implements how all the tiles are blended together. -// This is called when a new cache image needs to be generated. -type StitchedImageBlendFunc func(tiles []*ImageTile, destImage *image.RGBA) +// StitchedImageBlendMethod defines how tiles are blended together. +type StitchedImageBlendMethod interface { + Draw(tiles []*ImageTile, destImage *image.RGBA) // Draw is called when a new cache image is generated. +} type StitchedImageOverlay interface { Draw(*image.RGBA) @@ -28,10 +29,10 @@ type StitchedImageOverlay interface { // StitchedImage combines several ImageTile objects into a single RGBA image. // The way the images are combined/blended is defined by the blendFunc. type StitchedImage struct { - tiles []ImageTile - bounds image.Rectangle - blendFunc StitchedImageBlendFunc - overlays []StitchedImageOverlay + tiles []ImageTile + bounds image.Rectangle + blendMethod StitchedImageBlendMethod + overlays []StitchedImageOverlay cacheHeight int cacheImage *image.RGBA @@ -40,12 +41,12 @@ type StitchedImage struct { } // 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() { return nil, fmt.Errorf("given boundaries are empty") } - if blendFunc == nil { - return nil, fmt.Errorf("no blending function given") + if blendMethod == nil { + return nil, fmt.Errorf("no blending method given") } if cacheHeight <= 0 { 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{ tiles: tiles, bounds: bounds, - blendFunc: blendFunc, + blendMethod: blendMethod, overlays: overlays, cacheHeight: cacheHeight, cacheImage: &image.RGBA{}, @@ -148,7 +149,7 @@ func (si *StitchedImage) regenerateCache(rect image.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)) } }() }