grokify/mogo

View on GitHub
image/imageutil/modify.go

Summary

Maintainability
B
5 hrs
Test Coverage
package imageutil

import (
    "fmt"
    "image"
    "image/color"
    "strings"

    "github.com/grokify/mogo/image/colors"
    "golang.org/x/image/draw"
)

// AddBackgroundColor adds a background of `color.Color` to an image.
// It is is useful when the image has a transparent background. Use
// `colornames` for more colors, e.g. `colornames.Blue`. This returns
// a `draw.Image` so it can be used as an input to `draw.Draw()`.
func AddBackgroundColor(img image.Image, clr color.Color) draw.Image {
    imgNew := image.NewRGBA(img.Bounds())
    draw.Draw(imgNew, imgNew.Bounds(), &image.Uniform{clr}, image.Point{}, draw.Src)
    draw.Draw(imgNew, imgNew.Bounds(), img, img.Bounds().Min, draw.Over)
    return imgNew
}

// AddBackgroundWhite adds a white background which is usable
// when the image has a transparent background.
func AddBackgroundWhite(img image.Image) image.Image {
    return AddBackgroundColor(img, color.White)
}

// Resize scales an image to the provided size units. Use a 0
// to scale the aspect ratio. See gitub.com/nfnt/resize for Lanczos3, etc.
// https://github.com/nfnt/resize .
func Resize(width, height uint, src image.Image, scale draw.Scaler) image.Image {
    if width == 0 && height == 0 {
        return image.NewRGBA(image.Rect(0, 0, 0, 0))
    } else if width == 0 && height != 0 {
        width = uint(ImageAspect(src) * float64(height))
    } else if height == 0 && width != 0 {
        height = uint(float64(width) / ImageAspect(src))
    }
    rect := image.Rect(0, 0, int(width), int(height))
    dst := image.NewRGBA(rect)
    if scale == nil {
        scale = ScalerBest()
    }
    scale.Scale(dst, rect, src, src.Bounds(), draw.Over, nil)
    return dst
}

func ResizeMaxDimension(maxSide uint, src image.Image, scale draw.Scaler) image.Image {
    width := src.Bounds().Dx()
    height := src.Bounds().Dy()
    if width > height {
        if width == int(maxSide) {
            return src
        }
        return Resize(maxSide, 0, src, scale)
    }
    if height == int(maxSide) {
        return src
    }
    return Resize(0, maxSide, src, scale)
}

// ResizeMax resizes an image to maximum dimensions. To resize
// to a maximum of 800 pixels width, the following can be used:
// `ResizeMax(800, 0, img, nil)`.
func ResizeMax(maxWidth, maxHeight uint, src image.Image, scale draw.Scaler) image.Image {
    srcWidth := uint(src.Bounds().Dx())
    srcHeight := uint(src.Bounds().Dy())
    if srcWidth <= maxWidth && srcHeight <= maxHeight {
        return src
    }
    outWidth := uint(0)
    outHeight := uint(0)
    if maxHeight == 0 {
        outWidth = maxWidth
    } else if maxWidth == 0 {
        outHeight = maxHeight
    } else {
        wRatio := float64(maxWidth) / float64(srcWidth)
        hRatio := float64(maxHeight) / float64(srcHeight)
        if wRatio < hRatio {
            outHeight = maxHeight
        } else {
            outWidth = maxWidth
        }
    }
    return Resize(outWidth, outHeight, src, scale)
}

// ResizeMin resizes an image to minimum dimensions. To resize
// to a minimum of 800 pixels width, the following can be used:
// `ResizeMin(800, 0, img, nil)`.
func ResizeMin(minWidth, minHeight uint, src image.Image, scale draw.Scaler) image.Image {
    srcWidth := uint(src.Bounds().Dx())
    srcHeight := uint(src.Bounds().Dy())
    if srcWidth >= minWidth && srcHeight >= minHeight {
        return src
    }
    outWidth := uint(0)
    outHeight := uint(0)
    if minHeight == 0 {
        outWidth = minWidth
    } else if minWidth == 0 {
        outHeight = minHeight
    } else {
        wRatio := float64(minWidth) / float64(srcWidth)
        hRatio := float64(minHeight) / float64(srcHeight)
        if wRatio > hRatio {
            outHeight = minHeight
        } else {
            outWidth = minWidth
        }
    }
    return Resize(outWidth, outHeight, src, scale)
}

// Scale will resize the image to the provided rectangle using the
// provided interpolation function.
func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image {
    dst := image.NewRGBA(rect)
    scale.Scale(dst, rect, src, src.Bounds(), draw.Over, nil)
    return dst
}

