diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2026-06-12 20:59:52 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2026-06-12 20:59:52 -0700 |
| commit | 3391135078aecd774e44b4fafc1873d88e240530 (patch) | |
| tree | dc694def10f56326e9bbfc29b9c9146d801d154a | |
| parent | 084bd5469beba8fc230bd46f8b951f10a3e9d076 (diff) | |
| download | imgutil-3391135078aecd774e44b4fafc1873d88e240530.tar.zst | |
Add a -l flag like real ls
| -rw-r--r-- | ils.go | 137 |
1 files changed, 110 insertions, 27 deletions
@@ -5,6 +5,7 @@ import ( "flag" "fmt" "image" + "image/color" _ "image/gif" _ "image/jpeg" _ "image/png" @@ -89,14 +90,57 @@ func isImage(filename string) bool { return err == nil } -func imageDimensions(filename string) (width, height int, err error) { +type imageMetadata struct { + format, colorModel string + width, height int +} + +func (m *imageMetadata) String() string { + return fmt.Sprintf("%dx%d %s %s", m.width, m.height, m.format, m.colorModel) +} + +func colorModelName(m color.Model) string { + switch m { + case color.RGBAModel: + return "rgba" + case color.RGBA64Model: + return "rgba 64 bit" + case color.NRGBAModel: + return "rgba not alpha premultiplied" + case color.NRGBA64Model: + return "rgba 64 bit not alpha premultiplied" + case color.AlphaModel: + return "alpha" + case color.Alpha16Model: + return "alpha 16 bit" + case color.GrayModel: + return "grayscale" + case color.Gray16Model: + return "grayscale 16 bit" + case color.CMYKModel: + return "cmyk" + case color.NYCbCrAModel: + return "Y'CbCr with alpha" + case color.YCbCrModel: + return "Y'CbCr" + } + if palette, ok := m.(color.Palette); ok { + return fmt.Sprintf("palettized %d colors", len(palette)) + } + return fmt.Sprintf("unknown %v", m) +} + +func readMetadata(filename string) (imageMetadata, error) { f, err := os.Open(filename) if err != nil { - return 0, 0, err + return imageMetadata{}, err } defer f.Close() - config, _, err := image.DecodeConfig(f) - return config.Width, config.Height, err + config, format, err := image.DecodeConfig(f) + if err != nil { + return imageMetadata{}, err + } + return imageMetadata{format: format, colorModel: colorModelName(config.ColorModel), width: config.Width, height: config.Height}, nil } func load(filename string) (image.Image, error) { @@ -144,10 +188,13 @@ type pixelAspectRatio struct { x, y int } -func layoutRow(imagePaths []string, width, rowWidth, cellX int) []int { +func layoutRow(imagePaths []string, metadata []imageMetadata, width, rowWidth, cellX int) []int { filenameWidths := make([]int, len(imagePaths)) for i, path := range imagePaths { filenameWidths[i] = cellX * runewidth.StringWidth(escapeString(filepath.Base(path))) + if *l { + filenameWidths[i] = max(filenameWidths[i], cellX*runewidth.StringWidth(metadata[i].String())) + } } var logicalWidth int for i, filenameWidth := range filenameWidths { @@ -166,15 +213,21 @@ func layoutRow(imagePaths []string, width, rowWidth, cellX int) []int { return layout } -func compositeImageRow(imagePaths []string, layout []int, width, rowWidth, cellX int, aspectRatio pixelAspectRatio) (image.Image, error) { - var maxHeight int - for _, filename := range imagePaths { - imageWidth, imageHeight, err := imageDimensions(filename) - if err != nil { - return nil, err +func compositeImageRow(imagePaths []string, metadata []imageMetadata, width, rowWidth, cellX int, aspectRatio pixelAspectRatio) (image.Image, error) { + if len(metadata) == 0 { + metadata = make([]imageMetadata, len(imagePaths)) + for i, filename := range imagePaths { + var err error + if metadata[i], err = readMetadata(filename); err != nil { + return nil, err + } } - maxHeight = max(maxHeight, divRound(imageHeight*width*aspectRatio.x, imageWidth*aspectRatio.y)) } + var maxHeight int + for _, fileMetadata := range metadata { + maxHeight = max(maxHeight, divRound(fileMetadata.height*width*aspectRatio.x, fileMetadata.width*aspectRatio.y)) + } + layout := layoutRow(imagePaths, metadata, width, rowWidth, cellX) bounds := image.Rect(0, 0, rowWidth, maxHeight) result := image.NewRGBA(bounds) for i, filename := range imagePaths { @@ -195,13 +248,16 @@ func compositeImageRow(imagePaths []string, layout []int, width, rowWidth, cellX return result, nil } -func splitRows(imagePaths []string, width, rowWidth int) [][]string { +func splitRows(imagePaths []string, metadata []imageMetadata, 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(escapeString(filepath.Base(imagePaths[len(row)])))) + if *l { + nextW = max(nextW, len(metadata[len(row)].String())) + } if len(row) > 0 { nextW += 2 } @@ -211,6 +267,9 @@ func splitRows(imagePaths []string, width, rowWidth int) [][]string { thisRowWidth += nextW } imagePaths = imagePaths[len(row):] + if *l { + metadata = metadata[len(row):] + } rows = append(rows, row) } return rows @@ -229,10 +288,23 @@ func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, ce case modeSixel: padding *= cellX } - rows := splitRows(imagePaths, width, rowWidth) - layouts := make([][]int, len(rows)) - for i, row := range rows { - layouts[i] = layoutRow(row, width*cellX, rowWidth*cellX, cellX) + var metadata []imageMetadata + if *l { + metadata = make([]imageMetadata, len(imagePaths)) + for i, filename := range imagePaths { + var err error + if metadata[i], err = readMetadata(filename); err != nil { + return err + } + } + } + rows := splitRows(imagePaths, metadata, width, rowWidth) + rowMetadata := make([][]imageMetadata, len(rows)) + if *l { + for i, row := range rows { + rowMetadata[i] = metadata[:len(row)] + metadata = metadata[len(row):] + } } rowImages := make([]chan image.Image, len(rows)) for i := range rowImages { @@ -245,7 +317,7 @@ func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, ce sem <- struct{}{} go func() { defer func() { <-sem }() - img, err := compositeImageRow(row, layouts[i], width*cellX, rowWidth*cellX, cellX, aspectRatio) + img, err := compositeImageRow(row, rowMetadata[i], width*cellX, rowWidth*cellX, cellX, aspectRatio) if err != nil { select { case errCh <- err: @@ -258,13 +330,6 @@ func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, ce } }() for i, row := range rows { - thisRowWidth := 0 - for j, filename := range row { - if j > 0 { - thisRowWidth += 2 - } - thisRowWidth += max(width, runewidth.StringWidth(escapeString(filepath.Base(filename)))) - } var rowImage image.Image select { case err := <-errCh: @@ -273,12 +338,13 @@ func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, ce } printImg(w, rowImage) fmt.Fprintln(w) + layouts := layoutRow(row, rowMetadata[i], width*cellX, rowWidth*cellX, cellX) realOffset := 0 for j, filename := range row { - start := divRound(layouts[i][j], cellX) + start := divRound(layouts[j], cellX) end := rowWidth if j+1 < len(row) { - end = divRound(layouts[i][j+1], cellX) + end = divRound(layouts[j+1], cellX) } fmt.Fprintf(w, "%*s", start-realOffset, "") realOffset = start @@ -288,6 +354,23 @@ func thumbnailFiles(w io.Writer, imagePaths []string, width, rowWidth, cellX, ce realOffset += padding + fileNameWidth } fmt.Fprintln(w) + if *l { + realOffset = 0 + for j, metadata := range rowMetadata[i] { + start := divRound(layouts[j], cellX) + end := rowWidth + if j+1 < len(row) { + end = divRound(layouts[j+1], cellX) + } + fmt.Fprintf(w, "%*s", start-realOffset, "") + realOffset = start + details := metadata.String() + padding := max((end-start-len(details))/2, 0) + fmt.Fprintf(w, "%*s%s", padding, "", details) + realOffset += padding + len(details) + } + fmt.Fprintln(w) + } } return nil } |
