diff options
| -rw-r--r-- | thumbnailer.go | 129 |
1 files changed, 46 insertions, 83 deletions
diff --git a/thumbnailer.go b/thumbnailer.go index 1236504..55a8eb2 100644 --- a/thumbnailer.go +++ b/thumbnailer.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "errors" "flag" "fmt" "image" @@ -10,7 +9,6 @@ import ( _ "image/jpeg" _ "image/png" "io" - "io/fs" "os" "path/filepath" "slices" @@ -75,51 +73,41 @@ func flagPrintMode(fs *flag.FlagSet, name string, value printMode, usage string) return &value } -func isDir(filename string) (bool, error) { +func isDir(filename string) bool { stat, err := os.Stat(filename) - if err != nil { - return false, err - } - return stat.IsDir(), nil + return err == nil && stat.IsDir() } -func isLink(filename string) (bool, error) { - stat, err := os.Lstat(filename) +func isImage(filename string) bool { + f, err := os.Open(filename) if err != nil { - return false, err + return false } - return stat.Mode().Type() == fs.ModeSymlink, nil -} - -var errNotAnImage = errors.New("not an image") - -type notAnImageError struct { - error + defer f.Close() + _, _, err = image.DecodeConfig(f) + return err == nil } -func (e *notAnImageError) Is(err error) bool { - return err == errNotAnImage +func imageDimensions(filename string) (width, height int, err error) { + f, err := os.Open(filename) + if err != nil { + return 0, 0, err + } + defer f.Close() + config, _, err := image.DecodeConfig(f) + return config.Width, config.Height, nil } func load(filename string) (image.Image, error) { - file := os.Stdin - if filename != "-" { - var err error - file, err = os.Open(filename) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - if isLink, _ := isLink(filename); isLink { - return nil, ¬AnImageError{err} - } - } - return nil, err - } - defer file.Close() + f, err := os.Open(filename) + if err != nil { + return nil, err } + defer f.Close() - img, _, err := image.Decode(file) + img, _, err := image.Decode(f) if err != nil { - return nil, ¬AnImageError{fmt.Errorf("decode %q: %s", filename, err)} + return nil, fmt.Errorf("decode %q: %s", filename, err) } return img, nil @@ -142,30 +130,33 @@ type pixelAspectRatio struct { x, y int } -func compositeImageRow(images []image.Image, width, padding int, aspectRatio pixelAspectRatio) image.Image { - resultWidth := width*len(images) + padding*(len(images)-1) +func compositeImageRow(imagePaths []string, width, padding int, aspectRatio pixelAspectRatio) (image.Image, error) { + resultWidth := width*len(imagePaths) + padding*(len(imagePaths)-1) maxHeight := 0 - for _, img := range images { - maxHeight = max(maxHeight, img.Bounds().Dy()*width/(img.Bounds().Dx()*aspectRatio.y/aspectRatio.x)) + for _, filename := range imagePaths { + imageWidth, imageHeight, err := imageDimensions(filename) + if err != nil { + return nil, err + } + maxHeight = max(maxHeight, imageHeight*width/(imageWidth*aspectRatio.y/aspectRatio.x)) } bounds := image.Rect(0, 0, resultWidth, maxHeight) result := image.NewRGBA(bounds) offset := 0 - for _, img := range images { + for _, filename := range imagePaths { + img, err := load(filename) + if err != nil { + return nil, err + } height := img.Bounds().Dy() * width / (img.Bounds().Dx() * aspectRatio.y / aspectRatio.x) dstBounds := image.Rect(offset, (maxHeight-height)/2, offset+width, (maxHeight-height)/2+height) draw.BiLinear.Scale(result, dstBounds, img, img.Bounds(), draw.Src, nil) offset += width + padding } - return result -} - -type img struct { - i image.Image - filename string + return result, nil } -func compositeImages(w io.Writer, images []img, width, imagesPerRow, cellX, cellY int) { +func thumbnailFiles(w io.Writer, imagePaths []string, width, imagesPerRow, cellX, cellY int) error { imageWidth := width padding := 1 aspectRatio := pixelAspectRatio{1, 1} @@ -178,17 +169,16 @@ func compositeImages(w io.Writer, images []img, width, imagesPerRow, cellX, cell imageWidth = width * cellX padding = cellX } - for row := range slices.Chunk(images, imagesPerRow) { - images := make([]image.Image, len(row)) - for i, img := range row { - images[i] = img.i + for row := range slices.Chunk(imagePaths, imagesPerRow) { + rowImage, err := compositeImageRow(row, imageWidth, padding, aspectRatio) + if err != nil { + return err } - rowImage := compositeImageRow(images, imageWidth, padding, aspectRatio) printImg(w, rowImage) names := make([][]string, len(row)) maxLen := 0 - for i, img := range row { - names[i] = strings.Split(runewidth.Wrap(img.filename, width), "\n") + for i, filename := range row { + names[i] = strings.Split(runewidth.Wrap(filepath.Base(filename), width), "\n") maxLen = max(maxLen, len(names[i])) } for i := 0; i < maxLen; i++ { @@ -206,18 +196,6 @@ func compositeImages(w io.Writer, images []img, width, imagesPerRow, cellX, cell } fmt.Fprintln(w) } -} - -func thumbnailFiles(w io.Writer, args []string, width, imagesPerRow, cellX, cellY int) error { - images := make([]img, len(args)) - for i, arg := range args { - image, err := load(arg) - if err != nil { - return err - } - images[i] = img{image, filepath.Base(arg)} - } - compositeImages(w, images, width, imagesPerRow, cellX, cellY) return nil } @@ -238,19 +216,8 @@ func thumbnailDir(w io.Writer, dir string, width, imagesPerRow, cellX, cellY int if err != nil { return err } - var images []img - for _, file := range files { - image, err := load(file) - if err != nil { - if errors.Is(err, errNotAnImage) { - continue - } - return err - } - images = append(images, img{image, filepath.Base(file)}) - } - compositeImages(w, images, width, imagesPerRow, cellX, cellY) - return nil + files = slices.DeleteFunc(files, func(filename string) bool { return !isImage(filename) }) + return thumbnailFiles(w, files, width, imagesPerRow, cellX, cellY) } func thumbnailer(args []string) error { @@ -273,11 +240,7 @@ func thumbnailer(args []string) error { dirs = []string{"."} } else { for _, arg := range args { - isDir, err := isDir(arg) - if err != nil { - return err - } - if isDir { + if isDir(arg) { dirs = append(dirs, arg) } else { files = append(files, arg) |
