Replace space indent with tab

This commit is contained in:
wenyu
2022-06-22 14:07:54 +08:00
parent 6f0bdfe924
commit 6b51b6fcd4
19 changed files with 955 additions and 955 deletions

View File

@@ -1,54 +1,54 @@
package imgo package imgo
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"image/png" "image/png"
"strings" "strings"
) )
// ToBase64 returns the base64 encoded string of the image. // ToBase64 returns the base64 encoded string of the image.
func (i Image) ToBase64() string { func (i Image) ToBase64() string {
buff := bytes.NewBuffer(nil) buff := bytes.NewBuffer(nil)
err := png.Encode(buff, i.image) err := png.Encode(buff, i.image)
if err != nil { if err != nil {
return "" return ""
} }
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buff.Bytes()) return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buff.Bytes())
} }
// LoadFromBase64 loads an image from a base64 encoded string. // LoadFromBase64 loads an image from a base64 encoded string.
func LoadFromBase64(base64Str string) (i *Image) { func LoadFromBase64(base64Str string) (i *Image) {
i = &Image{} i = &Image{}
base64Str = strings.Split(base64Str, ",")[1] base64Str = strings.Split(base64Str, ",")[1]
// Decode the base64 string // Decode the base64 string
decodeString, err := base64.StdEncoding.DecodeString(base64Str) decodeString, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Get the extension, mimetype and corresponding decoder function of the image. // Get the extension, mimetype and corresponding decoder function of the image.
ext, mimetype, decoder, err := GetImageType(decodeString[:8]) ext, mimetype, decoder, err := GetImageType(decodeString[:8])
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Decode the image. // Decode the image.
buff := bytes.NewBuffer(decodeString) buff := bytes.NewBuffer(decodeString)
img, err := decoder(buff) img, err := decoder(buff)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
return &Image{ return &Image{
image: Image2RGBA(img), image: Image2RGBA(img),
width: img.Bounds().Dx(), width: img.Bounds().Dx(),
height: img.Bounds().Dy(), height: img.Bounds().Dy(),
extension: ext, extension: ext,
mimetype: mimetype, mimetype: mimetype,
} }
} }

214
blur.go
View File

