disintegration / imaging

Imaging is a simple image processing package for Go
MIT License
5.28k stars 440 forks source link

How to turn a picture into a circle #72

Closed realtech-inc closed 6 years ago

disintegration commented 6 years ago

Hello,

You can write a simple function to crop an image to a square and set all the pixels outside of the circle to a transparent color. For example:

package main

import (
    "image"
    "image/color"
    "log"

    "github.com/disintegration/imaging"
)

func main() {
    img, err := imaging.Open("branches.jpg")
    if err != nil {
        log.Fatal(err)
    }

    img = makeCircle(img)

    err = imaging.Save(img, "circle.png")
    if err != nil {
        log.Fatal(err)
    }
}

func makeCircle(src image.Image) image.Image {
    d := src.Bounds().Dx()
    if src.Bounds().Dy() < d {
        d = src.Bounds().Dy()
    }
    dst := imaging.CropCenter(src, d, d)
    r := d / 2
    for x := 0; x < d; x++ {
        for y := 0; y < d; y++ {
            if (x-r)*(x-r)+(y-r)*(y-r) > r*r {
                dst.SetNRGBA(x, y, color.NRGBA{0, 0, 0, 0})
            }
        }
    }
    return dst
}

branches.jpg:

branches

circle.png:

circle

Is this what you need?

There is also the standard image/draw package that may be helpful to you. See the DrawMask function and this article in the Go blog: https://blog.golang.org/go-imagedraw-package

agaskell commented 6 years ago

Is there a way to add anti aliasing to the circle with this package or image/draw?

disintegration commented 6 years ago

@agaskell I've improved the code example slightly, adding a smoothness effect to the edge of a circle:

package main

import (
    "image"
    "image/color"
    "log"
    "math"

    "github.com/disintegration/imaging"
)

func main() {
    img, err := imaging.Open("branches.jpg")
    if err != nil {
        log.Fatal(err)
    }

    img = makeCircleSmooth(img, 1)

    err = imaging.Save(img, "circle.png")
    if err != nil {
        log.Fatal(err)
    }
}

func makeCircleSmooth(src image.Image, factor float64) image.Image {
    d := src.Bounds().Dx()
    if src.Bounds().Dy() < d {
        d = src.Bounds().Dy()
    }
    dst := imaging.CropCenter(src, d, d)
    r := float64(d) / 2
    for x := 0; x < d; x++ {
        for y := 0; y < d; y++ {
            xf := float64(x)
            yf := float64(y)
            delta := math.Sqrt((xf-r)*(xf-r)+(yf-r)*(yf-r)) + factor - r
            switch {
            case delta > factor:
                dst.SetNRGBA(x, y, color.NRGBA{0, 0, 0, 0})
            case delta > 0:
                m := 1 - delta/factor
                c := dst.NRGBAAt(x, y)
                c.A = uint8(float64(c.A) * m)
                dst.SetNRGBA(x, y, c)
            }
        }
    }
    return dst
}

You can use factor = 1 to add a simple anti-aliasing:

circle

Higher factor values will increase the smoothness effect (e.g. factor=10):

circle-10

disintegration commented 6 years ago

@agaskell I've just noticed that the resulting image is shifted one pixel to the bottom-right corner. Here's a more accurate calculation:

func makeCircleSmooth(src image.Image, factor float64) image.Image {
    d := src.Bounds().Dx()
    if src.Bounds().Dy() < d {
        d = src.Bounds().Dy()
    }
    dst := imaging.CropCenter(src, d, d)
    r := float64(d) / 2
    center := r - 0.5
    for x := 0; x < d; x++ {
        for y := 0; y < d; y++ {
            xf := float64(x)
            yf := float64(y)
            delta := math.Sqrt((xf-center)*(xf-center)+(yf-center)*(yf-center)) + factor - r
            switch {
            case delta > factor:
                dst.SetNRGBA(x, y, color.NRGBA{0, 0, 0, 0})
            case delta > 0:
                m := 1 - delta/factor
                c := dst.NRGBAAt(x, y)
                c.A = uint8(float64(c.A) * m)
                dst.SetNRGBA(x, y, c)
            }
        }
    }
    return dst
}