mirror of
https://github.com/esimov/caire.git
synced 2026-04-23 00:07:11 +08:00
feat: implemented process GUI preview mode #70
This commit is contained in:
@@ -9,7 +9,13 @@ import (
|
||||
pigo "github.com/esimov/pigo/core"
|
||||
)
|
||||
|
||||
var usedSeams []UsedSeams
|
||||
// maxFaceDetAttempts defines the maximum number of attempts of face detections,
|
||||
const maxFaceDetAttempts = 20
|
||||
|
||||
var (
|
||||
faceDetAttempts int
|
||||
usedSeams []UsedSeams
|
||||
)
|
||||
|
||||
// Carver is the main entry struct having as parameters the newly generated image width, height and seam points.
|
||||
type Carver struct {
|
||||
@@ -69,12 +75,22 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) error {
|
||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
sobel := c.SobelDetector(img, float64(p.SobelThreshold))
|
||||
|
||||
if p.FaceDetect {
|
||||
// Transform the image to a pixel array.
|
||||
if p.FaceDetect && faceDetAttempts < maxFaceDetAttempts {
|
||||
var ratio float64
|
||||
|
||||
if width < height {
|
||||
ratio = float64(width) / float64(height)
|
||||
} else {
|
||||
ratio = float64(height) / float64(width)
|
||||
}
|
||||
minSize := float64(min(width, height)) * ratio / 3
|
||||
|
||||
// Transform the image to pixel array.
|
||||
pixels := c.rgbToGrayscale(img)
|
||||
|
||||
cParams := pigo.CascadeParams{
|
||||
MinSize: 100,
|
||||
MaxSize: max(width, height),
|
||||
MinSize: int(minSize),
|
||||
MaxSize: min(width, height),
|
||||
ShiftFactor: 0.1,
|
||||
ScaleFactor: 1.1,
|
||||
|
||||
@@ -85,13 +101,24 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) error {
|
||||
Dim: width,
|
||||
},
|
||||
}
|
||||
|
||||
if p.vRes {
|
||||
p.FaceAngle = 0.5
|
||||
}
|
||||
// Run the classifier over the obtained leaf nodes and return the detection results.
|
||||
// The result contains quadruplets representing the row, column, scale and detection score.
|
||||
faces := p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
|
||||
|
||||
// Calculate the intersection over union (IoU) of two clusters.
|
||||
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
|
||||
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.1)
|
||||
|
||||
if len(faces) == 0 {
|
||||
// Retry detecting faces for a certain amount of time.
|
||||
if faceDetAttempts < maxFaceDetAttempts {
|
||||
faceDetAttempts++
|
||||
}
|
||||
} else {
|
||||
faceDetAttempts = 0
|
||||
}
|
||||
|
||||
// Range over all the detected faces and draw a white rectangle mask over each of them.
|
||||
// We need to trick the sobel detector to consider them as important image parts.
|
||||
@@ -236,7 +263,7 @@ func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGB
|
||||
y := seam.Y
|
||||
for x := 0; x < bounds.Max.X; x++ {
|
||||
if seam.X == x {
|
||||
if debug == true {
|
||||
if debug {
|
||||
dst.Set(x, y, color.RGBA{255, 0, 0, 255})
|
||||
continue
|
||||
}
|
||||
|
||||
+8
-5
@@ -62,8 +62,9 @@ var (
|
||||
percentage = flag.Bool("perc", false, "Reduce image by percentage")
|
||||
square = flag.Bool("square", false, "Reduce image to square dimensions")
|
||||
debug = flag.Bool("debug", false, "Use debugger")
|
||||
preview = flag.Bool("preview", true, "Show preview window")
|
||||
faceDetect = flag.Bool("face", false, "Use face detection")
|
||||
faceAngle = flag.Float64("angle", 0.0, "Plane rotated faces angle")
|
||||
faceAngle = flag.Float64("angle", 0.0, "Face rotation angle")
|
||||
workers = flag.Int("conc", runtime.NumCPU(), "Number of files to process concurrently")
|
||||
|
||||
// Common file related variable
|
||||
@@ -88,6 +89,7 @@ func main() {
|
||||
Percentage: *percentage,
|
||||
Square: *square,
|
||||
Debug: *debug,
|
||||
Preview: *preview,
|
||||
FaceDetect: *faceDetect,
|
||||
FaceAngle: *faceAngle,
|
||||
}
|
||||
@@ -155,6 +157,7 @@ func main() {
|
||||
)
|
||||
}
|
||||
}
|
||||
proc.Preview = false
|
||||
|
||||
// Limit the concurrently running workers to maxWorkers.
|
||||
if *workers <= 0 || *workers > maxWorkers {
|
||||
@@ -231,17 +234,17 @@ func walkDir(
|
||||
// Close the paths channel after Walk returns.
|
||||
defer close(pathChan)
|
||||
|
||||
errChan <- filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
errChan <- filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
|
||||
isFileSupported := false
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
if !f.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the file base name.
|
||||
fx := filepath.Ext(info.Name())
|
||||
fx := filepath.Ext(f.Name())
|
||||
for _, ext := range srcExts {
|
||||
if ext == fx {
|
||||
isFileSupported = true
|
||||
@@ -402,7 +405,7 @@ func printStatus(fname string, err error) {
|
||||
)
|
||||
} else {
|
||||
if fname != pipeName {
|
||||
fmt.Fprintf(os.Stderr, fmt.Sprintf("\nThe new image has been saved as: %s %s\n\n",
|
||||
fmt.Fprintf(os.Stderr, fmt.Sprintf("\nThe resized image has been saved as: %s %s\n\n",
|
||||
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
|
||||
utils.DefaultColor,
|
||||
))
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// Grayscale converts the image to grayscale mode.
|
||||
func (c *Carver) Grayscale(src *image.NRGBA) *image.NRGBA {
|
||||
func (p *Processor) Grayscale(src *image.NRGBA) *image.NRGBA {
|
||||
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(src.Bounds())
|
||||
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/io/system"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxScreenX = 1366
|
||||
MaxScreenY = 768
|
||||
)
|
||||
|
||||
// showPreview spawn a new Gio GUI window and updates its content
|
||||
// with the resized image recived from a channel.
|
||||
func (p *Processor) showPreview(
|
||||
workerChan <-chan worker,
|
||||
errChan chan<- error,
|
||||
guiParams struct {
|
||||
width int
|
||||
height int
|
||||
},
|
||||
) {
|
||||
width, height := guiParams.width, guiParams.height
|
||||
newWidth, newHeight := float64(width), float64(height)
|
||||
|
||||
// Resize the image but retain the aspect ratio in case the
|
||||
// image width and height is greater than the predefined window.
|
||||
if width > MaxScreenX && height > MaxScreenY {
|
||||
widthRatio := float64(MaxScreenX) / float64(width)
|
||||
heightRatio := float64(MaxScreenY) / float64(height)
|
||||
ratio := math.Min(widthRatio, heightRatio)
|
||||
|
||||
newWidth = float64(width) * ratio
|
||||
newHeight = float64(height) * ratio
|
||||
}
|
||||
|
||||
// Create a new window.
|
||||
w := app.NewWindow(
|
||||
app.Title("Image resize in progress..."),
|
||||
app.Size(unit.Px(float32(newWidth)), unit.Px(float32(newHeight))),
|
||||
)
|
||||
|
||||
for err := range p.run(w, workerChan) {
|
||||
errChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
// run the Gio main thread until a DestroyEvent or an ESC key event is captured.
|
||||
func (p *Processor) run(w *app.Window, workerChan <-chan worker) chan error {
|
||||
var (
|
||||
ops op.Ops
|
||||
img image.Image
|
||||
)
|
||||
err := make(chan error)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-w.Events():
|
||||
switch e := e.(type) {
|
||||
case system.FrameEvent:
|
||||
gtx := layout.NewContext(&ops, e)
|
||||
w.Invalidate()
|
||||
|
||||
if img != nil {
|
||||
src := paint.NewImageOp(img)
|
||||
src.Add(gtx.Ops)
|
||||
|
||||
imgWidget := widget.Image{
|
||||
Src: src,
|
||||
Scale: 1 / float32(gtx.Px(unit.Dp(1))),
|
||||
Fit: widget.Contain,
|
||||
}
|
||||
imgWidget.Layout(gtx)
|
||||
}
|
||||
e.Frame(gtx.Ops)
|
||||
case key.Event:
|
||||
switch e.Name {
|
||||
case key.NameEscape:
|
||||
w.Close()
|
||||
}
|
||||
case system.DestroyEvent:
|
||||
err <- e.Err
|
||||
break
|
||||
}
|
||||
case worker := <-workerChan:
|
||||
img = worker.img
|
||||
if p.vRes {
|
||||
img = worker.carver.RotateImage270(img.(*image.NRGBA))
|
||||
}
|
||||
w.Invalidate()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return err
|
||||
}
|
||||
+144
-52
@@ -2,6 +2,7 @@ package caire
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
pigo "github.com/esimov/pigo/core"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
@@ -26,12 +26,22 @@ var classifier embed.FS
|
||||
|
||||
var (
|
||||
g *gif.GIF
|
||||
xCount int
|
||||
yCount int
|
||||
resizeBothSide = false // used to tell that the image is resized both verticlaly and horizontally
|
||||
rCount int
|
||||
resizeBothSide = false // the image is resized both verticlaly and horizontally
|
||||
isGif = false
|
||||
)
|
||||
|
||||
var (
|
||||
imgWorker = make(chan worker) // channel used to transfer the image to the GUI
|
||||
errs = make(chan error)
|
||||
)
|
||||
|
||||
// worker struct contains all the information needed for transfering the resized image to the Gio GUI.
|
||||
type worker struct {
|
||||
carver *Carver
|
||||
img *image.NRGBA
|
||||
}
|
||||
|
||||
// SeamCarver interface defines the Resize method.
|
||||
// This needs to be implemented by every struct which declares a Resize method.
|
||||
type SeamCarver interface {
|
||||
@@ -53,9 +63,12 @@ type Processor struct {
|
||||
Percentage bool
|
||||
Square bool
|
||||
Debug bool
|
||||
Preview bool
|
||||
FaceDetect bool
|
||||
FaceAngle float64
|
||||
PigoFaceDetector *pigo.Pigo
|
||||
|
||||
vRes bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -83,7 +96,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
pw, ph int
|
||||
err error
|
||||
)
|
||||
xCount, yCount = 0, 0
|
||||
rCount = 0
|
||||
|
||||
if p.NewWidth > c.Width {
|
||||
newWidth = p.NewWidth - (p.NewWidth - (p.NewWidth - c.Width))
|
||||
@@ -104,11 +117,11 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
newHeight = p.NewHeight
|
||||
}
|
||||
|
||||
// shrinkHorizFn calls itself iteratively and shrink the image horizontally.
|
||||
// shrinkHorizFn calls itself recursively to shrink the image horizontally.
|
||||
// If the image is resized on both X and Y axis it calls the shrink and enlarge
|
||||
// function intermitently up until the desired dimension is reached.
|
||||
// We are opting for this solution instead of resizing the image secventially,
|
||||
// because we can merge more seamlessly together the horizontal and vertical seams.
|
||||
// because this way the horizontal and vertical seams are merged together seamlessly.
|
||||
shrinkHorizFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
if dx > p.NewWidth {
|
||||
@@ -126,11 +139,11 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
img, _ = shrinkHorizFn(c, img)
|
||||
}
|
||||
}
|
||||
xCount++
|
||||
rCount++
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// enlargeHorizFn calls itself iteratively and enlarge the image horizontally.
|
||||
// enlargeHorizFn calls itself recursively to enlarge the image horizontally.
|
||||
enlargeHorizFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
if dx < p.NewWidth {
|
||||
@@ -148,20 +161,23 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
img, _ = enlargeHorizFn(c, img)
|
||||
}
|
||||
}
|
||||
rCount++
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// shrinkVertFn calls itself iteratively and shrink the image vertically.
|
||||
// shrinkVertFn calls itself recursively to shrink the image vertically.
|
||||
shrinkVertFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
p.vRes = true
|
||||
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
// If the image is resized on both side we need to rotate the image
|
||||
// each time we are invoking the shrink function.
|
||||
// Otherwise if we are resizing the image on one side only we can invoke
|
||||
// the rotating function only once, right before calling this function.
|
||||
|
||||
// If the image is resized both horizontally and vertically we need
|
||||
// to rotate the image each time we are invoking the shrink function.
|
||||
// Otherwise we rotate the image only once, right before calling this function.
|
||||
if resizeBothSide {
|
||||
dx, dy = img.Bounds().Dy(), img.Bounds().Dx()
|
||||
img = c.RotateImage90(img)
|
||||
}
|
||||
if dy > p.NewHeight {
|
||||
if dx > p.NewHeight {
|
||||
img, err = p.shrink(c, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -169,8 +185,8 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
if resizeBothSide {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
if p.NewWidth > 0 && p.NewWidth != dx {
|
||||
if p.NewWidth <= dx {
|
||||
if p.NewWidth > 0 && p.NewWidth != dy {
|
||||
if p.NewWidth <= dy {
|
||||
img, _ = shrinkHorizFn(c, img)
|
||||
} else {
|
||||
img, _ = enlargeHorizFn(c, img)
|
||||
@@ -183,17 +199,20 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
}
|
||||
yCount++
|
||||
rCount++
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// shrinkVertFn calls itself iteratively and enlarge the image vertically.
|
||||
// enlargeVertFn calls itself recursively to enlarge the image vertically.
|
||||
enlargeVertFn = func(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
p.vRes = true
|
||||
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
|
||||
if resizeBothSide {
|
||||
dx, dy = img.Bounds().Dy(), img.Bounds().Dx()
|
||||
img = c.RotateImage90(img)
|
||||
}
|
||||
if dy < p.NewHeight {
|
||||
if dx < p.NewHeight {
|
||||
img, err = p.enlarge(c, img)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -201,8 +220,8 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
if resizeBothSide {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
if p.NewWidth > 0 && p.NewWidth != dx {
|
||||
if p.NewWidth <= dx {
|
||||
if p.NewWidth > 0 && p.NewWidth != dy {
|
||||
if p.NewWidth <= dy {
|
||||
img, _ = shrinkHorizFn(c, img)
|
||||
} else {
|
||||
img, _ = enlargeHorizFn(c, img)
|
||||
@@ -215,6 +234,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
}
|
||||
rCount++
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -223,16 +243,23 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
}
|
||||
|
||||
if p.Percentage || p.Square {
|
||||
// When square option is used the image will be resized to a square based on the shortest edge.
|
||||
pw = c.Width - c.Height
|
||||
ph = c.Height - c.Width
|
||||
|
||||
// In case pw and ph is zero, it means that the target image is square.
|
||||
// In this case we can simply resize the image without running the carving operation.
|
||||
if pw == 0 && ph == 0 {
|
||||
return imaging.Resize(img, p.NewWidth, p.NewHeight, imaging.Lanczos), nil
|
||||
if p.Percentage && pw == 0 && ph == 0 {
|
||||
pw = c.Width - int(float64(c.Width)-(float64(p.NewWidth)/100*float64(c.Width)))
|
||||
ph = c.Height - int(float64(c.Height)-(float64(p.NewHeight)/100*float64(c.Height)))
|
||||
|
||||
p.NewWidth = absint(c.Width - pw)
|
||||
p.NewHeight = absint(c.Height - ph)
|
||||
|
||||
resImgSize := min(p.NewWidth, p.NewHeight)
|
||||
return imaging.Resize(img, resImgSize, 0, imaging.Lanczos), nil
|
||||
}
|
||||
|
||||
// When the square option is used the image will be resized to a square based on the shortest edge.
|
||||
if p.Square {
|
||||
// Calling the image rescale method only when both a new width and height is provided.
|
||||
if p.NewWidth != 0 && p.NewHeight != 0 {
|
||||
@@ -268,11 +295,14 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
pw = c.Width - int(float64(c.Width)-(float64(p.NewWidth)/100*float64(c.Width)))
|
||||
ph = c.Height - int(float64(c.Height)-(float64(p.NewHeight)/100*float64(c.Height)))
|
||||
|
||||
p.NewWidth = absint(c.Width - pw)
|
||||
p.NewHeight = absint(c.Height - ph)
|
||||
|
||||
if pw > newWidth || ph > newHeight {
|
||||
return nil, errors.New("the generated image size should be less than the original image size")
|
||||
if p.NewWidth != 0 {
|
||||
p.NewWidth = absint(c.Width - pw)
|
||||
}
|
||||
if p.NewHeight != 0 {
|
||||
p.NewHeight = absint(c.Height - ph)
|
||||
}
|
||||
if pw >= c.Width || ph >= c.Height {
|
||||
return nil, errors.New("cannot use the percentage flag for image enlargement")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,22 +342,16 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
|
||||
|
||||
// Run the carver function if the desired image height is not identical with the rescaled image height.
|
||||
if newHeight > 0 && p.NewHeight != c.Height {
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage90(img)
|
||||
}
|
||||
if p.NewHeight > c.Height {
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage90(img)
|
||||
}
|
||||
img, _ = enlargeVertFn(c, img)
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
} else {
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage90(img)
|
||||
}
|
||||
img, _ = shrinkVertFn(c, img)
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
}
|
||||
if !resizeBothSide {
|
||||
img = c.RotateImage270(img)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,13 +409,34 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
g = new(gif.GIF)
|
||||
src, _, err := image.Decode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img := p.imgToNRGBA(src)
|
||||
|
||||
if p.Preview {
|
||||
guiWidth := img.Bounds().Max.X
|
||||
guiHeight := img.Bounds().Max.Y
|
||||
|
||||
if p.NewWidth > guiWidth {
|
||||
guiWidth = p.NewWidth
|
||||
}
|
||||
if p.NewHeight > guiHeight {
|
||||
guiHeight = p.NewHeight
|
||||
}
|
||||
|
||||
guiParams := struct {
|
||||
width int
|
||||
height int
|
||||
}{
|
||||
width: guiWidth,
|
||||
height: guiHeight,
|
||||
}
|
||||
// Lunch Gio GUI thread.
|
||||
go p.showPreview(imgWorker, errs, guiParams)
|
||||
}
|
||||
|
||||
switch w.(type) {
|
||||
case *os.File:
|
||||
ext := filepath.Ext(w.(*os.File).Name())
|
||||
@@ -415,12 +460,13 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
|
||||
}
|
||||
return bmp.Encode(w, res)
|
||||
case ".gif":
|
||||
g = new(gif.GIF)
|
||||
isGif = true
|
||||
_, err := Resize(p, img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeGifToFile(w.(*os.File).Name())
|
||||
return writeGifToFile(w.(*os.File).Name(), g)
|
||||
default:
|
||||
return errors.New("unsupported image format")
|
||||
}
|
||||
@@ -445,8 +491,19 @@ func (p *Processor) shrink(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
img = c.RemoveSeam(img, seams, p.Debug)
|
||||
|
||||
if isGif {
|
||||
g = encodeImageToGif(img)
|
||||
p.encodeImgToGif(c, img, g)
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case imgWorker <- worker{
|
||||
carver: c,
|
||||
img: img,
|
||||
}:
|
||||
case <-errs:
|
||||
return
|
||||
}
|
||||
}()
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -460,6 +517,21 @@ func (p *Processor) enlarge(c *Carver, img *image.NRGBA) (*image.NRGBA, error) {
|
||||
seams := c.FindLowestEnergySeams()
|
||||
img = c.AddSeam(img, seams, p.Debug)
|
||||
|
||||
if isGif {
|
||||
p.encodeImgToGif(c, img, g)
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case imgWorker <- worker{
|
||||
carver: c,
|
||||
img: img,
|
||||
}:
|
||||
case <-errs:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -521,19 +593,39 @@ func (p *Processor) imgToNRGBA(img image.Image) *image.NRGBA {
|
||||
return dst
|
||||
}
|
||||
|
||||
// encodeImageToGif encodes the provided image to a Gif file.
|
||||
func encodeImageToGif(src image.Image) *gif.GIF {
|
||||
bounds := src.Bounds()
|
||||
dst := image.NewPaletted(image.Rect(0, 0, bounds.Dx()-xCount, bounds.Dy()-yCount), palette.Plan9)
|
||||
// encodeImgToGif encodes the provided image to a Gif file.
|
||||
func (p *Processor) encodeImgToGif(c *Carver, src image.Image, g *gif.GIF) {
|
||||
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y
|
||||
dst := image.NewPaletted(image.Rect(0, 0, dx, dy), palette.Plan9)
|
||||
if p.NewHeight != 0 {
|
||||
dst = image.NewPaletted(image.Rect(0, 0, dy, dx), palette.Plan9)
|
||||
}
|
||||
|
||||
if p.NewWidth > dx {
|
||||
dx += rCount
|
||||
g.Config.Width = dst.Bounds().Max.X + 1
|
||||
g.Config.Height = dst.Bounds().Max.Y + 1
|
||||
} else {
|
||||
dx -= rCount
|
||||
}
|
||||
if p.NewHeight > dx {
|
||||
dx += rCount
|
||||
g.Config.Width = dst.Bounds().Max.X + 1
|
||||
g.Config.Height = dst.Bounds().Max.Y + 1
|
||||
} else {
|
||||
dx -= rCount
|
||||
}
|
||||
|
||||
if p.NewHeight != 0 {
|
||||
src = c.RotateImage270(src.(*image.NRGBA))
|
||||
}
|
||||
draw.Draw(dst, src.Bounds(), src, image.Point{}, draw.Src)
|
||||
g.Image = append(g.Image, dst)
|
||||
g.Delay = append(g.Delay, 0)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// writeGifToFile writes the encoded Gif file to the destination file.
|
||||
func writeGifToFile(path string) error {
|
||||
func writeGifToFile(path string, g *gif.GIF) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user