diff options
Diffstat (limited to 'ils.go')
| -rw-r--r-- | ils.go | 130 |
1 files changed, 92 insertions, 38 deletions
@@ -12,7 +12,6 @@ import ( "os" "path/filepath" "slices" - "strings" runewidth "github.com/mattn/go-runewidth" _ "golang.org/x/image/bmp" @@ -95,7 +94,7 @@ func imageDimensions(filename string) (width, height int, err error) { } defer f.Close() config, _, err := image.DecodeConfig(f) - return config.Width, config.Height, nil + return config.Width, config.Height, err } func load(filename string) (image.Image, error) { @@ -130,17 +129,22 @@ type pixelAspectRatio struct { x, y int } -func compositeImageRow(imagePaths []string, width, padding int, aspectRatio pixelAspectRatio) (image.Image, error) { - resultWidth := width*len(imagePaths) + padding*(len(imagePaths)-1) - maxHeight := 0 - for _, filename := range imagePaths { +func compositeImageRow(imagePaths []string, width, rowWidth, cellX int, aspectRatio pixelAspectRatio) (image.Image, error) { + var logicalWidth, maxHeight int + for i, filename := range imagePaths { + if i > 0 { + logicalWidth += 2 * cellX + } + fileNameWidth := cellX * runewidth.StringWidth(filepath.Base(filename)) + logicalWidth += max(width, fileNameWidth) + 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) + bounds := image.Rect(0, 0, rowWidth, maxHeight) result := image.NewRGBA(bounds) offset := 0 for _, filename := range imagePaths { @@ -149,50 +153,101 @@ func compositeImageRow(imagePaths []string, width, padding int, aspectRatio pixe 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) + imageWidth := max(width, cellX*runewidth.StringWidth(filepath.Base(filename))) + start := (offset*rowWidth + logicalWidth - 1) / logicalWidth + end := ((offset+imageWidth)*rowWidth + logicalWidth - 1) / logicalWidth + offsetStart := start + (end-start-width)/2 + dstBounds := image.Rect(offsetStart, (maxHeight-height)/2, offsetStart+width, (maxHeight-height)/2+height) draw.BiLinear.Scale(result, dstBounds, img, img.Bounds(), draw.Src, nil) - offset += width + padding + offset += imageWidth + 2*cellX } return result, nil } -func thumbnailFiles(w io.Writer, imagePaths []string, width, imagesPerRow, cellX, cellY int) error { - imageWidth := width - padding := 1 +func splitRows(imagePaths []string, width, rowWidth int) [][]string { + var rows [][]string + for len(imagePaths) > 0 { + row := imagePaths[:0] + thisRowWidth := 0 + for ; len(row) < len(imagePaths); row = row[:len(row)+1] { + nextW := max(width, runewidth.StringWidth(filepath.Base(imagePaths[len(row)]))) + if len(row) > 0 { + nextW += 2 + } + if thisRowWidth > 0 && thisRowWidth+nextW > rowWidth { + break + } + thisRowWidth += nextW + } + imagePaths = imagePaths[len(row):] + rows = append(rows, row) + } + return rows +} + +func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, cellY int) error { + padding := 2 aspectRatio := pixelAspectRatio{1, 1} switch *m { case modeBlock: aspectRatio = pixelAspectRatio{cellX, cellY} + cellX = 1 case modeBlock24: aspectRatio = pixelAspectRatio{2 * cellX, cellY} + cellX = 1 case modeSixel: - imageWidth = width * cellX - padding = cellX + padding *= cellX } - for row := range slices.Chunk(imagePaths, imagesPerRow) { - rowImage, err := compositeImageRow(row, imageWidth, padding, aspectRatio) - if err != nil { + rows := splitRows(imagePaths, width, rowWidth) + rowImages := make([]chan image.Image, len(rows)) + for i := range rowImages { + rowImages[i] = make(chan image.Image, 1) + } + errCh := make(chan error, 1) + for i, row := range rows { + go func() { + img, err := compositeImageRow(row, width*cellX, rowWidth*cellX, cellX, aspectRatio) + if err != nil { + select { + case errCh <- err: + default: + } + return + } + rowImages[i] <- img + }() + } + for i, row := range rows { + thisRowWidth := 0 + for j, filename := range row { + if j > 0 { + thisRowWidth += 2 + } + thisRowWidth += max(width, runewidth.StringWidth(filepath.Base(filename))) + } + var rowImage image.Image + select { + case err := <-errCh: return err + case rowImage = <-rowImages[i]: } printImg(w, rowImage) - names := make([][]string, len(row)) - maxLen := 0 + fmt.Fprintln(w) + offset := 0 + realOffset := 0 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++ { - fmt.Fprintln(w) - for imgIdx, name := range names { - if imgIdx > 0 { - fmt.Fprint(w, " ") - } - if i < len(name) { - fmt.Fprintf(w, "%s", runewidth.FillRight(name[i], width)) - } else { - fmt.Fprintf(w, "%*s", width, "") - } + if i > 0 { + offset += 2 } + start := (offset*rowWidth + thisRowWidth - 1) / thisRowWidth + fmt.Fprintf(w, "%*s", start-realOffset, "") + realOffset = start + fileNameWidth := runewidth.StringWidth(filepath.Base(filename)) + offset += max(width, fileNameWidth) + end := (offset*rowWidth + thisRowWidth - 1) / thisRowWidth + padding := (end - start - fileNameWidth) / 2 + fmt.Fprintf(w, "%*s%s", padding, "", filepath.Base(filename)) + realOffset += padding + fileNameWidth } fmt.Fprintln(w) } @@ -211,13 +266,13 @@ func readDir(d string) ([]string, error) { return filenames, nil } -func thumbnailDir(w io.Writer, dir string, width, imagesPerRow, cellX, cellY int) error { +func thumbnailDir(w io.Writer, dir string, width, rowWidth, cellX, cellY int) error { files, err := readDir(dir) if err != nil { return err } files = slices.DeleteFunc(files, func(filename string) bool { return !isImage(filename) }) - return thumbnailFiles(w, files, width, imagesPerRow, cellX, cellY) + return thumbnailFiles(w, files, width, rowWidth, cellX, cellY) } func thumbnailer(args []string) error { @@ -233,7 +288,6 @@ func thumbnailer(args []string) error { cellX, cellY := int(ws.Xpixel)/int(ws.Col), int(ws.Ypixel)/int(ws.Row) cols = min(cols, termCols) - imagesPerRow := (termCols + 1) / (cols + 1) var files, dirs []string if len(args) == 0 { @@ -249,7 +303,7 @@ func thumbnailer(args []string) error { } w := bufio.NewWriter(os.Stdout) defer w.Flush() - if err := thumbnailFiles(w, files, cols, imagesPerRow, cellX, cellY); err != nil { + if err := thumbnailFiles(w, files, cols, termCols, cellX, cellY); err != nil { return err } if len(files) > 0 && len(dirs) > 0 { @@ -262,7 +316,7 @@ func thumbnailer(args []string) error { if len(dirs) > 1 { fmt.Fprintf(w, "%s:\n", d) } - if err := thumbnailDir(w, d, cols, imagesPerRow, cellX, cellY); err != nil { + if err := thumbnailDir(w, d, cols, termCols, cellX, cellY); err != nil { return err } } |
