Code refactoring and improvements

This commit is contained in:
esimov
2024-04-24 14:31:06 +03:00
parent d8bf889688
commit bdceadba96
9 changed files with 446 additions and 405 deletions

View File

@@ -77,7 +77,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
dets := []pigo.Detection{}
if p.PigoFaceDetector != nil && p.FaceDetect && detAttempts < maxFaceDetAttempts {
if p.FaceDetector != nil && p.FaceDetect && detAttempts < maxFaceDetAttempts {
var ratio float64
if width < height {
@@ -108,10 +108,10 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) (*image.NRGBA, err
}
// 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.
dets = p.PigoFaceDetector.RunCascade(cParams, p.FaceAngle)
dets = p.FaceDetector.RunCascade(cParams, p.FaceAngle)
// Calculate the intersection over union (IoU) of two clusters.
dets = p.PigoFaceDetector.ClusterDetections(dets, 0.1)
dets = p.FaceDetector.ClusterDetections(dets, 0.1)
if len(dets) == 0 {
// Retry detecting faces for a certain amount of time.

View File

@@ -250,7 +250,7 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
}
defer f.Close()
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
if err != nil {
t.Fatalf("error unpacking the cascade file: %v", err)
}
@@ -282,10 +282,10 @@ func TestCarver_ShouldDetectFace(t *testing.T) {
// 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)
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
// Calculate the intersection over union (IoU) of two clusters.
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
assert.Equal(1, len(faces))
}
@@ -301,7 +301,7 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
}
defer f.Close()
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
if err != nil {
t.Fatalf("error unpacking the cascade file: %v", err)
}
@@ -336,10 +336,10 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) {
// 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)
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
// Calculate the intersection over union (IoU) of two clusters.
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
// 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.
@@ -378,7 +378,7 @@ func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
}
defer f.Close()
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
if err != nil {
t.Fatalf("error unpacking the cascade file: %v", err)
}
@@ -409,10 +409,10 @@ func TestCarver_ShouldNotResizeWithFaceDistorsion(t *testing.T) {
// 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)
faces := p.FaceDetector.RunCascade(cParams, p.FaceAngle)
// Calculate the intersection over union (IoU) of two clusters.
faces = p.PigoFaceDetector.ClusterDetections(faces, 0.2)
faces = p.FaceDetector.ClusterDetections(faces, 0.2)
for _, face := range faces {
if p.NewHeight < face.Scale {

View File

@@ -1,23 +1,15 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"sync"
"syscall"
"time"
"gioui.org/app"
"github.com/esimov/caire"
"github.com/esimov/caire/utils"
"golang.org/x/term"
)
const HelpBanner = `
@@ -33,22 +25,6 @@ Content aware image resize library.
// pipeName indicates that stdin/stdout is being used as file names.
const pipeName = "-"
// maxWorkers sets the maximum number of concurrently running workers.
const maxWorkers = 20
// result holds the relevant information about the resizing process and the generated image.
type result struct {
path string
err error
}
var (
// imgfile holds the file being accessed, be it normal file or pipe name.
imgfile *os.File
// spinner is used to instantiate and call the progress indicator.
spinner *utils.Spinner
)
// Version indicates the current build version.
var Version string
@@ -71,13 +47,11 @@ var (
faceDetect = flag.Bool("face", false, "Use face detection")
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
fs os.FileInfo
)
func main() {
log.SetFlags(0)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, fmt.Sprintf(HelpBanner, Version))
flag.PrintDefaults()
@@ -101,13 +75,6 @@ func main() {
SeamColor: *seamColor,
}
defaultMsg := fmt.Sprintf("%s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("⇢ image resizing in progress (be patient, it may take a while)...", utils.DefaultMessage),
)
spinner = utils.NewSpinner(defaultMsg, time.Millisecond*80)
if !(*newWidth > 0 || *newHeight > 0 || *percentage || *square) {
flag.Usage()
log.Fatal(fmt.Sprintf("%s%s",
@@ -115,335 +82,21 @@ func main() {
utils.DefaultColor,
))
} else {
op := &caire.Ops{
Src: *source,
Dst: *destination,
Workers: *workers,
PipeName: pipeName,
}
if *preview {
// When the preview mode is activated we need to execute the resizing process
// When the preview mode is activated we have to execute the resizing process
// in a separate goroutine in order to not block the Gio thread,
// which needs to be run on the main OS thread on operating systems like MacOS.
go execute(proc)
// which have to run on the main OS thread of the operating systems like MacOS.
go proc.Execute(op)
app.Main()
} else {
execute(proc)
proc.Execute(op)
}
}
}
// execute executes the image resizing process.
// In case the preview mode is activated it will be invoked in a separate goroutine
// in order to not block the main OS thread. Otherwise it will be called normally.
func execute(proc *caire.Processor) {
var err error
proc.Spinner = spinner
// Supported files
validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"}
// Check if source path is a local image or URL.
if utils.IsValidUrl(*source) {
src, err := utils.DownloadImage(*source)
if src != nil {
defer os.Remove(src.Name())
}
defer src.Close()
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
fs, err = src.Stat()
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
img, err := os.Open(src.Name())
if err != nil {
log.Fatalf(
utils.DecorateText("Unable to open the temporary image file: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
imgfile = img
} else {
// Check if the source is a pipe name or a regular file.
if *source == pipeName {
fs, err = os.Stdin.Stat()
} else {
fs, err = os.Stat(*source)
}
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
}
now := time.Now()
switch mode := fs.Mode(); {
case mode.IsDir():
var wg sync.WaitGroup
// Read destination file or directory.
_, err := os.Stat(*destination)
if err != nil {
err = os.Mkdir(*destination, 0755)
if err != nil {
log.Fatalf(
utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
}
proc.Preview = false
// Limit the concurrently running workers to maxWorkers.
if *workers <= 0 || *workers > maxWorkers {
*workers = runtime.NumCPU()
}
// Process recursively the image files from the specified directory concurrently.
ch := make(chan result)
done := make(chan interface{})
defer close(done)
paths, errc := walkDir(done, *source, validExtensions)
wg.Add(*workers)
for i := 0; i < *workers; i++ {
go func() {
defer wg.Done()
consumer(done, paths, *destination, proc, ch)
}()
}
// Close the channel after the values are consumed.
go func() {
defer close(ch)
wg.Wait()
}()
// Consume the channel values.
for res := range ch {
if res.err != nil {
err = res.err
}
printStatus(res.path, err)
}
if err = <-errc; err != nil {
fmt.Fprintf(os.Stderr, utils.DecorateText(err.Error(), utils.ErrorMessage))
}
case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names
ext := filepath.Ext(*destination)
if !isValidExtension(ext, validExtensions) && *destination != pipeName {
log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage))
}
err = processor(*source, *destination, proc)
printStatus(*destination, err)
}
if err == nil {
fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(fmt.Sprintf("%s", utils.FormatTime(time.Since(now))), utils.SuccessMessage))
}
}
// walkDir starts a goroutine to walk the specified directory tree in recursive manner
// and send the path of each regular file on the string channel.
// It terminates in case done channel is closed.
func walkDir(
done <-chan interface{},
src string,
srcExts []string,
) (<-chan string, <-chan error) {
pathChan := make(chan string)
errChan := make(chan error, 1)
go func() {
// Close the paths channel after Walk returns.
defer close(pathChan)
errChan <- filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
isFileSupported := false
if err != nil {
return err
}
if !f.Mode().IsRegular() {
return nil
}
// Get the file base name.
fx := filepath.Ext(f.Name())
for _, ext := range srcExts {
if ext == fx {
isFileSupported = true
break
}
}
if isFileSupported {
select {
case <-done:
return errors.New("directory walk cancelled")
case pathChan <- path:
}
}
return nil
})
}()
return pathChan, errChan
}
// consumer reads the path names from the paths channel and calls the resizing processor against the source image.
func consumer(
done <-chan interface{},
paths <-chan string,
dest string,
proc *caire.Processor,
res chan<- result,
) {
for src := range paths {
dst := filepath.Join(dest, filepath.Base(src))
err := processor(src, dst, proc)
select {
case <-done:
return
case res <- result{
path: src,
err: err,
}:
}
}
}
// processor calls the resizer method over the source image and returns the error in case exists.
func processor(in, out string, proc *caire.Processor) error {
var (
successMsg string
errorMsg string
)
// Start the progress indicator.
spinner.Start()
successMsg = fmt.Sprintf("%s %s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("⇢", utils.DefaultMessage),
utils.DecorateText("the image has been resized successfully ✔", utils.SuccessMessage),
)
errorMsg = fmt.Sprintf("%s %s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("resizing image failed...", utils.DefaultMessage),
utils.DecorateText("✘", utils.ErrorMessage),
)
src, dst, err := pathToFile(in, out)
if err != nil {
spinner.StopMsg = errorMsg
return err
}
// Capture CTRL-C signal and restores back the cursor visibility.
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalChan
func() {
spinner.RestoreCursor()
os.Remove(dst.(*os.File).Name())
os.Exit(1)
}()
}()
defer src.(*os.File).Close()
defer dst.(*os.File).Close()
err = proc.Process(src, dst)
if err != nil {
// remove the generated image file in case of an error
os.Remove(dst.(*os.File).Name())
spinner.StopMsg = errorMsg
// Stop the progress indicator.
spinner.Stop()
return err
} else {
spinner.StopMsg = successMsg
// Stop the progress indicator.
spinner.Stop()
}
return nil
}
// pathToFile converts the source and destination paths to readable and writable files.
func pathToFile(in, out string) (io.Reader, io.Writer, error) {
var (
src io.Reader
dst io.Writer
err error
)
// Check if the source path is a local image or URL.
if utils.IsValidUrl(in) {
src = imgfile
} else {
// Check if the source is a pipe name or a regular file.
if in == pipeName {
if term.IsTerminal(int(os.Stdin.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdin")
}
src = os.Stdin
} else {
src, err = os.Open(in)
if err != nil {
return nil, nil, fmt.Errorf("unable to open the source file: %v", err)
}
}
}
// Check if the destination is a pipe name or a regular file.
if out == pipeName {
if term.IsTerminal(int(os.Stdout.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdout")
}
dst = os.Stdout
} else {
dst, err = os.OpenFile(out, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
return nil, nil, fmt.Errorf("unable to create the destination file: %v", err)
}
}
return src, dst, nil
}
// printStatus displays the relavant information about the image resizing process.
func printStatus(fname string, err error) {
if err != nil {
fmt.Fprintf(os.Stderr,
utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage),
utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage),
)
os.Exit(0)
} else {
if fname != pipeName {
fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n",
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
utils.DefaultColor,
)
}
}
}
// isValidExtension checks for the supported extensions.
func isValidExtension(ext string, extensions []string) bool {
for _, ex := range extensions {
if ex == ext {
return true
}
}
return false
}

378
exec.go Normal file
View File

@@ -0,0 +1,378 @@
package caire
import (
"errors"
"fmt"
"io"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"sync"
"syscall"
"time"
"github.com/esimov/caire/utils"
"golang.org/x/term"
)
// maxWorkers sets the maximum number of concurrently running workers.
const maxWorkers = 20
var (
// imgFile holds the file being accessed, be it normal file or pipe name.
imgFile *os.File
// Common file related variable
fs os.FileInfo
)
type Ops struct {
Src, Dst, PipeName string
Workers int
}
// result holds the relevant information about the resizing process and the generated image.
type result struct {
path string
err error
}
// Execute executes the image resizing process.
// In case the preview mode is activated it will be invoked in a separate goroutine
// in order to not block the main OS thread. Otherwise it will be called normally.
func (p *Processor) Execute(op *Ops) {
var err error
defaultMsg := fmt.Sprintf("%s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("⇢ resizing image (be patient, it may take a while)...", utils.DefaultMessage),
)
p.Spinner = utils.NewSpinner(defaultMsg, time.Millisecond*80)
// Supported files
validExtensions := []string{".jpg", ".png", ".jpeg", ".bmp", ".gif"}
// Check if source path is a local image or URL.
if utils.IsValidUrl(op.Src) {
src, err := utils.DownloadImage(op.Src)
if src != nil {
defer os.Remove(src.Name())
}
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
fs, err = src.Stat()
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
img, err := os.Open(src.Name())
if err != nil {
log.Fatalf(
utils.DecorateText("Unable to open the temporary image file: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
imgFile = img
} else {
// Check if the source is a pipe name or a regular file.
if op.Src == op.PipeName {
fs, err = os.Stdin.Stat()
} else {
fs, err = os.Stat(op.Src)
}
if err != nil {
log.Fatalf(
utils.DecorateText("Failed to load the source image: %v", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
}
now := time.Now()
switch mode := fs.Mode(); {
case mode.IsDir():
var wg sync.WaitGroup
// Read destination file or directory.
_, err := os.Stat(op.Dst)
if err != nil {
err = os.Mkdir(op.Dst, 0755)
if err != nil {
log.Fatalf(
utils.DecorateText("Unable to get dir stats: %v\n", utils.ErrorMessage),
utils.DecorateText(err.Error(), utils.DefaultMessage),
)
}
}
p.Preview = false
// Limit the concurrently running workers to maxWorkers.
if op.Workers <= 0 || op.Workers > maxWorkers {
op.Workers = runtime.NumCPU()
}
// Process recursively the image files from the specified directory concurrently.
ch := make(chan result)
done := make(chan interface{})
defer close(done)
paths, errc := walkDir(done, op.Src, validExtensions)
wg.Add(op.Workers)
for i := 0; i < op.Workers; i++ {
go func() {
defer wg.Done()
op.consumer(p, op.Dst, ch, done, paths)
}()
}
// Close the channel after the values are consumed.
go func() {
defer close(ch)
wg.Wait()
}()
// Consume the channel values.
for res := range ch {
if res.err != nil {
err = res.err
}
op.printOpStatus(res.path, err)
}
if err = <-errc; err != nil {
fmt.Fprintf(os.Stderr, utils.DecorateText(err.Error(), utils.ErrorMessage))
}
case mode.IsRegular() || mode&os.ModeNamedPipe != 0: // check for regular files or pipe names
ext := filepath.Ext(op.Dst)
if !isValidExtension(ext, validExtensions) && op.Dst != op.PipeName {
log.Fatalf(utils.DecorateText(fmt.Sprintf("%v file type not supported", ext), utils.ErrorMessage))
}
err = op.process(p, op.Src, op.Dst)
op.printOpStatus(op.Dst, err)
}
if err == nil {
fmt.Fprintf(os.Stderr, "\nExecution time: %s\n", utils.DecorateText(fmt.Sprintf("%s", utils.FormatTime(time.Since(now))), utils.SuccessMessage))
}
}
// consumer reads the path names from the paths channel and calls the resizing processor against the source image.
func (op *Ops) consumer(
p *Processor,
dest string,
res chan<- result,
done <-chan interface{},
paths <-chan string,
) {
for src := range paths {
dst := filepath.Join(dest, filepath.Base(src))
err := op.process(p, src, dst)
select {
case <-done:
return
case res <- result{
path: src,
err: err,
}:
}
}
}
// processor calls the resizer method over the source image and returns the error in case exists.
func (op *Ops) process(p *Processor, in, out string) error {
var (
successMsg string
errorMsg string
)
// Start the progress indicator.
p.Spinner.Start()
successMsg = fmt.Sprintf("%s %s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("⇢", utils.DefaultMessage),
utils.DecorateText("the image has been resized successfully ✔", utils.SuccessMessage),
)
errorMsg = fmt.Sprintf("%s %s %s",
utils.DecorateText("⚡ CAIRE", utils.StatusMessage),
utils.DecorateText("resizing image failed...", utils.DefaultMessage),
utils.DecorateText("✘", utils.ErrorMessage),
)
src, dst, err := op.pathToFile(in, out)
if err != nil {
p.Spinner.StopMsg = errorMsg
return err
}
// Capture CTRL-C signal and restores back the cursor visibility.
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalChan
func() {
p.Spinner.RestoreCursor()
os.Remove(dst.(*os.File).Name())
os.Exit(1)
}()
}()
defer func() {
if img, ok := src.(*os.File); ok {
if err := img.Close(); err != nil {
log.Printf("could not close the opened file: %v", err)
}
}
}()
defer func() {
if img, ok := dst.(*os.File); ok {
if err := img.Close(); err != nil {
log.Printf("could not close the opened file: %v", err)
}
}
}()
err = p.Process(src, dst)
if err != nil {
// remove the generated image file in case of an error
os.Remove(dst.(*os.File).Name())
p.Spinner.StopMsg = errorMsg
// Stop the progress indicator.
p.Spinner.Stop()
return err
} else {
p.Spinner.StopMsg = successMsg
// Stop the progress indicator.
p.Spinner.Stop()
}
return nil
}
// pathToFile converts the source and destination paths to readable and writable files.
func (op *Ops) pathToFile(in, out string) (io.Reader, io.Writer, error) {
var (
src io.Reader
dst io.Writer
err error
)
// Check if the source path is a local image or URL.
if utils.IsValidUrl(in) {
src = imgFile
} else {
// Check if the source is a pipe name or a regular file.
if in == op.PipeName {
if term.IsTerminal(int(os.Stdin.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdin")
}
src = os.Stdin
} else {
src, err = os.Open(in)
if err != nil {
return nil, nil, fmt.Errorf("unable to open the source file: %v", err)
}
}
}
// Check if the destination is a pipe name or a regular file.
if out == op.PipeName {
if term.IsTerminal(int(os.Stdout.Fd())) {
return nil, nil, errors.New("`-` should be used with a pipe for stdout")
}
dst = os.Stdout
} else {
dst, err = os.OpenFile(out, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
return nil, nil, fmt.Errorf("unable to create the destination file: %v", err)
}
}
return src, dst, nil
}
// printOpStatus displays the relevant information about the image resizing process.
func (op *Ops) printOpStatus(fname string, err error) {
if err != nil {
log.Fatalf(
utils.DecorateText("\nError resizing the image: %s", utils.ErrorMessage),
utils.DecorateText(fmt.Sprintf("\n\tReason: %v\n", err.Error()), utils.DefaultMessage),
)
} else {
if fname != op.PipeName {
fmt.Fprintf(os.Stderr, "\nThe image has been saved as: %s %s\n\n",
utils.DecorateText(filepath.Base(fname), utils.SuccessMessage),
utils.DefaultColor,
)
}
}
}
// walkDir starts a new goroutine to walk the specified directory tree
// in recursive manner and sends the path of each regular file to a new channel.
// It finishes in case the done channel is getting closed.
func walkDir(
done <-chan interface{},
src string,
srcExts []string,
) (<-chan string, <-chan error) {
pathChan := make(chan string)
errChan := make(chan error, 1)
go func() {
// Close the paths channel after Walk returns.
defer close(pathChan)
errChan <- filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
isFileSupported := false
if err != nil {
return err
}
if !f.Mode().IsRegular() {
return nil
}
// Get the file base name.
fx := filepath.Ext(f.Name())
for _, ext := range srcExts {
if ext == fx {
isFileSupported = true
break
}
}
if isFileSupported {
select {
case <-done:
return errors.New("directory walk cancelled")
case pathChan <- path:
}
}
return nil
})
}()
return pathChan, errChan
}
// isValidExtension checks for the supported extensions.
func isValidExtension(ext string, extensions []string) bool {
for _, ex := range extensions {
if ex == ext {
return true
}
}
return false
}

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/esimov/caire
go 1.19
go 1.22
require (
gioui.org v0.3.1

2
go.sum
View File

@@ -1,4 +1,5 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.3.1 h1:hslYkrkIWvx28Mxe3A87opl+8s9mnWsnWmPDh11+zco=
gioui.org v0.3.1/go.mod h1:2atiYR4upH71/6ehnh6XsUELa7JZOrOHHNMDxGBZF0Q=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
@@ -17,6 +18,7 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

View File

@@ -78,7 +78,7 @@ type Processor struct {
RMask *image.NRGBA
GuiDebug *image.NRGBA
FaceAngle float64
PigoFaceDetector *pigo.Pigo
FaceDetector *pigo.Pigo
Spinner *utils.Spinner
vRes bool
@@ -476,13 +476,13 @@ func (p *Processor) calculateFitness(img *image.NRGBA, c *Carver) *image.NRGBA {
func (p *Processor) Process(r io.Reader, w io.Writer) error {
var err error
// Instantiate a new Pigo object in case the face detection option is used.
p.PigoFaceDetector = pigo.NewPigo()
if p.FaceDetect {
// Instantiate a new Pigo object in case the face detection option is used.
p.FaceDetector = pigo.NewPigo()
// Unpack the binary file. This will return the number of cascade trees,
// the tree depth, the threshold and the prediction from tree's leaf nodes.
p.PigoFaceDetector, err = p.PigoFaceDetector.Unpack(cascadeFile)
p.FaceDetector, err = p.FaceDetector.Unpack(cascadeFile)
if err != nil {
return fmt.Errorf("error unpacking the cascade file: %v", err)
}
@@ -494,6 +494,8 @@ func (p *Processor) Process(r io.Reader, w io.Writer) error {
src, _, err := image.Decode(r)
if err != nil {
fmt.Println("err:", err)
os.Exit(2)
return err
}

View File

@@ -2,10 +2,9 @@ package utils
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
@@ -17,32 +16,35 @@ func DownloadImage(url string) (*os.File, error) {
// Retrieve the url and decode the response body.
res, err := http.Get(url)
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to download image file from URI: %s, status %v", url, res.Status))
return nil, fmt.Errorf("unable to download image file from URI: %s, status %v", url, res.Status)
}
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(res.Body)
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to read response body: %s", err))
return nil, fmt.Errorf("unable to read response body: %w", err)
}
tmpfile, err := ioutil.TempFile("/tmp", "image")
tmpfile, err := os.CreateTemp("/tmp", "image")
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to create temporary file: %v", err))
return nil, fmt.Errorf("unable to create temporary file: %w", err)
}
// Copy the image binary data into the temporary file.
_, err = io.Copy(tmpfile, bytes.NewBuffer(data))
if err != nil {
return nil, errors.New(fmt.Sprintf("unable to copy the source URI into the destination file"))
return nil, fmt.Errorf("unable to copy the source URI into the destination file")
}
ctype, err := DetectContentType(tmpfile.Name())
if err != nil {
return nil, err
}
if !strings.Contains(ctype.(string), "image") {
return nil, errors.New("the downloaded file is a valid image type.")
return nil, fmt.Errorf("the downloaded file is not a valid image type")
}
return tmpfile, nil
}
@@ -67,7 +69,11 @@ func DetectContentType(fname string) (interface{}, error) {
if err != nil {
return nil, err
}
defer file.Close()
defer func() {
if err := file.Close(); err != nil {
log.Printf("could not close the opened file: %v", err)
}
}()
// Only the first 512 bytes are used to sniff the content type.
buffer := make([]byte, 512)