mirror of
https://github.com/fishtailstudio/imgo
synced 2025-09-26 20:41:47 +08:00
Replace space indent with tab
This commit is contained in:
76
base64.go
76
base64.go
@@ -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
214
blur.go
@@ -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
|
||||||
}
|
}
|
||||||
|
42
color.go
42
color.go
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
const.go
4
const.go
@@ -4,6 +4,6 @@ package imgo
|
|||||||
type FlipType int
|
type FlipType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Vertical FlipType = iota
|
Vertical FlipType = iota
|
||||||
Horizontal
|
Horizontal
|
||||||
)
|
)
|
||||||
|
10
errors.go
10
errors.go
@@ -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")
|
||||||
)
|
)
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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
564
image.go
@@ -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
310
loader.go
@@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
174
pixelate.go
174
pixelate.go
@@ -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))
|
||||||
}
|
}
|
||||||
|
82
radius.go
82
radius.go
@@ -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
240
shape.go
@@ -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
112
util.go
@@ -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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user