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
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,
}
}

214
blur.go
View File

@@ -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
}

View File

@@ -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,
}
}

View File

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

View File

@@ -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")
)

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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")
}
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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")
}

564
image.go
View File

@@ -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)
}
}

310
loader.go
View File

@@ -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",
}
}

View File

@@ -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))
}

View File

@@ -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}
}

240
shape.go
View File

@@ -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
}

112
util.go
View File

@@ -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
}