@@ -1,143 +1,143 @@
package imgo package imgo
import ( import (
"github.com/BurntSushi/graphics-go/graphics" "github.com/BurntSushi/graphics-go/graphics"
"image" "image"
"image/color" "image/color"
"sync" "sync"
) )
// GaussianBlur returns a blurred image. // GaussianBlur returns a blurred image.
// ksize is Gaussian kernel size // ksize is Gaussian kernel size
// sigma is Gaussian kernel standard deviation. // sigma is Gaussian kernel standard deviation.
func (i *Image) GaussianBlur(ksize int, sigma float64) *Image { func (i *Image) GaussianBlur(ksize int, sigma float64) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
dst := image.NewRGBA(i.image.Bounds()) dst := image.NewRGBA(i.image.Bounds())
err := graphics.Blur(dst, i.image, &graphics.BlurOptions{ err := graphics.Blur(dst, i.image, &graphics.BlurOptions{
StdDev: sigma, StdDev: sigma,
Size: ksize, Size: ksize,
}) })
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return i return i
} }
i.image = dst i.image = dst
i.width = dst.Bounds().Dx() i.width = dst.Bounds().Dx()
i.height = dst.Bounds().Dy() i.height = dst.Bounds().Dy()
return i return i
} }
// normalizeKernel normalizes a kernel. // normalizeKernel normalizes a kernel.
func (i Image) normalizeKernel(kernel [][]float64) { func (i Image) normalizeKernel(kernel [][]float64) {
var sum float64 var sum float64
for _, row := range kernel { for _, row := range kernel {
for _, value := range row { for _, value := range row {
sum += value sum += value
} }
} }
for i, row := range kernel { for i, row := range kernel {
for j, value := range row { for j, value := range row {
kernel[i][j] = value / sum kernel[i][j] = value / sum
} }
} }
} }
// Blur returns a blurred image. // Blur returns a blurred image.
// ksize is filter kernel size, it must be a odd number. // ksize is filter kernel size, it must be a odd number.
func (i *Image) Blur(ksize int) *Image { func (i *Image) Blur(ksize int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if ksize < 2 { if ksize < 2 {
return i return i
} }
if ksize%2 == 0 { if ksize%2 == 0 {
ksize++ ksize++
} }
kernel := make([][]float64, ksize) kernel := make([][]float64, ksize)
for p := range kernel { for p := range kernel {
row := make([]float64, ksize) row := make([]float64, ksize)
for q := 0; q < ksize; q++ { for q := 0; q < ksize; q++ {
row[q] = 1 row[q] = 1
} }
kernel[p] = row kernel[p] = row
} }
i.imageFilterFast(kernel) i.imageFilterFast(kernel)
return i return i
} }
// imageFilterFast returns a filtered image with Goroutine. // imageFilterFast returns a filtered image with Goroutine.
func (i *Image) imageFilterFast(kernel [][]float64) *Image { func (i *Image) imageFilterFast(kernel [][]float64) *Image {
i.normalizeKernel(kernel) i.normalizeKernel(kernel)
dst := image.NewRGBA(i.image.Bounds()) dst := image.NewRGBA(i.image.Bounds())
kernelSize := len(kernel) kernelSize := len(kernel)
radius := (kernelSize - 1) / 2 radius := (kernelSize - 1) / 2
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
convolution := func(x, y int, wg *sync.WaitGroup) { convolution := func(x, y int, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
var sumR, sumG, sumB, sumA uint16 var sumR, sumG, sumB, sumA uint16
for p := -radius; p <= radius; p++ { for p := -radius; p <= radius; p++ {
for q := -radius; q <= radius; q++ { for q := -radius; q <= radius; q++ {
trueX := x + q trueX := x + q
trueY := y + p trueY := y + p
if image.Pt(trueX, trueY).In(i.Bounds()) { if image.Pt(trueX, trueY).In(i.Bounds()) {
thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA()
sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) sumR += uint16(float64(thisR) * kernel[q+radius][p+radius])
sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) sumG += uint16(float64(thisG) * kernel[q+radius][p+radius])
sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) sumB += uint16(float64(thisB) * kernel[q+radius][p+radius])
sumA += uint16(float64(thisA) * 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}) dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA})
} }
wg.Add(i.width * i.height) wg.Add(i.width * i.height)
for y := 0; y < i.height; y++ { for y := 0; y < i.height; y++ {
for x := 0; x < i.width; x++ { for x := 0; x < i.width; x++ {
go convolution(x, y, &wg) go convolution(x, y, &wg)
} }
} }
wg.Wait() wg.Wait()
i.image = dst i.image = dst
return i return i
} }
// imageFilter returns a filtered image. // imageFilter returns a filtered image.
func (i *Image) imageFilter(kernel [][]float64) *Image { func (i *Image) imageFilter(kernel [][]float64) *Image {
i.normalizeKernel(kernel) i.normalizeKernel(kernel)
dst := image.NewNRGBA64(i.image.Bounds()) dst := image.NewNRGBA64(i.image.Bounds())
kernelSize := len(kernel) kernelSize := len(kernel)
radius := (kernelSize - 1) / 2 radius := (kernelSize - 1) / 2
for y := 0; y < i.height; y++ { for y := 0; y < i.height; y++ {
for x := 0; x < i.width; x++ { for x := 0; x < i.width; x++ {
var sumR, sumG, sumB, sumA uint16 var sumR, sumG, sumB, sumA uint16
for p := -radius; p <= radius; p++ { for p := -radius; p <= radius; p++ {
for q := -radius; q <= radius; q++ { for q := -radius; q <= radius; q++ {
trueX := x + q trueX := x + q
trueY := y + p trueY := y + p
if image.Pt(trueX, trueY).In(i.Bounds()) { if image.Pt(trueX, trueY).In(i.Bounds()) {
thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA() thisR, thisG, thisB, thisA := i.image.At(trueX, trueY).RGBA()
sumR += uint16(float64(thisR) * kernel[q+radius][p+radius]) sumR += uint16(float64(thisR) * kernel[q+radius][p+radius])
sumG += uint16(float64(thisG) * kernel[q+radius][p+radius]) sumG += uint16(float64(thisG) * kernel[q+radius][p+radius])
sumB += uint16(float64(thisB) * kernel[q+radius][p+radius]) sumB += uint16(float64(thisB) * kernel[q+radius][p+radius])
sumA += uint16(float64(thisA) * 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}) dst.SetRGBA64(x, y, color.RGBA64{R: sumR, G: sumG, B: sumB, A: sumA})
} }
} }
i.image = Image2RGBA(dst) i.image = Image2RGBA(dst)
return i return i
} }

View File

@@ -1,36 +1,36 @@
package imgo package imgo
import ( import (
"image/color" "image/color"
"log" "log"
) )
// PickColor returns the color of the pixel at (x, y). // PickColor returns the color of the pixel at (x, y).
func (i Image) PickColor(x, y int) (res color.RGBA) { func (i Image) PickColor(x, y int) (res color.RGBA) {
if i.Error != nil { if i.Error != nil {
return return
} }
if x < 0 || x > i.width || y < 0 || y > i.height { if x < 0 || x > i.width || y < 0 || y > i.height {
return return
} }
pixel := i.image.At(x, y) pixel := i.image.At(x, y)
return color.RGBAModel.Convert(pixel).(color.RGBA) return color.RGBAModel.Convert(pixel).(color.RGBA)
} }
// MainColor returns the main color of the image // MainColor returns the main color of the image
func (i *Image) MainColor() (res color.RGBA) { func (i *Image) MainColor() (res color.RGBA) {
if i.Error != nil { if i.Error != nil {
log.Println(i.Error) log.Println(i.Error)
return return
} }
red, green, blue := i.calculateMeanAverageColorWithRect(i.image.Bounds(), true) red, green, blue := i.calculateMeanAverageColorWithRect(i.image.Bounds(), true)
return color.RGBA{ return color.RGBA{
R: red, R: red,
G: green, G: green,
B: blue, B: blue,
A: 0, A: 0,
} }
} }

View File

@@ -4,6 +4,6 @@ package imgo
type FlipType int type FlipType int
const ( const (
Vertical FlipType = iota Vertical FlipType = iota
Horizontal Horizontal
) )

View File

@@ -3,9 +3,9 @@ package imgo
import "errors" import "errors"
var ( var (
ErrSourceImageIsNil = errors.New("source image is nil") ErrSourceImageIsNil = errors.New("source image is nil")
ErrSourceNotSupport = errors.New("source not support") ErrSourceNotSupport = errors.New("source not support")
ErrSourceStringIsEmpty = errors.New("source string is empty") ErrSourceStringIsEmpty = errors.New("source string is empty")
ErrSourceImageNotSupport = errors.New("source image not support") ErrSourceImageNotSupport = errors.New("source image not support")
ErrSaveImageFormatNotSupport = errors.New("save image format not support") ErrSaveImageFormatNotSupport = errors.New("save image format not support")
) )

View File

@@ -1,10 +1,10 @@
package main package main
import ( import (
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
) )
func main() { func main() {
base64Img := imgo.Load("gopher.png").ToBase64() base64Img := imgo.Load("gopher.png").ToBase64()
imgo.Load(base64Img).Save("out.png") imgo.Load(base64Img).Save("out.png")
} }

View File

@@ -3,7 +3,7 @@ package main
import "github.com/fishtailstudio/imgo" import "github.com/fishtailstudio/imgo"
func main() { func main() {
imgo.Load("gopher.png"). imgo.Load("gopher.png").
Blur(5). Blur(5).
Save("out.png") Save("out.png")
} }

View File

@@ -1,15 +1,15 @@
package main package main
import ( import (
"fmt" "fmt"
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
) )
func main() { func main() {
err := imgo.Load("gopher.jpg").Save("out.png").Error err := imgo.Load("gopher.jpg").Save("out.png").Error
if err != nil { if err != nil {
fmt.Println("error:", err.Error()) fmt.Println("error:", err.Error())
} else { } else {
fmt.Println("success") fmt.Println("success")
} }
} }

View File

@@ -1,11 +1,11 @@
package main package main
import ( import (
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
"net/http" "net/http"
) )
func main() { func main() {
http.HandleFunc("/gopher", imgo.Load("gopher.png").HttpHandler) http.HandleFunc("/gopher", imgo.Load("gopher.png").HttpHandler)
http.ListenAndServe(":8080", nil) http.ListenAndServe(":8080", nil)
} }

View File

@@ -1,12 +1,12 @@
package main package main
import ( import (
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
"image/color" "image/color"
) )
func main() { func main() {
imgo.Canvas(500, 500, color.White). imgo.Canvas(500, 500, color.White).
Insert("gopher.png", 100, 100). Insert("gopher.png", 100, 100).
Save("out.png") Save("out.png")
} }

View File

@@ -3,7 +3,7 @@ package main
import "github.com/fishtailstudio/imgo" import "github.com/fishtailstudio/imgo"
func main() { func main() {
imgo.Load("gopher.png"). imgo.Load("gopher.png").
Mosaic(5, 60, 50, 120, 100). Mosaic(5, 60, 50, 120, 100).
Save("out.png") Save("out.png")
} }

View File

@@ -1,12 +1,12 @@
package main package main
import ( import (
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
"image/color" "image/color"
) )
func main() { func main() {
imgo.Canvas(300, 300, color.White). imgo.Canvas(300, 300, color.White).
BorderRadius(20). BorderRadius(20).
Save("out.png") Save("out.png")
} }

View File

@@ -1,17 +1,17 @@
package main package main
import ( import (
"github.com/fishtailstudio/imgo" "github.com/fishtailstudio/imgo"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
"image/color" "image/color"
) )
func main() { func main() {
imgo.Canvas(500, 500, color.Black). imgo.Canvas(500, 500, color.Black).
Pixel(10, 10, colornames.Blueviolet). Pixel(10, 10, colornames.Blueviolet).
Line(20, 10, 50, 450, colornames.Gold). Line(20, 10, 50, 450, colornames.Gold).
Circle(200, 100, 50, colornames.Aqua). Circle(200, 100, 50, colornames.Aqua).
Rectangle(150, 200, 100, 150, colornames.Darkblue). Rectangle(150, 200, 100, 150, colornames.Darkblue).
Ellipse(400, 200, 150, 50, colornames.Tomato). Ellipse(400, 200, 150, 50, colornames.Tomato).
Save("out.png") Save("out.png")
} }

564
image.go
View File

@@ -1,140 +1,140 @@
package imgo package imgo
import ( import (
"fmt" "fmt"
"github.com/BurntSushi/graphics-go/graphics" "github.com/BurntSushi/graphics-go/graphics"
cliColor "github.com/fatih/color" cliColor "github.com/fatih/color"
"github.com/golang/freetype" "github.com/golang/freetype"
"github.com/nfnt/resize" "github.com/nfnt/resize"
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/tiff" "golang.org/x/image/tiff"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"image/jpeg" "image/jpeg"
"image/png" "image/png"
"io/ioutil" "io/ioutil"
"log" "log"
"math" "math"
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"strings" "strings"
) )
type Image struct { type Image struct {
Error error Error error
image *image.RGBA // the image image *image.RGBA // the image
width int // image width width int // image width
height int // image height height int // image height
extension string // image extension extension string // image extension
mimetype string // image mimetype mimetype string // image mimetype
filesize int64 // image filesize filesize int64 // image filesize
isGrayscale bool // is grayscale image isGrayscale bool // is grayscale image
} }
// ToImage returns the instance of image.Image of the image. // ToImage returns the instance of image.Image of the image.
func (i Image) ToImage() image.Image { func (i Image) ToImage() image.Image {
return i.image return i.image
} }
// String returns the image as a string. // String returns the image as a string.
func (i Image) String() 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. // addError adds an error to imgo.
// if OnlyReason is true, only the error message is returned. // if OnlyReason is true, only the error message is returned.
func (i *Image) addError(err error, OnlyReason ...bool) { func (i *Image) addError(err error, OnlyReason ...bool) {
log.SetPrefix("[IMGO] ") log.SetPrefix("[IMGO] ")
var onlyReason bool var onlyReason bool
if len(OnlyReason) > 0 { if len(OnlyReason) > 0 {
onlyReason = OnlyReason[0] onlyReason = OnlyReason[0]
} }
yellow := cliColor.New(cliColor.FgYellow).SprintFunc() yellow := cliColor.New(cliColor.FgYellow).SprintFunc()
magenta := cliColor.New(cliColor.FgMagenta).SprintFunc() magenta := cliColor.New(cliColor.FgMagenta).SprintFunc()
_, file, line, ok := runtime.Caller(1) _, file, line, ok := runtime.Caller(1)
if i.Error == nil { if i.Error == nil {
if ok && !onlyReason { if ok && !onlyReason {
i.Error = fmt.Errorf("%v %v", yellow(file, ":", line), magenta("Error: ", err.Error())) i.Error = fmt.Errorf("%v %v", yellow(file, ":", line), magenta("Error: ", err.Error()))
} else { } else {
i.Error = err i.Error = err
} }
} else if err != nil { } else if err != nil {
if ok && !onlyReason { if ok && !onlyReason {
i.Error = fmt.Errorf("%v\n%v %v", i.Error, yellow(file, ":", line), magenta("Error: ", err.Error())) i.Error = fmt.Errorf("%v\n%v %v", i.Error, yellow(file, ":", line), magenta("Error: ", err.Error()))
} else { } else {
i.Error = fmt.Errorf("%v\n%v", i.Error, err) i.Error = fmt.Errorf("%v\n%v", i.Error, err)
} }
} }
} }
// Extension returns the extension of the image. // Extension returns the extension of the image.
func (i Image) Extension() string { func (i Image) Extension() string {
return i.extension return i.extension
} }
// Mimetype returns the mimetype of the image. // Mimetype returns the mimetype of the image.
func (i Image) Mimetype() string { func (i Image) Mimetype() string {
return i.mimetype return i.mimetype
} }
// Height returns the height of the image. // Height returns the height of the image.
func (i Image) Height() int { func (i Image) Height() int {
return i.height return i.height
} }
// Width returns the width of the image. // Width returns the width of the image.
func (i Image) Width() int { 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. // Filesize returns the filesize of the image, if instance is initiated from an actual file.
func (i Image) Filesize() int64 { func (i Image) Filesize() int64 {
return i.filesize return i.filesize
} }
// Bounds returns the bounds of the image. // Bounds returns the bounds of the image.
func (i Image) Bounds() image.Rectangle { 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. // 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, // source can be a file path, a URL, a base64 encoded string, an *os.File, an image.Image,
// a byte slice or an *Image. // a byte slice or an *Image.
func (i *Image) Insert(source interface{}, x, y int) *Image { func (i *Image) Insert(source interface{}, x, y int) *Image {
// the image to insert // the image to insert
insert := &Image{} insert := &Image{}
switch source.(type) { switch source.(type) {
case *Image: case *Image:
insert = source.(*Image) insert = source.(*Image)
default: default:
insert = Load(source) insert = Load(source)
} }
// check errors // check errors
if insert.Error != nil { if insert.Error != nil {
i.addError(insert.Error, true) i.addError(insert.Error, true)
return i return i
} }
if i.Error != nil { if i.Error != nil {
return i return i
} }
// check x and y is within the image // check x and y is within the image
if x > i.width || y > i.height { if x > i.width || y > i.height {
return i return i
} }
// insert the image // insert the image
draw.Draw(i.image, i.Bounds(), insert.image, insert.image.Bounds().Min.Sub(image.Pt(x, y)), draw.Over) 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. // 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. // 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. // 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 { func (i *Image) Save(path string, quality ...int) *Image {
if i.Error != nil { if i.Error != nil {
log.Println(i.Error) log.Println(i.Error)
return i return i
} }
// check extension // check extension
pathSplit := strings.Split(path, ".") pathSplit := strings.Split(path, ".")
extension := pathSplit[len(pathSplit)-1] extension := pathSplit[len(pathSplit)-1]
if !(extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "tiff" || extension == "bmp") { if !(extension == "png" || extension == "jpg" || extension == "jpeg" || extension == "tiff" || extension == "bmp") {
i.addError(ErrSaveImageFormatNotSupport) i.addError(ErrSaveImageFormatNotSupport)
log.Println(i.Error) log.Println(i.Error)
return i return i
} }
// create file // create file
file, err := os.Create(path) file, err := os.Create(path)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
log.Println(i.Error) log.Println(i.Error)
return i return i
} }
defer func(file *os.File) { defer func(file *os.File) {
err := file.Close() err := file.Close()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
}(file) }(file)
// get the image // get the image
var img image.Image var img image.Image
if i.isGrayscale { // grayscale image if i.isGrayscale { // grayscale image
gray := image.NewGray(i.image.Bounds()) gray := image.NewGray(i.image.Bounds())
for x := 0; x < i.width; x++ { for x := 0; x < i.width; x++ {
for y := 0; y < i.height; y++ { for y := 0; y < i.height; y++ {
rgbColor := i.image.At(x, y) rgbColor := i.image.At(x, y)
grayColor := gray.ColorModel().Convert(rgbColor) grayColor := gray.ColorModel().Convert(rgbColor)
gray.Set(x, y, grayColor) gray.Set(x, y, grayColor)
} }
} }
img = gray img = gray
} else { // RGBA image } else { // RGBA image
img = i.image img = i.image
} }
// save image to file // save image to file
if extension == "png" { if extension == "png" {
err = png.Encode(file, img) err = png.Encode(file, img)
} else if extension == "jpg" || extension == "jpeg" { } else if extension == "jpg" || extension == "jpeg" {
if len(quality) > 0 && quality[0] > 0 && quality[0] < 100 { if len(quality) > 0 && quality[0] > 0 && quality[0] < 100 {
err = jpeg.Encode(file, img, &jpeg.Options{Quality: quality[0]}) err = jpeg.Encode(file, img, &jpeg.Options{Quality: quality[0]})
} else { } else {
err = jpeg.Encode(file, img, &jpeg.Options{Quality: 100}) err = jpeg.Encode(file, img, &jpeg.Options{Quality: 100})
} }
} else if extension == "tiff" { } else if extension == "tiff" {
err = tiff.Encode(file, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) err = tiff.Encode(file, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
} else if extension == "bmp" { } else if extension == "bmp" {
err = bmp.Encode(file, img) err = bmp.Encode(file, img)
} }
if err != nil { if err != nil {
i.addError(err) i.addError(err)
log.Println(i.Error) log.Println(i.Error)
return i return i
} }
return i return i
} }
// Resize resizes the image to the specified width and height. // Resize resizes the image to the specified width and height.
func (i *Image) Resize(width, height int) *Image { func (i *Image) Resize(width, height int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if width == i.width || height == i.height || (width == 0 && height == 0) { if width == i.width || height == i.height || (width == 0 && height == 0) {
return i return i
} }
resized := resize.Resize(uint(width), uint(height), i.image, resize.Lanczos3) resized := resize.Resize(uint(width), uint(height), i.image, resize.Lanczos3)
i.image = Image2RGBA(resized) i.image = Image2RGBA(resized)
i.width = resized.Bounds().Dx() i.width = resized.Bounds().Dx()
i.height = resized.Bounds().Dy() i.height = resized.Bounds().Dy()
return i return i
} }
// Crop Cut out a rectangular part of the current image with given width and height. // 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 { func (i *Image) Crop(x, y, width, height int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if width == i.width || height == i.height || width == 0 || height == 0 || x > i.width || y > i.height { if width == i.width || height == i.height || width == 0 || height == 0 || x > i.width || y > i.height {
return i return i
} }
x1 := x + width x1 := x + width
y1 := y + height y1 := y + height
clipped := i.image.SubImage(image.Rect(x, y, x1, y1)) clipped := i.image.SubImage(image.Rect(x, y, x1, y1))
i.image = Image2RGBA(clipped) i.image = Image2RGBA(clipped)
i.width = width i.width = width
i.height = height i.height = height
return i return i
} }
// Rotate rotates the image clockwise by the specified angle. // Rotate rotates the image clockwise by the specified angle.
func (i *Image) Rotate(angle int) *Image { func (i *Image) Rotate(angle int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
angle %= 360 angle %= 360
if angle == 0 { if angle == 0 {
return i return i
} }
// angle to radian // angle to radian
radian := float64(angle) * math.Pi / 180.0 radian := float64(angle) * math.Pi / 180.0
cos := math.Cos(radian) cos := math.Cos(radian)
sin := math.Sin(radian) sin := math.Sin(radian)
w := float64(i.width) w := float64(i.width)
h := float64(i.height) h := float64(i.height)
// calculate the new image size // calculate the new image size
W := int(math.Max(math.Abs(w*cos-h*sin), math.Abs(w*cos+h*sin))) 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))) H := int(math.Max(math.Abs(w*sin-h*cos), math.Abs(w*sin+h*cos)))
// rotate the image // rotate the image
dst := image.NewRGBA(image.Rect(0, 0, W, H)) dst := image.NewRGBA(image.Rect(0, 0, W, H))
err := graphics.Rotate(dst, i.image, &graphics.RotateOptions{Angle: radian}) err := graphics.Rotate(dst, i.image, &graphics.RotateOptions{Angle: radian})
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return i return i
} }
i.image = dst i.image = dst
i.width = W i.width = W
i.height = H i.height = H
return i return i
} }
// Grayscale converts the image to grayscale. // Grayscale converts the image to grayscale.
func (i *Image) Grayscale() *Image { func (i *Image) Grayscale() *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
for x := 0; x < i.width; x++ { for x := 0; x < i.width; x++ {
for y := 0; y < i.height; y++ { for y := 0; y < i.height; y++ {
rgbColor := i.image.At(x, y) rgbColor := i.image.At(x, y)
grayColor := color.GrayModel.Convert(rgbColor) grayColor := color.GrayModel.Convert(rgbColor)
i.image.Set(x, y, grayColor) i.image.Set(x, y, grayColor)
} }
} }
i.isGrayscale = true i.isGrayscale = true
return i return i
} }
// Flip mirror the image vertically or horizontally. // Flip mirror the image vertically or horizontally.
func (i *Image) Flip(flipType FlipType) *Image { func (i *Image) Flip(flipType FlipType) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if flipType == Horizontal { if flipType == Horizontal {
i.flipHorizontally() i.flipHorizontally()
} else if flipType == Vertical { } else if flipType == Vertical {
i.flipVertically() i.flipVertically()
} }
return i return i
} }
// flipHorizontally flips the image horizontally. // flipHorizontally flips the image horizontally.
func (i *Image) flipHorizontally() { func (i *Image) flipHorizontally() {
for x := 0; x < i.width/2; x++ { for x := 0; x < i.width/2; x++ {
for y := 0; y < i.height; y++ { for y := 0; y < i.height; y++ {
pixel := i.image.At(x, y) pixel := i.image.At(x, y)
i.image.Set(x, y, i.image.At(i.width-x-1, y)) i.image.Set(x, y, i.image.At(i.width-x-1, y))
i.image.Set(i.width-x-1, y, pixel) i.image.Set(i.width-x-1, y, pixel)
} }
} }
} }
// flipVertically flips the image vertically. // flipVertically flips the image vertically.
func (i *Image) flipVertically() { func (i *Image) flipVertically() {
for y := 0; y < i.height/2; y++ { for y := 0; y < i.height/2; y++ {
for x := 0; x < i.width; x++ { for x := 0; x < i.width; x++ {
pixel := i.image.At(x, y) pixel := i.image.At(x, y)
i.image.Set(x, y, i.image.At(x, i.height-y-1)) i.image.Set(x, y, i.image.At(x, i.height-y-1))
i.image.Set(x, i.height-y-1, pixel) i.image.Set(x, i.height-y-1, pixel)
} }
} }
} }
// Text write a text string to the image at given (x, y) coordinate. // 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 { func (i *Image) Text(label string, x, y int, fontPath string, fontColor color.Color, fontSize float64, dpi float64) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
// Load font // Load font
fontBytes, err := ioutil.ReadFile(fontPath) fontBytes, err := ioutil.ReadFile(fontPath)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return i return i
} }
myFont, err := freetype.ParseFont(fontBytes) myFont, err := freetype.ParseFont(fontBytes)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return i return i
} }
c := freetype.NewContext() c := freetype.NewContext()
c.SetDPI(dpi) c.SetDPI(dpi)
c.SetFont(myFont) c.SetFont(myFont)
c.SetFontSize(fontSize) c.SetFontSize(fontSize)
c.SetClip(i.Bounds()) c.SetClip(i.Bounds())
c.SetDst(i.image) c.SetDst(i.image)
uni := image.NewUniform(fontColor) uni := image.NewUniform(fontColor)
c.SetSrc(uni) c.SetSrc(uni)
c.SetHinting(font.HintingNone) c.SetHinting(font.HintingNone)
// Draw text // Draw text
pt := freetype.Pt(x, y+int(c.PointToFixed(fontSize)>>6)) pt := freetype.Pt(x, y+int(c.PointToFixed(fontSize)>>6))
if _, err := c.DrawString(label, pt); err != nil { if _, err := c.DrawString(label, pt); err != nil {
i.addError(err) i.addError(err)
return i return i
} }
return i return i
} }
// Thumbnail returns a thumbnail of the image with given width and height. // Thumbnail returns a thumbnail of the image with given width and height.
func (i *Image) Thumbnail(width, height int) *Image { func (i *Image) Thumbnail(width, height int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if width >= i.width || height >= i.height || width == 0 || height == 0 { if width >= i.width || height >= i.height || width == 0 || height == 0 {
return i return i
} }
dst := image.NewRGBA(image.Rect(0, 0, width, height)) dst := image.NewRGBA(image.Rect(0, 0, width, height))
err := graphics.Thumbnail(dst, i.image) err := graphics.Thumbnail(dst, i.image)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return i return i
} }
i.image = dst i.image = dst
i.width = width i.width = width
i.height = height i.height = height
return i return i
} }
// HttpHandler responds the image as an HTTP handler. // HttpHandler responds the image as an HTTP handler.
func (i Image) HttpHandler(w http.ResponseWriter, r *http.Request) { func (i Image) HttpHandler(w http.ResponseWriter, r *http.Request) {
if i.Error != nil { if i.Error != nil {
log.Println(i.Error) log.Println(i.Error)
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Type", "image/png")
err := png.Encode(w, i.image) err := png.Encode(w, i.image)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} }

310
loader.go
View File

@@ -1,12 +1,12 @@
package imgo package imgo
import ( import (
"bytes" "bytes"
"image" "image"
"image/color" "image/color"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
) )
type ImageManager struct { type ImageManager struct {
@@ -15,201 +15,201 @@ type ImageManager struct {
// Load an image from source. // 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. // 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 { func Load(source interface{}) *Image {
switch source.(type) { switch source.(type) {
case string: case string:
return loadFromString(source.(string)) return loadFromString(source.(string))
case *os.File: case *os.File:
return LoadFromFile(source.(*os.File)) return LoadFromFile(source.(*os.File))
case image.Image: case image.Image:
return LoadFromImage(source.(image.Image)) return LoadFromImage(source.(image.Image))
case []byte: case []byte:
return loadFromString(string(source.([]byte))) return loadFromString(string(source.([]byte)))
case *Image: case *Image:
return LoadFromImgo(source.(*Image)) return LoadFromImgo(source.(*Image))
default: default:
i := &Image{} i := &Image{}
i.addError(ErrSourceNotSupport) i.addError(ErrSourceNotSupport)
return i return i
} }
} }
// loadFromString loads an image when the source is a string. // loadFromString loads an image when the source is a string.
func loadFromString(source string) (i *Image) { func loadFromString(source string) (i *Image) {
i = &Image{} i = &Image{}
if len(source) == 0 { if len(source) == 0 {
i.addError(ErrSourceStringIsEmpty) i.addError(ErrSourceStringIsEmpty)
return return
} }
if len(source) > 4 && source[:4] == "http" { if len(source) > 4 && source[:4] == "http" {
return LoadFromUrl(source) return LoadFromUrl(source)
} else if len(source) > 10 && source[:10] == "data:image" { } else if len(source) > 10 && source[:10] == "data:image" {
return LoadFromBase64(source) return LoadFromBase64(source)
} else { } else {
return LoadFromPath(source) return LoadFromPath(source)
} }
} }
// LoadFromUrl loads an image when the source is an url. // LoadFromUrl loads an image when the source is an url.
func LoadFromUrl(url string) (i *Image) { func LoadFromUrl(url string) (i *Image) {
i = &Image{} i = &Image{}
// Get the image response from the url. // Get the image response from the url.
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
// Read the image data from the response. // Read the image data from the response.
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Get the extension, mimetype and corresponding decoder function of the image. // Get the extension, mimetype and corresponding decoder function of the image.
ext, mime, decoder, err := GetImageType(bodyBytes[:8]) ext, mime, decoder, err := GetImageType(bodyBytes[:8])
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Decode the image. // Decode the image.
file := bytes.NewReader(bodyBytes) file := bytes.NewReader(bodyBytes)
img, err := decoder(file) img, err := decoder(file)
if err != nil { if err != nil {
i.addError(ErrSourceNotSupport) i.addError(ErrSourceNotSupport)
return return
} }
return &Image{ return &Image{
image: Image2RGBA(img), image: Image2RGBA(img),
width: img.Bounds().Dx(), width: img.Bounds().Dx(),
height: img.Bounds().Dy(), height: img.Bounds().Dy(),
extension: ext, extension: ext,
mimetype: mime, mimetype: mime,
} }
} }
// LoadFromPath loads an image from a path. // LoadFromPath loads an image from a path.
func LoadFromPath(path string) (i *Image) { func LoadFromPath(path string) (i *Image) {
i = &Image{} i = &Image{}
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
defer file.Close() defer file.Close()
return LoadFromFile(file) return LoadFromFile(file)
} }
// LoadFromFile loads an image from a file. // LoadFromFile loads an image from a file.
func LoadFromFile(file *os.File) (i *Image) { func LoadFromFile(file *os.File) (i *Image) {
i = &Image{} i = &Image{}
// Read the first 8 bytes of the image. // Read the first 8 bytes of the image.
buf := make([]byte, 8) buf := make([]byte, 8)
_, err := file.Read(buf) _, err := file.Read(buf)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// After reading the first 8 bytes, we seek back to the beginning of the file. // After reading the first 8 bytes, we seek back to the beginning of the file.
_, err = file.Seek(0, 0) _, err = file.Seek(0, 0)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Get the extension, mimetype and corresponding decoder function of the image. // Get the extension, mimetype and corresponding decoder function of the image.
ext, mime, decoder, err := GetImageType(buf) ext, mime, decoder, err := GetImageType(buf)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Decode the image. // Decode the image.
img, err := decoder(file) img, err := decoder(file)
if err != nil { if err != nil {
i.addError(err) i.addError(err)
return return
} }
// Set the image properties. // Set the image properties.
stat, _ := file.Stat() stat, _ := file.Stat()
return &Image{ return &Image{
image: Image2RGBA(img), image: Image2RGBA(img),
width: img.Bounds().Dx(), width: img.Bounds().Dx(),
height: img.Bounds().Dy(), height: img.Bounds().Dy(),
extension: ext, extension: ext,
mimetype: mime, mimetype: mime,
filesize: stat.Size(), filesize: stat.Size(),
} }
} }
// LoadFromImage loads an image from an instance of image.Image. // LoadFromImage loads an image from an instance of image.Image.
func LoadFromImage(img image.Image) (i *Image) { func LoadFromImage(img image.Image) (i *Image) {
i = &Image{} i = &Image{}
if img == nil { if img == nil {
i.addError(ErrSourceImageIsNil) i.addError(ErrSourceImageIsNil)
return return
} }
var formatName string var formatName string
switch img.(type) { switch img.(type) {
case *image.NRGBA: // png case *image.NRGBA: // png
formatName = "png" formatName = "png"
case *image.RGBA: // bmp, tiff case *image.RGBA: // bmp, tiff
formatName = "png" formatName = "png"
case *image.YCbCr: // jpeg, webp case *image.YCbCr: // jpeg, webp
formatName = "jpg" formatName = "jpg"
default: default:
i.addError(ErrSourceImageNotSupport) i.addError(ErrSourceImageNotSupport)
return return
} }
return &Image{ return &Image{
image: Image2RGBA(img), image: Image2RGBA(img),
width: img.Bounds().Dx(), width: img.Bounds().Dx(),
height: img.Bounds().Dy(), height: img.Bounds().Dy(),
extension: formatName, extension: formatName,
mimetype: "image/" + formatName, mimetype: "image/" + formatName,
} }
} }
// LoadFromImgo loads an image from an instance of Image. // LoadFromImgo loads an image from an instance of Image.
func LoadFromImgo(i *Image) *Image { func LoadFromImgo(i *Image) *Image {
return i return i
} }
// Canvas create a new empty image. // Canvas create a new empty image.
func Canvas(width, height int, fillColor ...color.Color) *Image { func Canvas(width, height int, fillColor ...color.Color) *Image {
var c color.Color var c color.Color
if len(fillColor) == 0 { if len(fillColor) == 0 {
c = color.Transparent c = color.Transparent
} else { } else {
c = fillColor[0] c = fillColor[0]
} }
img := image.NewRGBA(image.Rect(0, 0, width, height)) img := image.NewRGBA(image.Rect(0, 0, width, height))
for x := 0; x < img.Bounds().Dx(); x++ { for x := 0; x < img.Bounds().Dx(); x++ {
for y := 0; y < img.Bounds().Dy(); y++ { for y := 0; y < img.Bounds().Dy(); y++ {
img.Set(x, y, c) img.Set(x, y, c)
} }
} }
return &Image{ return &Image{
image: img, image: img,
width: img.Bounds().Dx(), width: img.Bounds().Dx(),
height: img.Bounds().Dy(), height: img.Bounds().Dy(),
extension: "png", extension: "png",
mimetype: "image/png", mimetype: "image/png",
} }
} }

View File

@@ -1,125 +1,125 @@
package imgo package imgo
import ( import (
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"math" "math"
) )
// calculateMeanAverageColorWithRect returns the mean average color of the image in given rectangle. // 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) { func (i Image) calculateMeanAverageColorWithRect(rect image.Rectangle, useSquaredAverage bool) (red, green, blue uint8) {
var redSum float64 var redSum float64
var greenSum float64 var greenSum float64
var blueSum float64 var blueSum float64
for x := rect.Min.X; x <= rect.Max.X; x++ { for x := rect.Min.X; x <= rect.Max.X; x++ {
for y := rect.Min.Y; y <= rect.Max.Y; y++ { for y := rect.Min.Y; y <= rect.Max.Y; y++ {
pixel := i.image.At(x, y) pixel := i.image.At(x, y)
col := color.RGBAModel.Convert(pixel).(color.RGBA) col := color.RGBAModel.Convert(pixel).(color.RGBA)
if useSquaredAverage { if useSquaredAverage {
redSum += float64(col.R) * float64(col.R) redSum += float64(col.R) * float64(col.R)
greenSum += float64(col.G) * float64(col.G) greenSum += float64(col.G) * float64(col.G)
blueSum += float64(col.B) * float64(col.B) blueSum += float64(col.B) * float64(col.B)
} else { } else {
redSum += float64(col.R) redSum += float64(col.R)
greenSum += float64(col.G) greenSum += float64(col.G)
blueSum += float64(col.B) blueSum += float64(col.B)
} }
} }
} }
rectArea := float64((rect.Dx() + 1) * (rect.Dy() + 1)) rectArea := float64((rect.Dx() + 1) * (rect.Dy() + 1))
if useSquaredAverage { if useSquaredAverage {
red = uint8(math.Round(math.Sqrt(redSum / rectArea))) red = uint8(math.Round(math.Sqrt(redSum / rectArea)))
green = uint8(math.Round(math.Sqrt(greenSum / rectArea))) green = uint8(math.Round(math.Sqrt(greenSum / rectArea)))
blue = uint8(math.Round(math.Sqrt(blueSum / rectArea))) blue = uint8(math.Round(math.Sqrt(blueSum / rectArea)))
} else { } else {
red = uint8(math.Round(redSum / rectArea)) red = uint8(math.Round(redSum / rectArea))
green = uint8(math.Round(greenSum / rectArea)) green = uint8(math.Round(greenSum / rectArea))
blue = uint8(math.Round(blueSum / rectArea)) blue = uint8(math.Round(blueSum / rectArea))
} }
return return
} }
// pixelate apply pixelation filter to the image in given rectangle. // pixelate apply pixelation filter to the image in given rectangle.
func (i *Image) pixelate(size int, rectangle image.Rectangle) *Image { func (i *Image) pixelate(size int, rectangle image.Rectangle) *Image {
bg := image.NewRGBA(i.image.Bounds()) bg := image.NewRGBA(i.image.Bounds())
draw.Draw(bg, bg.Bounds(), i.image, i.image.Bounds().Min, draw.Over) 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 x := rectangle.Min.X; x < rectangle.Max.X; x += size {
for y := rectangle.Min.Y; y < rectangle.Max.Y; y += size { for y := rectangle.Min.Y; y < rectangle.Max.Y; y += size {
rect := image.Rect(x, y, x+size, y+size) rect := image.Rect(x, y, x+size, y+size)
if rect.Max.X > i.width { if rect.Max.X > i.width {
rect.Max.X = i.width rect.Max.X = i.width
} }
if rect.Max.Y > i.height { if rect.Max.Y > i.height {
rect.Max.Y = i.height rect.Max.Y = i.height
} }
r, g, b := i.calculateMeanAverageColorWithRect(rect, true) r, g, b := i.calculateMeanAverageColorWithRect(rect, true)
col := color.RGBA{R: r, G: g, B: b, A: 255} col := color.RGBA{R: r, G: g, B: b, A: 255}
for x2 := rect.Min.X; x2 < rect.Max.X; x2++ { for x2 := rect.Min.X; x2 < rect.Max.X; x2++ {
for y2 := rect.Min.Y; y2 < rect.Max.Y; y2++ { for y2 := rect.Min.Y; y2 < rect.Max.Y; y2++ {
bg.Set(x2, y2, col) bg.Set(x2, y2, col)
} }
} }
} }
} }
i.image = bg i.image = bg
return i return i
} }
// Pixelate apply pixelation filter to the image. // Pixelate apply pixelation filter to the image.
// size is the size of the pixel. // size is the size of the pixel.
func (i *Image) Pixelate(size int) *Image { func (i *Image) Pixelate(size int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if size <= 1 { if size <= 1 {
return i return i
} }
if i.width > i.height { if i.width > i.height {
if size > i.width { if size > i.width {
size = i.width size = i.width
} }
} else { } else {
if size > i.height { if size > i.height {
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 // 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. // (x1, y1) and (x2, y2) are the top-left and bottom-right coordinates of the rectangle.
// size is the size of the pixel. // size is the size of the pixel.
func (i *Image) Mosaic(size, x1, y1, x2, y2 int) *Image { func (i *Image) Mosaic(size, x1, y1, x2, y2 int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if x1 < 0 { if x1 < 0 {
x1 = 0 x1 = 0
} }
if y1 < 0 { if y1 < 0 {
y1 = 0 y1 = 0
} }
if x2 > i.width { if x2 > i.width {
x2 = i.width x2 = i.width
} }
if y2 > i.height { if y2 > i.height {
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))
} }

View File

@@ -1,68 +1,68 @@
package imgo package imgo
import ( import (
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
) )
// BorderRadius draws rounded corners on the image with given radius. // BorderRadius draws rounded corners on the image with given radius.
func (i *Image) BorderRadius(radius float64) *Image { func (i *Image) BorderRadius(radius float64) *Image {
if radius > float64(i.width/2) || radius > float64(i.height/2) { if radius > float64(i.width/2) || radius > float64(i.height/2) {
return i return i
} }
c := Radius{p: image.Point{X: i.width, Y: i.height}, r: int(radius)} c := Radius{p: image.Point{X: i.width, Y: i.height}, r: int(radius)}
dst := image.NewRGBA(i.image.Bounds()) dst := image.NewRGBA(i.image.Bounds())
draw.DrawMask(dst, dst.Bounds(), i.image, image.Point{}, &c, image.Point{}, draw.Over) draw.DrawMask(dst, dst.Bounds(), i.image, image.Point{}, &c, image.Point{}, draw.Over)
i.image = dst i.image = dst
return i return i
} }
type Radius struct { type Radius struct {
p image.Point // the right-bottom Point of the image p image.Point // the right-bottom Point of the image
r int // radius r int // radius
} }
func (c *Radius) ColorModel() color.Model { func (c *Radius) ColorModel() color.Model {
return color.AlphaModel return color.AlphaModel
} }
func (c *Radius) Bounds() image.Rectangle { 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 { func (c *Radius) At(x, y int) color.Color {
var xx, yy, rr float64 var xx, yy, rr float64
var inArea bool var inArea bool
// left up // left up
if x <= c.r && y <= c.r { 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) xx, yy, rr = float64(c.r-x)+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true inArea = true
} }
// right up // right up
if x >= (c.p.X-c.r) && y <= c.r { 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) xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true inArea = true
} }
// left bottom // left bottom
if x <= c.r && y >= (c.p.Y-c.r) { 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) xx, yy, rr = float64(c.r-x)+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r)
inArea = true inArea = true
} }
// right bottom // right bottom
if x >= (c.p.X-c.r) && y >= (c.p.Y-c.r) { 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) 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 inArea = true
} }
if inArea && xx*xx+yy*yy >= rr*rr { if inArea && xx*xx+yy*yy >= rr*rr {
return color.Alpha{} return color.Alpha{}
} }
return color.Alpha{A: 255} return color.Alpha{A: 255}
} }

