diff --git a/base64.go b/base64.go index 20ce2e4..11a9eaa 100644 --- a/base64.go +++ b/base64.go @@ -1,54 +1,54 @@ package imgo import ( - "bytes" - "encoding/base64" - "image/png" - "strings" + "bytes" + "encoding/base64" + "image/png" + "strings" ) // ToBase64 returns the base64 encoded string of the image. func (i Image) ToBase64() string { - buff := bytes.NewBuffer(nil) - err := png.Encode(buff, i.image) - if err != nil { - return "" - } - return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buff.Bytes()) + buff := bytes.NewBuffer(nil) + err := png.Encode(buff, i.image) + if err != nil { + return "" + } + return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buff.Bytes()) } // LoadFromBase64 loads an image from a base64 encoded string. func LoadFromBase64(base64Str string) (i *Image) { - i = &Image{} - base64Str = strings.Split(base64Str, ",")[1] + i = &Image{} + base64Str = strings.Split(base64Str, ",")[1] - // Decode the base64 string - decodeString, err := base64.StdEncoding.DecodeString(base64Str) - if err != nil { - i.addError(err) - return - } + // Decode the base64 string + decodeString, err := base64.StdEncoding.DecodeString(base64Str) + if err != nil { + i.addError(err) + return + } - // Get the extension, mimetype and corresponding decoder function of the image. - ext, mimetype, decoder, err := GetImageType(decodeString[:8]) - if err != nil { - i.addError(err) - return - } + // Get the extension, mimetype and corresponding decoder function of the image. + ext, mimetype, decoder, err := GetImageType(decodeString[:8]) + if err != nil { + i.addError(err) + return + } - // Decode the image. - buff := bytes.NewBuffer(decodeString) - img, err := decoder(buff) - if err != nil { - i.addError(err) - return - } + // Decode the image. + buff := bytes.NewBuffer(decodeString) + img, err := decoder(buff) + if err != nil { + i.addError(err) + return + } - return &Image{ - image: Image2RGBA(img), - width: img.Bounds().Dx(), - height: img.Bounds().Dy(), - extension: ext, - mimetype: mimetype, - } + return &Image{ + image: Image2RGBA(img), + width: img.Bounds().Dx(), + height: img.Bounds().Dy(), + extension: ext, + mimetype: mimetype, + } } diff --git a/blur.go b/blur.go index 2e276f0..1aabe1f 100644 --- a/blur.go +++ b/blur.go @@ -1,143 +1,143 @@ package imgo import ( - "github.com/BurntSushi/graphics-go/graphics" - "image" - "image/color" - "sync" + "github.com/BurntSushi/graphics-go/graphics" + "image" + "image/color" + "sync" ) // GaussianBlur returns a blurred image. // ksize is Gaussian kernel size // sigma is Gaussian kernel standard deviation. func (i *Image) GaussianBlur(ksize int, sigma float64) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - dst := image.NewRGBA(i.image.Bounds()) - err := graphics.Blur(dst, i.image, &graphics.BlurOptions{ - StdDev: sigma, - Size: ksize, - }) + dst := image.NewRGBA(i.image.Bounds()) + err := graphics.Blur(dst, i.image, &graphics.BlurOptions{ + StdDev: sigma, + Size: ksize, + }) - if err != nil { - i.addError(err) - return i - } + if err != nil { + i.addError(err) + return i + } - i.image = dst - i.width = dst.Bounds().Dx() - i.height = dst.Bounds().Dy() + i.image = dst + i.width = dst.Bounds().Dx() + i.height = dst.Bounds().Dy() - return i + return i } // normalizeKernel normalizes a kernel. func (i Image) normalizeKernel(kernel [][]float64) { - var sum float64 - for _, row := range kernel { - for _, value := range row { - sum += value - } - } - for i, row := range kernel { - for j, value := range row { - kernel[i][j] = value / sum - } - } + var sum float64 + for _, row := range kernel { + for _, value := range row { + sum += value + } + } + for i, row := range kernel { + for j, value := range row { + kernel[i][j] = value / sum + } + } } // Blur returns a blurred image. // ksize is filter kernel size, it must be a odd number. func (i *Image) Blur(ksize int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if ksize < 2 { - return i - } + if ksize < 2 { + return i + } - if ksize%2 == 0 { - ksize++ - } + if ksize%2 == 0 { + ksize++ + } - kernel := make([][]float64, ksize) - for p := range kernel { - row := make([]float64, ksize) - for q := 0; q < ksize; q++ { - row[q] = 1 - } - kernel[p] = row - } + kernel := make([][]float64, ksize) + for p := range kernel { + row := make([]float64, ksize) + for q := 0; q < ksize; q++ { + row[q] = 1 + } + kernel[p] = row + } - i.imageFilterFast(kernel) + i.imageFilterFast(kernel) - return i + return i } // imageFilterFast returns a filtered image with Goroutine. func (i *Image) imageFilterFast(kernel [][]float64) *Image { - i.normalizeKernel(kernel) - dst := image.NewRGBA(i.image.Bounds()) - kernelSize := len(kernel) - radius := (kernelSize - 1) / 2 - wg := sync.WaitGroup{} - convolution := func(x, y int, wg *sync.WaitGroup) { - defer wg.Done() - var sumR, sumG, sumB, sumA uint16 - for p := -radius; p <= radius; p++ { - for q := -radius; q <= radius; q++ { - trueX := x + q - trueY := y + p - if image.Pt(trueX, trueY).In(i.Bounds()) { - thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() - sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) - sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) - sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) - sumA += uint16(float64(thisA) * kernel[q+radius][p+radius]) - } - } - } - dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA}) - } - wg.Add(i.width * i.height) - for y := 0; y < i.height; y++ { - for x := 0; x < i.width; x++ { - go convolution(x, y, &wg) - } - } - wg.Wait() - i.image = dst - return i + i.normalizeKernel(kernel) + dst := image.NewRGBA(i.image.Bounds()) + kernelSize := len(kernel) + radius := (kernelSize - 1) / 2 + wg := sync.WaitGroup{} + convolution := func(x, y int, wg *sync.WaitGroup) { + defer wg.Done() + var sumR, sumG, sumB, sumA uint16 + for p := -radius; p <= radius; p++ { + for q := -radius; q <= radius; q++ { + trueX := x + q + trueY := y + p + if image.Pt(trueX, trueY).In(i.Bounds()) { + thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() + sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) + sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) + sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) + sumA += uint16(float64(thisA) * kernel[q+radius][p+radius]) + } + } + } + dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA}) + } + wg.Add(i.width * i.height) + for y := 0; y < i.height; y++ { + for x := 0; x < i.width; x++ { + go convolution(x, y, &wg) + } + } + wg.Wait() + i.image = dst + return i } // imageFilter returns a filtered image. func (i *Image) imageFilter(kernel [][]float64) *Image { - i.normalizeKernel(kernel) - dst := image.NewNRGBA64(i.image.Bounds()) - kernelSize := len(kernel) - radius := (kernelSize - 1) / 2 - for y := 0; y < i.height; y++ { - for x := 0; x < i.width; x++ { - var sumR, sumG, sumB, sumA uint16 - for p := -radius; p <= radius; p++ { - for q := -radius; q <= radius; q++ { - trueX := x + q - trueY := y + p - if image.Pt(trueX, trueY).In(i.Bounds()) { - thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() - sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) - sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) - sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) - sumA += uint16(float64(thisA) * kernel[q+radius][p+radius]) - } - } - } - dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA}) - } - } - i.image = Image2RGBA(dst) - return i + i.normalizeKernel(kernel) + dst := image.NewNRGBA64(i.image.Bounds()) + kernelSize := len(kernel) + radius := (kernelSize - 1) / 2 + for y := 0; y < i.height; y++ { + for x := 0; x < i.width; x++ { + var sumR, sumG, sumB, sumA uint16 + for p := -radius; p <= radius; p++ { + for q := -radius; q <= radius; q++ { + trueX := x + q + trueY := y + p + if image.Pt(trueX, trueY).In(i.Bounds()) { + thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() + sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) + sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) + sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) + sumA += uint16(float64(thisA) * kernel[q+radius][p+radius]) + } + } + } + dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA}) + } + } + i.image = Image2RGBA(dst) + return i } diff --git a/color.go b/color.go index cdd5705..3ed0938 100644 --- a/color.go +++ b/color.go @@ -1,36 +1,36 @@ package imgo import ( - "image/color" - "log" + "image/color" + "log" ) // PickColor returns the color of the pixel at (x, y). func (i Image) PickColor(x, y int) (res color.RGBA) { - if i.Error != nil { - return - } + if i.Error != nil { + return + } - if x < 0 || x > i.width || y < 0 || y > i.height { - return - } - pixel := i.image.At(x, y) - return color.RGBAModel.Convert(pixel).(color.RGBA) + if x < 0 || x > i.width || y < 0 || y > i.height { + return + } + pixel := i.image.At(x, y) + return color.RGBAModel.Convert(pixel).(color.RGBA) } // MainColor returns the main color of the image func (i *Image) MainColor() (res color.RGBA) { - if i.Error != nil { - log.Println(i.Error) - return - } + if i.Error != nil { + log.Println(i.Error) + return + } - red, green, blue := i.calculateMeanAverageColorWithRect(i.image.Bounds(), true) + red, green, blue := i.calculateMeanAverageColorWithRect(i.image.Bounds(), true) - return color.RGBA{ - R: red, - G: green, - B: blue, - A: 0, - } + return color.RGBA{ + R: red, + G: green, + B: blue, + A: 0, + } } diff --git a/const.go b/const.go index fa6d465..f6f5300 100644 --- a/const.go +++ b/const.go @@ -4,6 +4,6 @@ package imgo type FlipType int const ( - Vertical FlipType = iota - Horizontal + Vertical FlipType = iota + Horizontal ) diff --git a/errors.go b/errors.go index 38436d6..270c582 100644 --- a/errors.go +++ b/errors.go @@ -3,9 +3,9 @@ package imgo import "errors" var ( - ErrSourceImageIsNil = errors.New("source image is nil") - ErrSourceNotSupport = errors.New("source not support") - ErrSourceStringIsEmpty = errors.New("source string is empty") - ErrSourceImageNotSupport = errors.New("source image not support") - ErrSaveImageFormatNotSupport = errors.New("save image format not support") + ErrSourceImageIsNil = errors.New("source image is nil") + ErrSourceNotSupport = errors.New("source not support") + ErrSourceStringIsEmpty = errors.New("source string is empty") + ErrSourceImageNotSupport = errors.New("source image not support") + ErrSaveImageFormatNotSupport = errors.New("save image format not support") ) diff --git a/examples/base64.go b/examples/base64.go index bd3ad39..351f87b 100644 --- a/examples/base64.go +++ b/examples/base64.go @@ -1,10 +1,10 @@ package main import ( - "github.com/fishtailstudio/imgo" + "github.com/fishtailstudio/imgo" ) func main() { - base64Img := imgo.Load("gopher.png").ToBase64() - imgo.Load(base64Img).Save("out.png") + base64Img := imgo.Load("gopher.png").ToBase64() + imgo.Load(base64Img).Save("out.png") } diff --git a/examples/blur.go b/examples/blur.go index eb807d9..edb8079 100644 --- a/examples/blur.go +++ b/examples/blur.go @@ -3,7 +3,7 @@ package main import "github.com/fishtailstudio/imgo" func main() { - imgo.Load("gopher.png"). - Blur(5). - Save("out.png") + imgo.Load("gopher.png"). + Blur(5). + Save("out.png") } diff --git a/examples/errors.go b/examples/errors.go index 5abee0c..3068f25 100644 --- a/examples/errors.go +++ b/examples/errors.go @@ -1,15 +1,15 @@ package main import ( - "fmt" - "github.com/fishtailstudio/imgo" + "fmt" + "github.com/fishtailstudio/imgo" ) func main() { - err := imgo.Load("gopher.jpg").Save("out.png").Error - if err != nil { - fmt.Println("error:", err.Error()) - } else { - fmt.Println("success") - } + err := imgo.Load("gopher.jpg").Save("out.png").Error + if err != nil { + fmt.Println("error:", err.Error()) + } else { + fmt.Println("success") + } } diff --git a/examples/http.go b/examples/http.go index 209eef9..73d3cb9 100644 --- a/examples/http.go +++ b/examples/http.go @@ -1,11 +1,11 @@ package main import ( - "github.com/fishtailstudio/imgo" - "net/http" + "github.com/fishtailstudio/imgo" + "net/http" ) func main() { - http.HandleFunc("/gopher", imgo.Load("gopher.png").HttpHandler) - http.ListenAndServe(":8080", nil) + http.HandleFunc("/gopher", imgo.Load("gopher.png").HttpHandler) + http.ListenAndServe(":8080", nil) } diff --git a/examples/insert.go b/examples/insert.go index 0281aed..149a062 100644 --- a/examples/insert.go +++ b/examples/insert.go @@ -1,12 +1,12 @@ package main import ( - "github.com/fishtailstudio/imgo" - "image/color" + "github.com/fishtailstudio/imgo" + "image/color" ) func main() { - imgo.Canvas(500, 500, color.White). - Insert("gopher.png", 100, 100). - Save("out.png") + imgo.Canvas(500, 500, color.White). + Insert("gopher.png", 100, 100). + Save("out.png") } diff --git a/examples/mosaic.go b/examples/mosaic.go index 2fd65fd..391ed56 100644 --- a/examples/mosaic.go +++ b/examples/mosaic.go @@ -3,7 +3,7 @@ package main import "github.com/fishtailstudio/imgo" func main() { - imgo.Load("gopher.png"). - Mosaic(5, 60, 50, 120, 100). - Save("out.png") + imgo.Load("gopher.png"). + Mosaic(5, 60, 50, 120, 100). + Save("out.png") } diff --git a/examples/radius.go b/examples/radius.go index 3cc8436..649d711 100644 --- a/examples/radius.go +++ b/examples/radius.go @@ -1,12 +1,12 @@ package main import ( - "github.com/fishtailstudio/imgo" - "image/color" + "github.com/fishtailstudio/imgo" + "image/color" ) func main() { - imgo.Canvas(300, 300, color.White). - BorderRadius(20). - Save("out.png") + imgo.Canvas(300, 300, color.White). + BorderRadius(20). + Save("out.png") } diff --git a/examples/shapes.go b/examples/shapes.go index 7609d22..38c2a9b 100644 --- a/examples/shapes.go +++ b/examples/shapes.go @@ -1,17 +1,17 @@ package main import ( - "github.com/fishtailstudio/imgo" - "golang.org/x/image/colornames" - "image/color" + "github.com/fishtailstudio/imgo" + "golang.org/x/image/colornames" + "image/color" ) func main() { - imgo.Canvas(500, 500, color.Black). - Pixel(10, 10, colornames.Blueviolet). - Line(20, 10, 50, 450, colornames.Gold). - Circle(200, 100, 50, colornames.Aqua). - Rectangle(150, 200, 100, 150, colornames.Darkblue). - Ellipse(400, 200, 150, 50, colornames.Tomato). - Save("out.png") + imgo.Canvas(500, 500, color.Black). + Pixel(10, 10, colornames.Blueviolet). + Line(20, 10, 50, 450, colornames.Gold). + Circle(200, 100, 50, colornames.Aqua). + Rectangle(150, 200, 100, 150, colornames.Darkblue). + Ellipse(400, 200, 150, 50, colornames.Tomato). + Save("out.png") } diff --git a/image.go b/image.go index f218bf2..9f24bb6 100644 --- a/image.go +++ b/image.go @@ -1,140 +1,140 @@ package imgo import ( - "fmt" - "github.com/BurntSushi/graphics-go/graphics" - cliColor "github.com/fatih/color" - "github.com/golang/freetype" - "github.com/nfnt/resize" - "golang.org/x/image/bmp" - "golang.org/x/image/font" - "golang.org/x/image/tiff" - "image" - "image/color" - "image/draw" - "image/jpeg" - "image/png" - "io/ioutil" - "log" - "math" - "net/http" - "os" - "runtime" - "strings" + "fmt" + "github.com/BurntSushi/graphics-go/graphics" + cliColor "github.com/fatih/color" + "github.com/golang/freetype" + "github.com/nfnt/resize" + "golang.org/x/image/bmp" + "golang.org/x/image/font" + "golang.org/x/image/tiff" + "image" + "image/color" + "image/draw" + "image/jpeg" + "image/png" + "io/ioutil" + "log" + "math" + "net/http" + "os" + "runtime" + "strings" ) type Image struct { - Error error - image *image.RGBA // the image - width int // image width - height int // image height - extension string // image extension - mimetype string // image mimetype - filesize int64 // image filesize - isGrayscale bool // is grayscale image + Error error + image *image.RGBA // the image + width int // image width + height int // image height + extension string // image extension + mimetype string // image mimetype + filesize int64 // image filesize + isGrayscale bool // is grayscale image } // ToImage returns the instance of image.Image of the image. func (i Image) ToImage() image.Image { - return i.image + return i.image } // String returns the image as a string. func (i Image) String() string { - return fmt.Sprintf("Extension: %v\nMimetype: %v\nWidth: %v\nHeight: %v\n", i.extension, i.mimetype, i.width, i.height) + return fmt.Sprintf("Extension: %v\nMimetype: %v\nWidth: %v\nHeight: %v\n", i.extension, i.mimetype, i.width, i.height) } // addError adds an error to imgo. // if OnlyReason is true, only the error message is returned. func (i *Image) addError(err error, OnlyReason ...bool) { - log.SetPrefix("[IMGO] ") + log.SetPrefix("[IMGO] ") - var onlyReason bool - if len(OnlyReason) > 0 { - onlyReason = OnlyReason[0] - } + var onlyReason bool + if len(OnlyReason) > 0 { + onlyReason = OnlyReason[0] + } - yellow := cliColor.New(cliColor.FgYellow).SprintFunc() - magenta := cliColor.New(cliColor.FgMagenta).SprintFunc() + yellow := cliColor.New(cliColor.FgYellow).SprintFunc() + magenta := cliColor.New(cliColor.FgMagenta).SprintFunc() - _, file, line, ok := runtime.Caller(1) + _, file, line, ok := runtime.Caller(1) - if i.Error == nil { - if ok && !onlyReason { - i.Error = fmt.Errorf("%v %v", yellow(file, ":", line), magenta("Error: ", err.Error())) - } else { - i.Error = err - } - } else if err != nil { - if ok && !onlyReason { - i.Error = fmt.Errorf("%v\n%v %v", i.Error, yellow(file, ":", line), magenta("Error: ", err.Error())) - } else { - i.Error = fmt.Errorf("%v\n%v", i.Error, err) - } - } + if i.Error == nil { + if ok && !onlyReason { + i.Error = fmt.Errorf("%v %v", yellow(file, ":", line), magenta("Error: ", err.Error())) + } else { + i.Error = err + } + } else if err != nil { + if ok && !onlyReason { + i.Error = fmt.Errorf("%v\n%v %v", i.Error, yellow(file, ":", line), magenta("Error: ", err.Error())) + } else { + i.Error = fmt.Errorf("%v\n%v", i.Error, err) + } + } } // Extension returns the extension of the image. func (i Image) Extension() string { - return i.extension + return i.extension } // Mimetype returns the mimetype of the image. func (i Image) Mimetype() string { - return i.mimetype + return i.mimetype } // Height returns the height of the image. func (i Image) Height() int { - return i.height + return i.height } // Width returns the width of the image. func (i Image) Width() int { - return i.width + return i.width } // Filesize returns the filesize of the image, if instance is initiated from an actual file. func (i Image) Filesize() int64 { - return i.filesize + return i.filesize } // Bounds returns the bounds of the image. func (i Image) Bounds() image.Rectangle { - return i.image.Bounds() + return i.image.Bounds() } // Insert inserts source into the image at given (x, y) coordinate. // source can be a file path, a URL, a base64 encoded string, an *os.File, an image.Image, // a byte slice or an *Image. func (i *Image) Insert(source interface{}, x, y int) *Image { - // the image to insert - insert := &Image{} - switch source.(type) { - case *Image: - insert = source.(*Image) - default: - insert = Load(source) - } + // the image to insert + insert := &Image{} + switch source.(type) { + case *Image: + insert = source.(*Image) + default: + insert = Load(source) + } - // check errors - if insert.Error != nil { - i.addError(insert.Error, true) - return i - } - if i.Error != nil { - return i - } + // check errors + if insert.Error != nil { + i.addError(insert.Error, true) + return i + } + if i.Error != nil { + return i + } - // check x and y is within the image - if x > i.width || y > i.height { - return i - } + // check x and y is within the image + if x > i.width || y > i.height { + return i + } - // insert the image - draw.Draw(i.image, i.Bounds(), insert.image, insert.image.Bounds().Min.Sub(image.Pt(x, y)), draw.Over) + // insert the image + draw.Draw(i.image, i.Bounds(), insert.image, insert.image.Bounds().Min.Sub(image.Pt(x, y)), draw.Over) - return i + return i } // Save saves the image to the specified path. @@ -142,276 +142,276 @@ func (i *Image) Insert(source interface{}, x, y int) *Image { // path is the path the image will be saved to. // quality is the quality of the image, between 1 and 100, default is 100, and is only used for jpeg images. func (i *Image) Save(path string, quality ...int) *Image { - if i.Error != nil { - log.Println(i.Error) - return i - } + if i.Error != nil { + log.Println(i.Error) + return i + } - // check extension - pathSplit := strings.Split(path, ".") - extension := pathSplit[len(pathSplit)-1] - if !(extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "tiff" || extension == "bmp") { - i.addError(ErrSaveImageFormatNotSupport) - log.Println(i.Error) - return i - } + // check extension + pathSplit := strings.Split(path, ".") + extension := pathSplit[len(pathSplit)-1] + if !(extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "tiff" || extension == "bmp") { + i.addError(ErrSaveImageFormatNotSupport) + log.Println(i.Error) + return i + } - // create file - file, err := os.Create(path) - if err != nil { - i.addError(err) - log.Println(i.Error) - return i - } - defer func(file *os.File) { - err := file.Close() - if err != nil { - log.Println(err) - } - }(file) + // create file + file, err := os.Create(path) + if err != nil { + i.addError(err) + log.Println(i.Error) + return i + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + log.Println(err) + } + }(file) - // get the image - var img image.Image - if i.isGrayscale { // grayscale image - gray := image.NewGray(i.image.Bounds()) - for x := 0; x < i.width; x++ { - for y := 0; y < i.height; y++ { - rgbColor := i.image.At(x, y) - grayColor := gray.ColorModel().Convert(rgbColor) - gray.Set(x, y, grayColor) - } - } - img = gray - } else { // RGBA image - img = i.image - } + // get the image + var img image.Image + if i.isGrayscale { // grayscale image + gray := image.NewGray(i.image.Bounds()) + for x := 0; x < i.width; x++ { + for y := 0; y < i.height; y++ { + rgbColor := i.image.At(x, y) + grayColor := gray.ColorModel().Convert(rgbColor) + gray.Set(x, y, grayColor) + } + } + img = gray + } else { // RGBA image + img = i.image + } - // save image to file - if extension == "png" { - err = png.Encode(file, img) - } else if extension == "jpg" || extension == "jpeg" { - if len(quality) > 0 && quality[0] > 0 && quality[0] < 100 { - err = jpeg.Encode(file, img, &jpeg.Options{Quality: quality[0]}) - } else { - err = jpeg.Encode(file, img, &jpeg.Options{Quality: 100}) - } - } else if extension == "tiff" { - err = tiff.Encode(file, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) - } else if extension == "bmp" { - err = bmp.Encode(file, img) - } + // save image to file + if extension == "png" { + err = png.Encode(file, img) + } else if extension == "jpg" || extension == "jpeg" { + if len(quality) > 0 && quality[0] > 0 && quality[0] < 100 { + err = jpeg.Encode(file, img, &jpeg.Options{Quality: quality[0]}) + } else { + err = jpeg.Encode(file, img, &jpeg.Options{Quality: 100}) + } + } else if extension == "tiff" { + err = tiff.Encode(file, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) + } else if extension == "bmp" { + err = bmp.Encode(file, img) + } - if err != nil { - i.addError(err) - log.Println(i.Error) - return i - } - return i + if err != nil { + i.addError(err) + log.Println(i.Error) + return i + } + return i } // Resize resizes the image to the specified width and height. func (i *Image) Resize(width, height int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if width == i.width || height == i.height || (width == 0 && height == 0) { - return i - } + if width == i.width || height == i.height || (width == 0 && height == 0) { + return i + } - resized := resize.Resize(uint(width), uint(height), i.image, resize.Lanczos3) - i.image = Image2RGBA(resized) - i.width = resized.Bounds().Dx() - i.height = resized.Bounds().Dy() + resized := resize.Resize(uint(width), uint(height), i.image, resize.Lanczos3) + i.image = Image2RGBA(resized) + i.width = resized.Bounds().Dx() + i.height = resized.Bounds().Dy() - return i + return i } // Crop Cut out a rectangular part of the current image with given width and height. func (i *Image) Crop(x, y, width, height int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if width == i.width || height == i.height || width == 0 || height == 0 || x > i.width || y > i.height { - return i - } + if width == i.width || height == i.height || width == 0 || height == 0 || x > i.width || y > i.height { + return i + } - x1 := x + width - y1 := y + height - clipped := i.image.SubImage(image.Rect(x, y, x1, y1)) - i.image = Image2RGBA(clipped) - i.width = width - i.height = height + x1 := x + width + y1 := y + height + clipped := i.image.SubImage(image.Rect(x, y, x1, y1)) + i.image = Image2RGBA(clipped) + i.width = width + i.height = height - return i + return i } // Rotate rotates the image clockwise by the specified angle. func (i *Image) Rotate(angle int) *Image { - if i.Error != nil { - return i - } - angle %= 360 - if angle == 0 { - return i - } + if i.Error != nil { + return i + } + angle %= 360 + if angle == 0 { + return i + } - // angle to radian - radian := float64(angle) * math.Pi / 180.0 - cos := math.Cos(radian) - sin := math.Sin(radian) + // angle to radian + radian := float64(angle) * math.Pi / 180.0 + cos := math.Cos(radian) + sin := math.Sin(radian) - w := float64(i.width) - h := float64(i.height) + w := float64(i.width) + h := float64(i.height) - // calculate the new image size - W := int(math.Max(math.Abs(w*cos-h*sin), math.Abs(w*cos+h*sin))) - H := int(math.Max(math.Abs(w*sin-h*cos), math.Abs(w*sin+h*cos))) + // calculate the new image size + W := int(math.Max(math.Abs(w*cos-h*sin), math.Abs(w*cos+h*sin))) + H := int(math.Max(math.Abs(w*sin-h*cos), math.Abs(w*sin+h*cos))) - // rotate the image - dst := image.NewRGBA(image.Rect(0, 0, W, H)) - err := graphics.Rotate(dst, i.image, &graphics.RotateOptions{Angle: radian}) - if err != nil { - i.addError(err) - return i - } + // rotate the image + dst := image.NewRGBA(image.Rect(0, 0, W, H)) + err := graphics.Rotate(dst, i.image, &graphics.RotateOptions{Angle: radian}) + if err != nil { + i.addError(err) + return i + } - i.image = dst - i.width = W - i.height = H - return i + i.image = dst + i.width = W + i.height = H + return i } // Grayscale converts the image to grayscale. func (i *Image) Grayscale() *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - for x := 0; x < i.width; x++ { - for y := 0; y < i.height; y++ { - rgbColor := i.image.At(x, y) - grayColor := color.GrayModel.Convert(rgbColor) - i.image.Set(x, y, grayColor) - } - } + for x := 0; x < i.width; x++ { + for y := 0; y < i.height; y++ { + rgbColor := i.image.At(x, y) + grayColor := color.GrayModel.Convert(rgbColor) + i.image.Set(x, y, grayColor) + } + } - i.isGrayscale = true + i.isGrayscale = true - return i + return i } // Flip mirror the image vertically or horizontally. func (i *Image) Flip(flipType FlipType) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if flipType == Horizontal { - i.flipHorizontally() - } else if flipType == Vertical { - i.flipVertically() - } + if flipType == Horizontal { + i.flipHorizontally() + } else if flipType == Vertical { + i.flipVertically() + } - return i + return i } // flipHorizontally flips the image horizontally. func (i *Image) flipHorizontally() { - for x := 0; x < i.width/2; x++ { - for y := 0; y < i.height; y++ { - pixel := i.image.At(x, y) - i.image.Set(x, y, i.image.At(i.width-x-1, y)) - i.image.Set(i.width-x-1, y, pixel) - } - } + for x := 0; x < i.width/2; x++ { + for y := 0; y < i.height; y++ { + pixel := i.image.At(x, y) + i.image.Set(x, y, i.image.At(i.width-x-1, y)) + i.image.Set(i.width-x-1, y, pixel) + } + } } // flipVertically flips the image vertically. func (i *Image) flipVertically() { - for y := 0; y < i.height/2; y++ { - for x := 0; x < i.width; x++ { - pixel := i.image.At(x, y) - i.image.Set(x, y, i.image.At(x, i.height-y-1)) - i.image.Set(x, i.height-y-1, pixel) - } - } + for y := 0; y < i.height/2; y++ { + for x := 0; x < i.width; x++ { + pixel := i.image.At(x, y) + i.image.Set(x, y, i.image.At(x, i.height-y-1)) + i.image.Set(x, i.height-y-1, pixel) + } + } } // Text write a text string to the image at given (x, y) coordinate. func (i *Image) Text(label string, x, y int, fontPath string, fontColor color.Color, fontSize float64, dpi float64) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - // Load font - fontBytes, err := ioutil.ReadFile(fontPath) - if err != nil { - i.addError(err) - return i - } - myFont, err := freetype.ParseFont(fontBytes) - if err != nil { - i.addError(err) - return i - } + // Load font + fontBytes, err := ioutil.ReadFile(fontPath) + if err != nil { + i.addError(err) + return i + } + myFont, err := freetype.ParseFont(fontBytes) + if err != nil { + i.addError(err) + return i + } - c := freetype.NewContext() - c.SetDPI(dpi) - c.SetFont(myFont) - c.SetFontSize(fontSize) - c.SetClip(i.Bounds()) - c.SetDst(i.image) - uni := image.NewUniform(fontColor) - c.SetSrc(uni) - c.SetHinting(font.HintingNone) + c := freetype.NewContext() + c.SetDPI(dpi) + c.SetFont(myFont) + c.SetFontSize(fontSize) + c.SetClip(i.Bounds()) + c.SetDst(i.image) + uni := image.NewUniform(fontColor) + c.SetSrc(uni) + c.SetHinting(font.HintingNone) - // Draw text - pt := freetype.Pt(x, y+int(c.PointToFixed(fontSize)>>6)) - if _, err := c.DrawString(label, pt); err != nil { - i.addError(err) - return i - } + // Draw text + pt := freetype.Pt(x, y+int(c.PointToFixed(fontSize)>>6)) + if _, err := c.DrawString(label, pt); err != nil { + i.addError(err) + return i + } - return i + return i } // Thumbnail returns a thumbnail of the image with given width and height. func (i *Image) Thumbnail(width, height int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if width >= i.width || height >= i.height || width == 0 || height == 0 { - return i - } + if width >= i.width || height >= i.height || width == 0 || height == 0 { + return i + } - dst := image.NewRGBA(image.Rect(0, 0, width, height)) - err := graphics.Thumbnail(dst, i.image) - if err != nil { - i.addError(err) - return i - } + dst := image.NewRGBA(image.Rect(0, 0, width, height)) + err := graphics.Thumbnail(dst, i.image) + if err != nil { + i.addError(err) + return i + } - i.image = dst - i.width = width - i.height = height - return i + i.image = dst + i.width = width + i.height = height + return i } // HttpHandler responds the image as an HTTP handler. func (i Image) HttpHandler(w http.ResponseWriter, r *http.Request) { - if i.Error != nil { - log.Println(i.Error) - return - } + if i.Error != nil { + log.Println(i.Error) + return + } - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "image/png") - err := png.Encode(w, i.image) + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "image/png") + err := png.Encode(w, i.image) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + } } diff --git a/loader.go b/loader.go index d0904ff..8d4a344 100644 --- a/loader.go +++ b/loader.go @@ -1,12 +1,12 @@ package imgo import ( - "bytes" - "image" - "image/color" - "io/ioutil" - "net/http" - "os" + "bytes" + "image" + "image/color" + "io/ioutil" + "net/http" + "os" ) type ImageManager struct { @@ -15,201 +15,201 @@ type ImageManager struct { // Load an image from source. // source can be a file path, a URL, a base64 encoded string, an *os.File, an image.Image or a byte slice. func Load(source interface{}) *Image { - switch source.(type) { - case string: - return loadFromString(source.(string)) - case *os.File: - return LoadFromFile(source.(*os.File)) - case image.Image: - return LoadFromImage(source.(image.Image)) - case []byte: - return loadFromString(string(source.([]byte))) - case *Image: - return LoadFromImgo(source.(*Image)) - default: - i := &Image{} - i.addError(ErrSourceNotSupport) - return i - } + switch source.(type) { + case string: + return loadFromString(source.(string)) + case *os.File: + return LoadFromFile(source.(*os.File)) + case image.Image: + return LoadFromImage(source.(image.Image)) + case []byte: + return loadFromString(string(source.([]byte))) + case *Image: + return LoadFromImgo(source.(*Image)) + default: + i := &Image{} + i.addError(ErrSourceNotSupport) + return i + } } // loadFromString loads an image when the source is a string. func loadFromString(source string) (i *Image) { - i = &Image{} + i = &Image{} - if len(source) == 0 { - i.addError(ErrSourceStringIsEmpty) - return - } + if len(source) == 0 { + i.addError(ErrSourceStringIsEmpty) + return + } - if len(source) > 4 && source[:4] == "http" { - return LoadFromUrl(source) - } else if len(source) > 10 && source[:10] == "data:image" { - return LoadFromBase64(source) - } else { - return LoadFromPath(source) - } + if len(source) > 4 && source[:4] == "http" { + return LoadFromUrl(source) + } else if len(source) > 10 && source[:10] == "data:image" { + return LoadFromBase64(source) + } else { + return LoadFromPath(source) + } } // LoadFromUrl loads an image when the source is an url. func LoadFromUrl(url string) (i *Image) { - i = &Image{} + i = &Image{} - // Get the image response from the url. - resp, err := http.Get(url) - if err != nil { - i.addError(err) - return - } - defer resp.Body.Close() + // Get the image response from the url. + resp, err := http.Get(url) + if err != nil { + i.addError(err) + return + } + defer resp.Body.Close() - // Read the image data from the response. - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - panic(err) - } + // Read the image data from the response. + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } - // Get the extension, mimetype and corresponding decoder function of the image. - ext, mime, decoder, err := GetImageType(bodyBytes[:8]) - if err != nil { - i.addError(err) - return - } + // Get the extension, mimetype and corresponding decoder function of the image. + ext, mime, decoder, err := GetImageType(bodyBytes[:8]) + if err != nil { + i.addError(err) + return + } - // Decode the image. - file := bytes.NewReader(bodyBytes) - img, err := decoder(file) - if err != nil { - i.addError(ErrSourceNotSupport) - return - } + // Decode the image. + file := bytes.NewReader(bodyBytes) + img, err := decoder(file) + if err != nil { + i.addError(ErrSourceNotSupport) + return + } - return &Image{ - image: Image2RGBA(img), - width: img.Bounds().Dx(), - height: img.Bounds().Dy(), - extension: ext, - mimetype: mime, - } + return &Image{ + image: Image2RGBA(img), + width: img.Bounds().Dx(), + height: img.Bounds().Dy(), + extension: ext, + mimetype: mime, + } } // LoadFromPath loads an image from a path. func LoadFromPath(path string) (i *Image) { - i = &Image{} + i = &Image{} - file, err := os.Open(path) - if err != nil { - i.addError(err) - return - } - defer file.Close() + file, err := os.Open(path) + if err != nil { + i.addError(err) + return + } + defer file.Close() - return LoadFromFile(file) + return LoadFromFile(file) } // LoadFromFile loads an image from a file. func LoadFromFile(file *os.File) (i *Image) { - i = &Image{} + i = &Image{} - // Read the first 8 bytes of the image. - buf := make([]byte, 8) - _, err := file.Read(buf) - if err != nil { - i.addError(err) - return - } + // Read the first 8 bytes of the image. + buf := make([]byte, 8) + _, err := file.Read(buf) + if err != nil { + i.addError(err) + return + } - // After reading the first 8 bytes, we seek back to the beginning of the file. - _, err = file.Seek(0, 0) - if err != nil { - i.addError(err) - return - } + // After reading the first 8 bytes, we seek back to the beginning of the file. + _, err = file.Seek(0, 0) + if err != nil { + i.addError(err) + return + } - // Get the extension, mimetype and corresponding decoder function of the image. - ext, mime, decoder, err := GetImageType(buf) - if err != nil { - i.addError(err) - return - } + // Get the extension, mimetype and corresponding decoder function of the image. + ext, mime, decoder, err := GetImageType(buf) + if err != nil { + i.addError(err) + return + } - // Decode the image. - img, err := decoder(file) - if err != nil { - i.addError(err) - return - } + // Decode the image. + img, err := decoder(file) + if err != nil { + i.addError(err) + return + } - // Set the image properties. - stat, _ := file.Stat() + // Set the image properties. + stat, _ := file.Stat() - return &Image{ - image: Image2RGBA(img), - width: img.Bounds().Dx(), - height: img.Bounds().Dy(), - extension: ext, - mimetype: mime, - filesize: stat.Size(), - } + return &Image{ + image: Image2RGBA(img), + width: img.Bounds().Dx(), + height: img.Bounds().Dy(), + extension: ext, + mimetype: mime, + filesize: stat.Size(), + } } // LoadFromImage loads an image from an instance of image.Image. func LoadFromImage(img image.Image) (i *Image) { - i = &Image{} + i = &Image{} - if img == nil { - i.addError(ErrSourceImageIsNil) - return - } + if img == nil { + i.addError(ErrSourceImageIsNil) + return + } - var formatName string - switch img.(type) { - case *image.NRGBA: // png - formatName = "png" - case *image.RGBA: // bmp, tiff - formatName = "png" - case *image.YCbCr: // jpeg, webp - formatName = "jpg" - default: - i.addError(ErrSourceImageNotSupport) - return - } + var formatName string + switch img.(type) { + case *image.NRGBA: // png + formatName = "png" + case *image.RGBA: // bmp, tiff + formatName = "png" + case *image.YCbCr: // jpeg, webp + formatName = "jpg" + default: + i.addError(ErrSourceImageNotSupport) + return + } - return &Image{ - image: Image2RGBA(img), - width: img.Bounds().Dx(), - height: img.Bounds().Dy(), - extension: formatName, - mimetype: "image/" + formatName, - } + return &Image{ + image: Image2RGBA(img), + width: img.Bounds().Dx(), + height: img.Bounds().Dy(), + extension: formatName, + mimetype: "image/" + formatName, + } } // LoadFromImgo loads an image from an instance of Image. func LoadFromImgo(i *Image) *Image { - return i + return i } // Canvas create a new empty image. func Canvas(width, height int, fillColor ...color.Color) *Image { - var c color.Color - if len(fillColor) == 0 { - c = color.Transparent - } else { - c = fillColor[0] - } + var c color.Color + if len(fillColor) == 0 { + c = color.Transparent + } else { + c = fillColor[0] + } - img := image.NewRGBA(image.Rect(0, 0, width, height)) - for x := 0; x < img.Bounds().Dx(); x++ { - for y := 0; y < img.Bounds().Dy(); y++ { - img.Set(x, y, c) - } - } + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for x := 0; x < img.Bounds().Dx(); x++ { + for y := 0; y < img.Bounds().Dy(); y++ { + img.Set(x, y, c) + } + } - return &Image{ - image: img, - width: img.Bounds().Dx(), - height: img.Bounds().Dy(), - extension: "png", - mimetype: "image/png", - } + return &Image{ + image: img, + width: img.Bounds().Dx(), + height: img.Bounds().Dy(), + extension: "png", + mimetype: "image/png", + } } diff --git a/pixelate.go b/pixelate.go index 52ee430..affb20e 100644 --- a/pixelate.go +++ b/pixelate.go @@ -1,125 +1,125 @@ package imgo import ( - "image" - "image/color" - "image/draw" - "math" + "image" + "image/color" + "image/draw" + "math" ) // calculateMeanAverageColorWithRect returns the mean average color of the image in given rectangle. func (i Image) calculateMeanAverageColorWithRect(rect image.Rectangle, useSquaredAverage bool) (red, green, blue uint8) { - var redSum float64 - var greenSum float64 - var blueSum float64 + var redSum float64 + var greenSum float64 + var blueSum float64 - for x := rect.Min.X; x <= rect.Max.X; x++ { - for y := rect.Min.Y; y <= rect.Max.Y; y++ { - pixel := i.image.At(x, y) - col := color.RGBAModel.Convert(pixel).(color.RGBA) + for x := rect.Min.X; x <= rect.Max.X; x++ { + for y := rect.Min.Y; y <= rect.Max.Y; y++ { + pixel := i.image.At(x, y) + col := color.RGBAModel.Convert(pixel).(color.RGBA) - if useSquaredAverage { - redSum += float64(col.R) * float64(col.R) - greenSum += float64(col.G) * float64(col.G) - blueSum += float64(col.B) * float64(col.B) - } else { - redSum += float64(col.R) - greenSum += float64(col.G) - blueSum += float64(col.B) - } - } - } + if useSquaredAverage { + redSum += float64(col.R) * float64(col.R) + greenSum += float64(col.G) * float64(col.G) + blueSum += float64(col.B) * float64(col.B) + } else { + redSum += float64(col.R) + greenSum += float64(col.G) + blueSum += float64(col.B) + } + } + } - rectArea := float64((rect.Dx() + 1) * (rect.Dy() + 1)) + rectArea := float64((rect.Dx() + 1) * (rect.Dy() + 1)) - if useSquaredAverage { - red = uint8(math.Round(math.Sqrt(redSum / rectArea))) - green = uint8(math.Round(math.Sqrt(greenSum / rectArea))) - blue = uint8(math.Round(math.Sqrt(blueSum / rectArea))) - } else { - red = uint8(math.Round(redSum / rectArea)) - green = uint8(math.Round(greenSum / rectArea)) - blue = uint8(math.Round(blueSum / rectArea)) - } + if useSquaredAverage { + red = uint8(math.Round(math.Sqrt(redSum / rectArea))) + green = uint8(math.Round(math.Sqrt(greenSum / rectArea))) + blue = uint8(math.Round(math.Sqrt(blueSum / rectArea))) + } else { + red = uint8(math.Round(redSum / rectArea)) + green = uint8(math.Round(greenSum / rectArea)) + blue = uint8(math.Round(blueSum / rectArea)) + } - return + return } // pixelate apply pixelation filter to the image in given rectangle. func (i *Image) pixelate(size int, rectangle image.Rectangle) *Image { - bg := image.NewRGBA(i.image.Bounds()) - draw.Draw(bg, bg.Bounds(), i.image, i.image.Bounds().Min, draw.Over) + bg := image.NewRGBA(i.image.Bounds()) + draw.Draw(bg, bg.Bounds(), i.image, i.image.Bounds().Min, draw.Over) - for x := rectangle.Min.X; x < rectangle.Max.X; x += size { - for y := rectangle.Min.Y; y < rectangle.Max.Y; y += size { - rect := image.Rect(x, y, x+size, y+size) + for x := rectangle.Min.X; x < rectangle.Max.X; x += size { + for y := rectangle.Min.Y; y < rectangle.Max.Y; y += size { + rect := image.Rect(x, y, x+size, y+size) - if rect.Max.X > i.width { - rect.Max.X = i.width - } - if rect.Max.Y > i.height { - rect.Max.Y = i.height - } + if rect.Max.X > i.width { + rect.Max.X = i.width + } + if rect.Max.Y > i.height { + rect.Max.Y = i.height + } - r, g, b := i.calculateMeanAverageColorWithRect(rect, true) - col := color.RGBA{R: r, G: g, B: b, A: 255} + r, g, b := i.calculateMeanAverageColorWithRect(rect, true) + col := color.RGBA{R: r, G: g, B: b, A: 255} - for x2 := rect.Min.X; x2 < rect.Max.X; x2++ { - for y2 := rect.Min.Y; y2 < rect.Max.Y; y2++ { - bg.Set(x2, y2, col) - } - } - } - } + for x2 := rect.Min.X; x2 < rect.Max.X; x2++ { + for y2 := rect.Min.Y; y2 < rect.Max.Y; y2++ { + bg.Set(x2, y2, col) + } + } + } + } - i.image = bg - return i + i.image = bg + return i } // Pixelate apply pixelation filter to the image. // size is the size of the pixel. func (i *Image) Pixelate(size int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if size <= 1 { - return i - } + if size <= 1 { + return i + } - if i.width > i.height { - if size > i.width { - size = i.width - } - } else { - if size > i.height { - size = i.height - } - } + if i.width > i.height { + if size > i.width { + size = i.width + } + } else { + if size > i.height { + size = i.height + } + } - return i.pixelate(size, i.image.Bounds()) + return i.pixelate(size, i.image.Bounds()) } // Mosaic apply mosaic filter to the image in given rectangle that // (x1, y1) and (x2, y2) are the top-left and bottom-right coordinates of the rectangle. // size is the size of the pixel. func (i *Image) Mosaic(size, x1, y1, x2, y2 int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if x1 < 0 { - x1 = 0 - } - if y1 < 0 { - y1 = 0 - } - if x2 > i.width { - x2 = i.width - } - if y2 > i.height { - y2 = i.height - } + if x1 < 0 { + x1 = 0 + } + if y1 < 0 { + y1 = 0 + } + if x2 > i.width { + x2 = i.width + } + if y2 > i.height { + y2 = i.height + } - return i.pixelate(size, image.Rect(x1, y1, x2, y2)) + return i.pixelate(size, image.Rect(x1, y1, x2, y2)) } diff --git a/radius.go b/radius.go index 505539c..96f5c62 100644 --- a/radius.go +++ b/radius.go @@ -1,68 +1,68 @@ package imgo import ( - "image" - "image/color" - "image/draw" + "image" + "image/color" + "image/draw" ) // BorderRadius draws rounded corners on the image with given radius. func (i *Image) BorderRadius(radius float64) *Image { - if radius > float64(i.width/2) || radius > float64(i.height/2) { - return i - } + if radius > float64(i.width/2) || radius > float64(i.height/2) { + return i + } - c := Radius{p: image.Point{X: i.width, Y: i.height}, r: int(radius)} - dst := image.NewRGBA(i.image.Bounds()) - draw.DrawMask(dst, dst.Bounds(), i.image, image.Point{}, &c, image.Point{}, draw.Over) + c := Radius{p: image.Point{X: i.width, Y: i.height}, r: int(radius)} + dst := image.NewRGBA(i.image.Bounds()) + draw.DrawMask(dst, dst.Bounds(), i.image, image.Point{}, &c, image.Point{}, draw.Over) - i.image = dst - return i + i.image = dst + return i } type Radius struct { - p image.Point // the right-bottom Point of the image - r int // radius + p image.Point // the right-bottom Point of the image + r int // radius } func (c *Radius) ColorModel() color.Model { - return color.AlphaModel + return color.AlphaModel } func (c *Radius) Bounds() image.Rectangle { - return image.Rect(0, 0, c.p.X, c.p.Y) + return image.Rect(0, 0, c.p.X, c.p.Y) } func (c *Radius) At(x, y int) color.Color { - var xx, yy, rr float64 - var inArea bool + var xx, yy, rr float64 + var inArea bool - // left up - if x <= c.r && y <= c.r { - xx, yy, rr = float64(c.r-x)+0.5, float64(y-c.r)+0.5, float64(c.r) - inArea = true - } + // left up + if x <= c.r && y <= c.r { + xx, yy, rr = float64(c.r-x)+0.5, float64(y-c.r)+0.5, float64(c.r) + inArea = true + } - // right up - if x >= (c.p.X-c.r) && y <= c.r { - xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-c.r)+0.5, float64(c.r) - inArea = true - } + // right up + if x >= (c.p.X-c.r) && y <= c.r { + xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-c.r)+0.5, float64(c.r) + inArea = true + } - // left bottom - if x <= c.r && y >= (c.p.Y-c.r) { - xx, yy, rr = float64(c.r-x)+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r) - inArea = true - } + // left bottom + if x <= c.r && y >= (c.p.Y-c.r) { + xx, yy, rr = float64(c.r-x)+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r) + inArea = true + } - // right bottom - if x >= (c.p.X-c.r) && y >= (c.p.Y-c.r) { - xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r) - inArea = true - } + // right bottom + if x >= (c.p.X-c.r) && y >= (c.p.Y-c.r) { + xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r) + inArea = true + } - if inArea && xx*xx+yy*yy >= rr*rr { - return color.Alpha{} - } - return color.Alpha{A: 255} + if inArea && xx*xx+yy*yy >= rr*rr { + return color.Alpha{} + } + return color.Alpha{A: 255} } diff --git a/shape.go b/shape.go index 8f4b91b..df3299c 100644 --- a/shape.go +++ b/shape.go @@ -1,166 +1,166 @@ package imgo import ( - "image" - "image/color" + "image" + "image/color" ) // Pixel draws a pixel at given (x, y) coordinate with given color. func (i *Image) Pixel(x, y int, c color.Color) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - i.image.Set(x, y, c) + i.image.Set(x, y, c) - return i + return i } // Line draws a line from (x1, y1) to (x2, y2) with given color. // TODO: width is not working yet. func (i *Image) Line(x1, y1, x2, y2 int, c color.Color, width ...int) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - w := 1 - if len(width) != 0 { - w = width[0] - } - if w <= 0 { - return i - } + w := 1 + if len(width) != 0 { + w = width[0] + } + if w <= 0 { + return i + } - bg := i.image + bg := i.image - dx := x2 - x1 - dy := y2 - y1 - if dx == 0 && dy == 0 { // Line is a point. - bg.Set(x1, y1, c) - i.image = bg - return i - } else if dx == 0 { // vertical line - if y1 > y2 { - y1, y2 = y2, y1 - } - for y := y1; y <= y2; y++ { - bg.Set(x1, y, c) - } - } else if dy == 0 { // horizontal line - if x1 > x2 { - x1, x2 = x2, x1 - } - for x := x1; x <= x2; x++ { - bg.Set(x, y1, c) - } - } else { // diagonal line - k := float64(dy) / float64(dx) - if x1 > x2 { - x1, x2 = x2, x1 - y1, y2 = y2, y1 - } - if -1 < k && k < 1 { - for x := x1; x <= x2; x++ { - y := int(float64(y1) + float64(x-x1)*k) - bg.Set(x, y, c) - } - } else { - ySmaller, yBigger := y1, y2 - if y1 > y2 { - ySmaller, yBigger = y2, y1 - } - for y := ySmaller; y <= yBigger; y++ { - x := int(float64(x1) + float64(y-y1)/k) - bg.Set(x, y, c) - } - } - } + dx := x2 - x1 + dy := y2 - y1 + if dx == 0 && dy == 0 { // Line is a point. + bg.Set(x1, y1, c) + i.image = bg + return i + } else if dx == 0 { // vertical line + if y1 > y2 { + y1, y2 = y2, y1 + } + for y := y1; y <= y2; y++ { + bg.Set(x1, y, c) + } + } else if dy == 0 { // horizontal line + if x1 > x2 { + x1, x2 = x2, x1 + } + for x := x1; x <= x2; x++ { + bg.Set(x, y1, c) + } + } else { // diagonal line + k := float64(dy) / float64(dx) + if x1 > x2 { + x1, x2 = x2, x1 + y1, y2 = y2, y1 + } + if -1 < k && k < 1 { + for x := x1; x <= x2; x++ { + y := int(float64(y1) + float64(x-x1)*k) + bg.Set(x, y, c) + } + } else { + ySmaller, yBigger := y1, y2 + if y1 > y2 { + ySmaller, yBigger = y2, y1 + } + for y := ySmaller; y <= yBigger; y++ { + x := int(float64(x1) + float64(y-y1)/k) + bg.Set(x, y, c) + } + } + } - return i + return i } // Circle draws a circle at given center coordinate (x, y) with given radius and color. func (i *Image) Circle(x, y, radius int, c color.Color) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if radius <= 0 || radius >= i.width || radius >= i.height { - return i - } + if radius <= 0 || radius >= i.width || radius >= i.height { + return i + } - x1 := x - radius - y1 := y - radius - x2 := x + radius - y2 := y + radius + x1 := x - radius + y1 := y - radius + x2 := x + radius + y2 := y + radius - bg := i.image + bg := i.image - for x3 := x1; x3 < x2; x3++ { - for y3 := y1; y3 < y2; y3++ { - if (x3-x)*(x3-x)+(y3-y)*(y3-y) <= radius*radius { - bg.Set(x3, y3, c) - } - } - } + for x3 := x1; x3 < x2; x3++ { + for y3 := y1; y3 < y2; y3++ { + if (x3-x)*(x3-x)+(y3-y)*(y3-y) <= radius*radius { + bg.Set(x3, y3, c) + } + } + } - return i + return i } // Rectangle draws a rectangle at given coordinate (x, y) with given width and height and color. func (i *Image) Rectangle(x, y, width, height int, c color.Color) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if width <= 0 || height <= 0 { - return i - } + if width <= 0 || height <= 0 { + return i + } - x1 := x - y1 := y - x2 := x + width - y2 := y + height + x1 := x + y1 := y + x2 := x + width + y2 := y + height - bg := i.image + bg := i.image - for x3 := x1; x3 < x2; x3++ { - for y3 := y1; y3 < y2; y3++ { - if image.Pt(x3, y3).In(i.image.Bounds()) { - bg.Set(x3, y3, c) - } - } - } + for x3 := x1; x3 < x2; x3++ { + for y3 := y1; y3 < y2; y3++ { + if image.Pt(x3, y3).In(i.image.Bounds()) { + bg.Set(x3, y3, c) + } + } + } - return i + return i } // Ellipse draws an ellipse at given center coordinate (x, y) with given width and height and color. func (i *Image) Ellipse(x, y, width, height int, c color.Color) *Image { - if i.Error != nil { - return i - } + if i.Error != nil { + return i + } - if width <= 0 || height <= 0 { - return i - } + if width <= 0 || height <= 0 { + return i + } - a := float64(width) / 2 - b := float64(height) / 2 - x1 := x - int(a) - y1 := y - int(b) - x2 := x + int(a) - y2 := y + int(b) + a := float64(width) / 2 + b := float64(height) / 2 + x1 := x - int(a) + y1 := y - int(b) + x2 := x + int(a) + y2 := y + int(b) - bg := i.image + bg := i.image - for x3 := x1; x3 <= x2; x3++ { - for y3 := y1; y3 <= y2; y3++ { - if (float64(x3)-float64(x))*(float64(x3)-float64(x))/a/a+(float64(y3)-float64(y))*(float64(y3)-float64(y))/b/b <= 1.0 { - bg.Set(x3, y3, c) - } - } - } + for x3 := x1; x3 <= x2; x3++ { + for y3 := y1; y3 <= y2; y3++ { + if (float64(x3)-float64(x))*(float64(x3)-float64(x))/a/a+(float64(y3)-float64(y))*(float64(y3)-float64(y))/b/b <= 1.0 { + bg.Set(x3, y3, c) + } + } + } - return i + return i } diff --git a/util.go b/util.go index b30f03c..f1e6b83 100644 --- a/util.go +++ b/util.go @@ -1,82 +1,82 @@ package imgo import ( - "fmt" - "golang.org/x/image/bmp" - "golang.org/x/image/tiff" - "golang.org/x/image/webp" - "image" - "image/color" - "image/draw" - "image/jpeg" - "image/png" - "io" + "fmt" + "golang.org/x/image/bmp" + "golang.org/x/image/tiff" + "golang.org/x/image/webp" + "image" + "image/color" + "image/draw" + "image/jpeg" + "image/png" + "io" ) // GetImageType returns the extension, mimetype and corresponding decoder function of the image. // It judges the image by its first few bytes called magic number. func GetImageType(bytes []byte) (ext string, mimetype string, decoder func(r io.Reader) (image.Image, error), err error) { - if len(bytes) < 2 { - err = ErrSourceImageNotSupport - return - } + if len(bytes) < 2 { + err = ErrSourceImageNotSupport + return + } - if bytes[0] == 0xFF && bytes[1] == 0xD8 { - ext = "jpg" - mimetype = "image/jpeg" - decoder = jpeg.Decode - } + if bytes[0] == 0xFF && bytes[1] == 0xD8 { + ext = "jpg" + mimetype = "image/jpeg" + decoder = jpeg.Decode + } - if len(bytes) >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 { - ext = "png" - mimetype = "image/png" - decoder = png.Decode - } + if len(bytes) >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 { + ext = "png" + mimetype = "image/png" + decoder = png.Decode + } - if bytes[0] == 0x42 && bytes[1] == 0x4D { - ext = "bmp" - mimetype = "image/x-ms-bmp" - decoder = bmp.Decode - } + if bytes[0] == 0x42 && bytes[1] == 0x4D { + ext = "bmp" + mimetype = "image/x-ms-bmp" + decoder = bmp.Decode + } - if (bytes[0] == 0x49 && bytes[1] == 0x49) || (bytes[0] == 0x4D && bytes[1] == 0x4D) { - ext = "tiff" - mimetype = "image/tiff" - decoder = tiff.Decode - } + if (bytes[0] == 0x49 && bytes[1] == 0x49) || (bytes[0] == 0x4D && bytes[1] == 0x4D) { + ext = "tiff" + mimetype = "image/tiff" + decoder = tiff.Decode + } - if bytes[0] == 0x52 && bytes[1] == 0x49 { - ext = "webp" - mimetype = "image/webp" - decoder = webp.Decode - } + if bytes[0] == 0x52 && bytes[1] == 0x49 { + ext = "webp" + mimetype = "image/webp" + decoder = webp.Decode + } - /*if bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x38 { - ext = "gif" - mimetype = "image/gif" - } + /*if bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x38 { + ext = "gif" + mimetype = "image/gif" + } - if bytes[0] == 0x00 && bytes[1] == 0x00 && (bytes[2] == 0x01 || bytes[2] == 0x02) && bytes[3] == 0x00 { - ext = "ico" - mimetype = "image/x-icon" - }*/ + if bytes[0] == 0x00 && bytes[1] == 0x00 && (bytes[2] == 0x01 || bytes[2] == 0x02) && bytes[3] == 0x00 { + ext = "ico" + mimetype = "image/x-icon" + }*/ - if ext == "" { - err = ErrSourceImageNotSupport - } + if ext == "" { + err = ErrSourceImageNotSupport + } - return + return } // Color2Hex converts a color.Color to its hex string representation. func Color2Hex(c color.Color) string { - r, g, b, _ := c.RGBA() - return fmt.Sprintf("#%02X%02X%02X", uint8(r>>8), uint8(g>>8), uint8(b>>8)) + r, g, b, _ := c.RGBA() + return fmt.Sprintf("#%02X%02X%02X", uint8(r>>8), uint8(g>>8), uint8(b>>8)) } // Image2RGBA converts an image to RGBA. func Image2RGBA(img image.Image) *image.RGBA { - rgba := image.NewRGBA(img.Bounds()) - draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Over) - return rgba + rgba := image.NewRGBA(img.Bounds()) + draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Over) + return rgba }