func SliceXY(images []image.Image, maxIdx int) (minX, maxX, minY, maxY, sumX, sumY int) {
    for i, img := range images {
        if maxIdx >= 0 && i > maxIdx {
            break
        }
        sumX += img.Bounds().Dx()
        sumY += img.Bounds().Dy()
        if i == 0 {
            minX = img.Bounds().Dx()
            maxX = img.Bounds().Dx()
            minY = img.Bounds().Dy()
            maxY = img.Bounds().Dy()
            continue
        }
        if minX > img.Bounds().Dx() {
            minX = img.Bounds().Dx()
        }
        if maxX < img.Bounds().Dx() {
            maxX = img.Bounds().Dx()
        }
        if minY > img.Bounds().Dy() {
            minY = img.Bounds().Dy()
        }
        if maxY < img.Bounds().Dy() {
            maxY = img.Bounds().Dy()
        }
    }
    return
}

func ResizeSameX(images []image.Image, larger bool) []image.Image {
    // minX, maxX, _, _, _, _ := SliceXY(images, -1)
    imgs := Images(images)
    minX := imgs.DxMin()
    maxX := imgs.DxMax()
    for i, img := range images {
        if larger && img.Bounds().Dx() != maxX {
            images[i] = Resize(uint(maxX), 0, img, ScalerBest())
        } else if !larger && img.Bounds().Dx() != minX {
            images[i] = Resize(uint(minX), 0, img, ScalerBest())
        }
    }
    return images
}

func ResizeSameY(images []image.Image, larger bool) []image.Image {
    // _, _, minY, maxY, _, _ := SliceXY(images, -1)
    imgs := Images(images)
    minY := imgs.DyMin()
    maxY := imgs.DyMax()
    for i, img := range images {
        if larger && img.Bounds().Dy() != maxY {
            images[i] = Resize(0, uint(maxY), img, ScalerBest())
        } else if !larger && img.Bounds().Dy() != minY {
            images[i] = Resize(0, uint(minY), img, ScalerBest())
        }
    }
    return images
}

// ScalerDefault returns a general best results interpolation
// algorithm. See more here https://blog.codinghorror.com/better-image-resizing/ ,
// https://support.esri.com/en/technical-article/000005606 ,
// https://stackoverflow.com/questions/384991/what-is-the-best-image-downscaling-algorithm-quality-wise/6171860 .
func ScalerDefault() draw.Scaler { return draw.BiLinear }

func ScalerBest() draw.Scaler { return draw.CatmullRom }

const (
    AlgNearestNeighbor = "nearestneighbor"
    AlgApproxBiLinear  = "approxbilinear"
    AlgBiLinear        = "bilinear"
    AlgCatmullRom      = "catmullrom"
)

func ParseScaler(rawInterpolation string) (draw.Scaler, error) {
    rawInterpolation = strings.ToLower(strings.TrimSpace(rawInterpolation))
    switch rawInterpolation {
    case AlgNearestNeighbor:
        return draw.NearestNeighbor, nil
    case AlgApproxBiLinear:
        return draw.ApproxBiLinear, nil
    case AlgBiLinear:
        return draw.BiLinear, nil
    case AlgCatmullRom:
        return draw.CatmullRom, nil
    }
    return draw.NearestNeighbor, fmt.Errorf("cannot parse Scalar [%s]", rawInterpolation)
}

func PaintColor(img draw.Image, clr color.Color, area image.Rectangle) {
    if img == nil {
        return
    }
    rectImg := img.Bounds()

    for x := area.Min.X; x < area.Max.X; x++ {
        if x < rectImg.Min.X || x > rectImg.Max.X {
            continue
        }
        for y := area.Min.Y; y < area.Max.Y; y++ {
            if y < rectImg.Min.Y || y > rectImg.Max.Y {
                continue
            }
            img.Set(x, y, clr)
        }
    }
}

// AddBorder adds a border to a `draw.Image`. If you have an `image.Image`,
// first convert it with `ImageToRGBA(img)`.
func AddBorder(img draw.Image, clr color.Color, width uint) draw.Image {
    if img == nil || width == 0 {
        return img
    }
    border := int(width)
    w, h := img.Bounds().Dx(), img.Bounds().Dy()
    w2 := w + border*2
    h2 := h + border*2
    i2 := image.NewRGBA(image.Rect(0, 0, w2-1, h2-1))
    for x := 0; x < w2; x++ {
        for y := 0; y < h2; y++ {
            if x < border || x > h+border {
                i2.Set(x, y, clr)
            } else if y < border || y > w+border {
                i2.Set(x, y, clr)
            } else {
                i2.Set(x, y, img.At(x-border, y-border))
            }
        }
    }
    return i2
}

func AddBorderAverageColor(img image.Image, width uint) image.Image {
    if img == nil || width == 0 {
        return img
    }
    imgRGBA := ImageToRGBA(img)
    return AddBorder(imgRGBA, colors.ColorAverageImage(imgRGBA), width)
}

// Information on rotation:
//
// https://www.golangprograms.com/how-to-rotate-an-image-in-golang.html
// https://code.google.com/archive/p/graphics-go/
// https://github.com/BurntSushi/graphics-go
// graphics.Rotate(dstImage, srcImage, &graphics.RotateOptions{math.Pi / 2.0}