240
shape.go
View File

@@ -1,166 +1,166 @@
package imgo package imgo
import ( import (
"image" "image"
"image/color" "image/color"
) )
// Pixel draws a pixel at given (x, y) coordinate with given color. // Pixel draws a pixel at given (x, y) coordinate with given color.
func (i *Image) Pixel(x, y int, c color.Color) *Image { func (i *Image) Pixel(x, y int, c color.Color) *Image {
if i.Error != nil { if i.Error != nil {
return i 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. // Line draws a line from (x1, y1) to (x2, y2) with given color.
// TODO: width is not working yet. // TODO: width is not working yet.
func (i *Image) Line(x1, y1, x2, y2 int, c color.Color, width ...int) *Image { func (i *Image) Line(x1, y1, x2, y2 int, c color.Color, width ...int) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
w := 1 w := 1
if len(width) != 0 { if len(width) != 0 {
w = width[0] w = width[0]
} }
if w <= 0 { if w <= 0 {
return i return i
} }
bg := i.image bg := i.image
dx := x2 - x1 dx := x2 - x1
dy := y2 - y1 dy := y2 - y1
if dx == 0 && dy == 0 { // Line is a point. if dx == 0 && dy == 0 { // Line is a point.
bg.Set(x1, y1, c) bg.Set(x1, y1, c)
i.image = bg i.image = bg
return i return i
} else if dx == 0 { // vertical line } else if dx == 0 { // vertical line
if y1 > y2 { if y1 > y2 {
y1, y2 = y2, y1 y1, y2 = y2, y1
} }
for y := y1; y <= y2; y++ { for y := y1; y <= y2; y++ {
bg.Set(x1, y, c) bg.Set(x1, y, c)
} }
} else if dy == 0 { // horizontal line } else if dy == 0 { // horizontal line
if x1 > x2 { if x1 > x2 {
x1, x2 = x2, x1 x1, x2 = x2, x1
} }
for x := x1; x <= x2; x++ { for x := x1; x <= x2; x++ {
bg.Set(x, y1, c) bg.Set(x, y1, c)
} }
} else { // diagonal line } else { // diagonal line
k := float64(dy) / float64(dx) k := float64(dy) / float64(dx)
if x1 > x2 { if x1 > x2 {
x1, x2 = x2, x1 x1, x2 = x2, x1
y1, y2 = y2, y1 y1, y2 = y2, y1
} }
if -1 < k && k < 1 { if -1 < k && k < 1 {
for x := x1; x <= x2; x++ { for x := x1; x <= x2; x++ {
y := int(float64(y1) + float64(x-x1)*k) y := int(float64(y1) + float64(x-x1)*k)
bg.Set(x, y, c) bg.Set(x, y, c)
} }
} else { } else {
ySmaller, yBigger := y1, y2 ySmaller, yBigger := y1, y2
if y1 > y2 { if y1 > y2 {
ySmaller, yBigger = y2, y1 ySmaller, yBigger = y2, y1
} }
for y := ySmaller; y <= yBigger; y++ { for y := ySmaller; y <= yBigger; y++ {
x := int(float64(x1) + float64(y-y1)/k) x := int(float64(x1) + float64(y-y1)/k)
bg.Set(x, y, c) bg.Set(x, y, c)
} }
} }
} }
return i return i
} }
// Circle draws a circle at given center coordinate (x, y) with given radius and color. // 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 { func (i *Image) Circle(x, y, radius int, c color.Color) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if radius <= 0 || radius >= i.width || radius >= i.height { if radius <= 0 || radius >= i.width || radius >= i.height {
return i return i
} }
x1 := x - radius x1 := x - radius
y1 := y - radius y1 := y - radius
x2 := x + radius x2 := x + radius
y2 := y + radius y2 := y + radius
bg := i.image bg := i.image
for x3 := x1; x3 < x2; x3++ { for x3 := x1; x3 < x2; x3++ {
for y3 := y1; y3 < y2; y3++ { for y3 := y1; y3 < y2; y3++ {
if (x3-x)*(x3-x)+(y3-y)*(y3-y) <= radius*radius { if (x3-x)*(x3-x)+(y3-y)*(y3-y) <= radius*radius {
bg.Set(x3, y3, c) 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. // 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 { func (i *Image) Rectangle(x, y, width, height int, c color.Color) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if width <= 0 || height <= 0 { if width <= 0 || height <= 0 {
return i return i
} }
x1 := x x1 := x
y1 := y y1 := y
x2 := x + width x2 := x + width
y2 := y + height y2 := y + height
bg := i.image bg := i.image
for x3 := x1; x3 < x2; x3++ { for x3 := x1; x3 < x2; x3++ {
for y3 := y1; y3 < y2; y3++ { for y3 := y1; y3 < y2; y3++ {
if image.Pt(x3, y3).In(i.image.Bounds()) { if image.Pt(x3, y3).In(i.image.Bounds()) {
bg.Set(x3, y3, c) 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. // 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 { func (i *Image) Ellipse(x, y, width, height int, c color.Color) *Image {
if i.Error != nil { if i.Error != nil {
return i return i
} }
if width <= 0 || height <= 0 { if width <= 0 || height <= 0 {
return i return i
} }
a := float64(width) / 2 a := float64(width) / 2
b := float64(height) / 2 b := float64(height) / 2
x1 := x - int(a) x1 := x - int(a)
y1 := y - int(b) y1 := y - int(b)
x2 := x + int(a) x2 := x + int(a)
y2 := y + int(b) y2 := y + int(b)
bg := i.image bg := i.image
for x3 := x1; x3 <= x2; x3++ { for x3 := x1; x3 <= x2; x3++ {
for y3 := y1; y3 <= y2; y3++ { 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 { 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) bg.Set(x3, y3, c)
} }
} }
} }
return i return i
} }

112
util.go
View File

@@ -1,82 +1,82 @@
package imgo package imgo
import ( import (
"fmt" "fmt"
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
"golang.org/x/image/tiff" "golang.org/x/image/tiff"
"golang.org/x/image/webp" "golang.org/x/image/webp"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"image/jpeg" "image/jpeg"
"image/png" "image/png"
"io" "io"
) )
// GetImageType returns the extension, mimetype and corresponding decoder function of the image. // GetImageType returns the extension, mimetype and corresponding decoder function of the image.
// It judges the image by its first few bytes called magic number. // 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) { func GetImageType(bytes []byte) (ext string, mimetype string, decoder func(r io.Reader) (image.Image, error), err error) {
if len(bytes) < 2 { if len(bytes) < 2 {
err = ErrSourceImageNotSupport err = ErrSourceImageNotSupport
return return
} }
if bytes[0] == 0xFF && bytes[1] == 0xD8 { if bytes[0] == 0xFF && bytes[1] == 0xD8 {
ext = "jpg" ext = "jpg"
mimetype = "image/jpeg" mimetype = "image/jpeg"
decoder = jpeg.Decode decoder = jpeg.Decode
} }
if len(bytes) >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 { if len(bytes) >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 {
ext = "png" ext = "png"
mimetype = "image/png" mimetype = "image/png"
decoder = png.Decode decoder = png.Decode
} }
if bytes[0] == 0x42 && bytes[1] == 0x4D { if bytes[0] == 0x42 && bytes[1] == 0x4D {
ext = "bmp" ext = "bmp"
mimetype = "image/x-ms-bmp" mimetype = "image/x-ms-bmp"
decoder = bmp.Decode decoder = bmp.Decode
} }
if (bytes[0] == 0x49 && bytes[1] == 0x49) || (bytes[0] == 0x4D && bytes[1] == 0x4D) { if (bytes[0] == 0x49 && bytes[1] == 0x49) || (bytes[0] == 0x4D && bytes[1] == 0x4D) {
ext = "tiff" ext = "tiff"
mimetype = "image/tiff" mimetype = "image/tiff"
decoder = tiff.Decode decoder = tiff.Decode
} }
if bytes[0] == 0x52 && bytes[1] == 0x49 { if bytes[0] == 0x52 && bytes[1] == 0x49 {
ext = "webp" ext = "webp"
mimetype = "image/webp" mimetype = "image/webp"
decoder = webp.Decode decoder = webp.Decode
} }
/*if bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x38 { /*if bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46 && bytes[3] == 0x38 {
ext = "gif" ext = "gif"
mimetype = "image/gif" mimetype = "image/gif"
} }
if bytes[0] == 0x00 && bytes[1] == 0x00 && (bytes[2] == 0x01 || bytes[2] == 0x02) && bytes[3] == 0x00 { if bytes[0] == 0x00 && bytes[1] == 0x00 && (bytes[2] == 0x01 || bytes[2] == 0x02) && bytes[3] == 0x00 {
ext = "ico" ext = "ico"
mimetype = "image/x-icon" mimetype = "image/x-icon"
}*/ }*/
if ext == "" { if ext == "" {
err = ErrSourceImageNotSupport err = ErrSourceImageNotSupport
} }
return return
} }
// Color2Hex converts a color.Color to its hex string representation. // Color2Hex converts a color.Color to its hex string representation.
func Color2Hex(c color.Color) string { func Color2Hex(c color.Color) string {
r, g, b, _ := c.RGBA() r, g, b, _ := c.RGBA()
return fmt.Sprintf("#%02X%02X%02X", uint8(r>>8), uint8(g>>8), uint8(b>>8)) return fmt.Sprintf("#%02X%02X%02X", uint8(r>>8), uint8(g>>8), uint8(b>>8))
} }
// Image2RGBA converts an image to RGBA. // Image2RGBA converts an image to RGBA.
func Image2RGBA(img image.Image) *image.RGBA { func Image2RGBA(img image.Image) *image.RGBA {
rgba := image.NewRGBA(img.Bounds()) rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Over) draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Over)
return rgba return rgba
} }