mirror of
https://github.com/esimov/caire.git
synced 2025-09-26 20:41:14 +08:00
258 lines
6.3 KiB
Go
258 lines
6.3 KiB
Go
package caire
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/esimov/caire/utils"
|
|
"golang.org/x/image/bmp"
|
|
)
|
|
|
|
// decodeImg decodes an image file to type image.Image
|
|
func decodeImg(src string) (image.Image, error) {
|
|
file, err := os.Open(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open the mask file: %v", err)
|
|
}
|
|
|
|
ctype, err := utils.DetectContentType(file.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !strings.Contains(ctype.(string), "image") {
|
|
return nil, fmt.Errorf("the mask should be an image file")
|
|
}
|
|
|
|
img, _, err := image.Decode(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not decode the mask file: %v", err)
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
|
|
// encodeImg encodes an image to a destination of type io.Writer.
|
|
func encodeImg(p *Processor, w io.Writer, img *image.NRGBA) error {
|
|
switch w := w.(type) {
|
|
case *os.File:
|
|
ext := filepath.Ext(w.Name())
|
|
switch ext {
|
|
case "", ".jpg", ".jpeg":
|
|
res, err := Resize(p, img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
|
|
case ".png":
|
|
res, err := Resize(p, img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return png.Encode(w, res)
|
|
case ".bmp":
|
|
res, err := Resize(p, img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return bmp.Encode(w, res)
|
|
default:
|
|
return errors.New("unsupported image format")
|
|
}
|
|
default:
|
|
res, err := Resize(p, img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return jpeg.Encode(w, res, &jpeg.Options{Quality: 100})
|
|
}
|
|
}
|
|
|
|
// rotateImage90 rotate the image by 90 degree counter clockwise.
|
|
func rotateImage90(src *image.NRGBA) *image.NRGBA {
|
|
b := src.Bounds()
|
|
dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X))
|
|
for dstY := 0; dstY < b.Max.X; dstY++ {
|
|
for dstX := 0; dstX < b.Max.Y; dstX++ {
|
|
srcX := b.Max.X - dstY - 1
|
|
srcY := dstX
|
|
|
|
srcOff := srcY*src.Stride + srcX*4
|
|
dstOff := dstY*dst.Stride + dstX*4
|
|
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// rotateImage270 rotate the image by 270 degree counter clockwise.
|
|
func rotateImage270(src *image.NRGBA) *image.NRGBA {
|
|
b := src.Bounds()
|
|
dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X))
|
|
for dstY := 0; dstY < b.Max.X; dstY++ {
|
|
for dstX := 0; dstX < b.Max.Y; dstX++ {
|
|
srcX := dstY
|
|
srcY := b.Max.Y - dstX - 1
|
|
|
|
srcOff := srcY*src.Stride + srcX*4
|
|
dstOff := dstY*dst.Stride + dstX*4
|
|
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
|
|
}
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// imgToNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
|
|
func imgToNRGBA(img image.Image) *image.NRGBA {
|
|
srcBounds := img.Bounds()
|
|
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
|
|
if src0, ok := img.(*image.NRGBA); ok {
|
|
return src0
|
|
}
|
|
}
|
|
srcMinX := srcBounds.Min.X
|
|
srcMinY := srcBounds.Min.Y
|
|
|
|
dstBounds := srcBounds.Sub(srcBounds.Min)
|
|
dstW := dstBounds.Dx()
|
|
dstH := dstBounds.Dy()
|
|
dst := image.NewNRGBA(dstBounds)
|
|
|
|
switch src := img.(type) {
|
|
case *image.NRGBA:
|
|
rowSize := srcBounds.Dx() * 4
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
di := dst.PixOffset(0, dstY)
|
|
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
|
|
}
|
|
}
|
|
case *image.YCbCr:
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
di := dst.PixOffset(0, dstY)
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
srcX := srcMinX + dstX
|
|
srcY := srcMinY + dstY
|
|
siy := src.YOffset(srcX, srcY)
|
|
sic := src.COffset(srcX, srcY)
|
|
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
|
|
dst.Pix[di+0] = r
|
|
dst.Pix[di+1] = g
|
|
dst.Pix[di+2] = b
|
|
dst.Pix[di+3] = 0xff
|
|
di += 4
|
|
}
|
|
}
|
|
default:
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
di := dst.PixOffset(0, dstY)
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
|
|
dst.Pix[di+0] = c.R
|
|
dst.Pix[di+1] = c.G
|
|
dst.Pix[di+2] = c.B
|
|
dst.Pix[di+3] = c.A
|
|
di += 4
|
|
}
|
|
}
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// imgToPix converts an image to a pixel array.
|
|
func imgToPix(src *image.NRGBA) []uint8 {
|
|
bounds := src.Bounds()
|
|
pixels := make([]uint8, 0, bounds.Max.X*bounds.Max.Y*4)
|
|
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
r, g, b, _ := src.At(y, x).RGBA()
|
|
pixels = append(pixels, uint8(r>>8), uint8(g>>8), uint8(b>>8), 255)
|
|
}
|
|
}
|
|
|
|
return pixels
|
|
}
|
|
|
|
// pixToImage converts an array buffer to an image.
|
|
func pixToImage(pixels []uint8, width, height int) image.Image {
|
|
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
|
bounds := dst.Bounds()
|
|
dx, dy := bounds.Max.X, bounds.Max.Y
|
|
col := color.NRGBA{
|
|
R: uint8(0),
|
|
G: uint8(0),
|
|
B: uint8(0),
|
|
A: uint8(255),
|
|
}
|
|
|
|
for x := bounds.Min.X; x < dx; x++ {
|
|
for y := bounds.Min.Y; y < dy*4; y += 4 {
|
|
col.R = uint8(pixels[y+x*dy*4])
|
|
col.G = uint8(pixels[y+x*dy*4+1])
|
|
col.B = uint8(pixels[y+x*dy*4+2])
|
|
col.A = uint8(pixels[y+x*dy*4+3])
|
|
|
|
dst.SetNRGBA(x, int(y/4), col)
|
|
}
|
|
}
|
|
|
|
return dst
|
|
}
|
|
|
|
// rgbToGrayscale converts an image to grayscale mode and
|
|
// returns the pixel values as an one dimensional array.
|
|
func rgbToGrayscale(src *image.NRGBA) []uint8 {
|
|
width, height := src.Bounds().Dx(), src.Bounds().Dy()
|
|
gray := make([]uint8, width*height)
|
|
|
|
for y := 0; y < height; y++ {
|
|
for x := 0; x < width; x++ {
|
|
r, g, b, _ := src.At(x, y).RGBA()
|
|
gray[y*width+x] = uint8(
|
|
(0.299*float64(r) +
|
|
0.587*float64(g) +
|
|
0.114*float64(b)) / 256,
|
|
)
|
|
}
|
|
}
|
|
|
|
return gray
|
|
}
|
|
|
|
// dither converts an image to black and white image, where the white is fully transparent.
|
|
func dither(src *image.NRGBA) *image.NRGBA {
|
|
var (
|
|
bounds = src.Bounds()
|
|
dithered = image.NewNRGBA(bounds)
|
|
dx = bounds.Dx()
|
|
dy = bounds.Dy()
|
|
)
|
|
|
|
for x := 0; x < dx; x++ {
|
|
for y := 0; y < dy; y++ {
|
|
r, g, b, _ := src.At(x, y).RGBA()
|
|
threshold := func() color.Color {
|
|
if r > 127 && g > 127 && b > 127 {
|
|
return color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
|
}
|
|
return color.NRGBA{A: 0x00}
|
|
}
|
|
dithered.Set(x, y, threshold())
|
|
}
|
|
}
|
|
|
|
return dithered
|
|
}
|