Original post: fahmifan.id/blog/automate-resizing-bulk-images-using-libvips
With the growing popularity of ShopeeFood, many restaurants were applying for partnership with them, included my relative's Padang restaurant. But, their registration was not easy. They need a photo for each menu to be in a format of 720x720 px. For a Padang restaurant with over 50 menus and unfortunately the restaurant's menu photos were in different sizes.
I was too lazy to resized them manually. At first, I search for a service that could resize an image. But they weren't able to resize so many images at once. So I thought to write a program to automate this.
Enter the libvips, libvips was a software that was used for image manipulation. Since I used Go and libvips was written in C, I searched for a Go libvips package and found bimg.
The code was pretty simple, we only need to iterate all of the photos in a directory. Then, for each of photo, resize it into 720x720 px. But, because the photos are not all squares, we can't use the Resize
method instead, we use ResizeAndCrop
that will resize the photos into 720x720 px and crop it fill to center
import (
"fmt"
"path"
"github.com/h2non/bimg"
)
var root = "/menu-photos"
var outdir = "/menu-photos/720x720"
func resize(filepath, filename string) error {
buf, err := bimg.Read(filepath)
if err != nil {
return err
}
img := bimg.NewImage(buf)
size, err := img.Size()
if err != nil {
return err
}
newImage, err := img.ResizeAndCrop(720, 720)
if err != nil {
return err
}
size, err = bimg.NewImage(newImage).Size()
if err != nil {
return err
}
if size.Width != 720 || size.Height != 720 {
fmt.Printf("wrong size: '%s' %vx%v\n", filename, size.Width, size.Height)
}
fmt.Printf("resize & crop: %s\n", filename)
return bimg.Write(path.Join(outdir, filename), newImage)
}
To make the code faster, we can utilized goroutine. Here i spawned 4 goroutine that resize the images concurently.
type resizeJob struct {
FilePath string
FileName string
}
func main() {
jobChan := make(chan resizeJob, 4)
wg := &sync.WaitGroup{}
nworker := 4
// spawn workers
for i := 0; i < nworker; i++ {
wg.Add(1)
go worker(wg, jobChan)
}
err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
if info.IsDir() {
return nil
}
// enqueue jobs
jobChan <- resizeJob{FilePath: path, FileName: info.Name()}
return nil
})
close(jobChan)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
// wait for workers to finished
wg.Wait()
fmt.Println("done")
}
func worker(wg *sync.WaitGroup, jobChan chan resizeJob) {
defer wg.Done()
for file := range jobChan {
err := resize(file.FilePath, file.FileName)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
}
Credits
Photo by Angèle Kamp on Unsplash
Top comments (1)
You can do this at the command-line too:
Will make 720x720 thumbnails of every file in the current directory and write them as JPG to the
720/
directory. It'll run a set of thumbnail processes in parallel, so performance should be good.