diff --git a/ansipixels/fps/fps.go b/ansipixels/fps/fps.go index 2cfb201..49c5d62 100644 --- a/ansipixels/fps/fps.go +++ b/ansipixels/fps/fps.go @@ -86,7 +86,7 @@ var fpsJpg []byte //go:embed fps_colors.jpg var fpsColorsJpg []byte -func imagesViewer(ap *ansipixels.AnsiPixels, imageFiles []string) int { //nolint:gocognit // yeah well... +func imagesViewer(ap *ansipixels.AnsiPixels, imageFiles []string) int { //nolint:gocognit,gocyclo,funlen // yeah well... ap.Data = make([]byte, 3) i := 0 l := len(imageFiles) @@ -94,6 +94,9 @@ func imagesViewer(ap *ansipixels.AnsiPixels, imageFiles []string) int { //nolint ap.SignalChannel() tv := terminal.TimeoutToTimeval(100 * time.Millisecond) for { + zoom := 1.0 + offsetX := 0 + offsetY := 0 if i >= l { i = 0 } @@ -109,7 +112,7 @@ func imagesViewer(ap *ansipixels.AnsiPixels, imageFiles []string) int { //nolint info := fmt.Sprintf("%s (%dx%d %s%s)", imageFile, img.Width, img.Height, img.Format, extra) ap.ClearScreen() redraw: - if err = ap.ShowImage(img, "\033[34m"); err != nil { + if err = ap.ShowImage(img, zoom, offsetX, offsetY, "\033[34m"); err != nil { return log.FErrf("Error showing image: %v", err) } if showInfo { @@ -139,23 +142,58 @@ func imagesViewer(ap *ansipixels.AnsiPixels, imageFiles []string) int { //nolint } } ap.Data = ap.Data[0:n] - if ap.Data[0] == '?' || ap.Data[0] == 'h' || ap.Data[0] == 'H' { - ap.WriteCentered(ap.H/2-1, "Showing %d out of %d images, hit any key to continue,", i+1, l) - ap.WriteCentered(ap.H/2, "'q' to exit, left arrow to go back, 'i' to toggle image information") + largeSteps := int(10. * zoom) + c := ap.Data[0] + switch c { + case '?', 'h', 'H': + ap.WriteCentered(ap.H/2-1, "Showing %d out of %d images, hit any key to continue, up/down for zoom,", i+1, l) + ap.WriteCentered(ap.H/2, "WSAD to pan, 'q' to exit, left arrow to go back, 'i' to toggle image information") goto wait - } - if ap.Data[0] == 'i' || ap.Data[0] == 'I' { + case 'i', 'I': showInfo = !showInfo goto redraw - } - if ap.Data[0] == 27 { + case 'W': + offsetY -= largeSteps + goto redraw + case 'S': + offsetY += largeSteps + goto redraw + case 'A': + offsetX -= largeSteps + goto redraw + case 'D': + offsetX += largeSteps + goto redraw + case 'w': + offsetY-- + goto redraw + case 's': + offsetY++ + goto redraw + case 'a': + offsetX-- + goto redraw + case 'd': + offsetX++ + goto redraw + case 27: n, _ := ap.In.Read(ap.Data[1:3]) ap.Data = ap.Data[:1+n] } - // check for left/right arrow to go to next/previous image - if len(ap.Data) >= 3 && ap.Data[0] == 27 && ap.Data[1] == '[' && ap.Data[2] == 'D' { - i = (i + l - 1) % l - continue + // check for left arrow to go to next/previous image + if len(ap.Data) >= 3 && c == 27 && ap.Data[1] == '[' { + // Arrow key + switch ap.Data[2] { + case 'D': // left arrow + i = (i + l - 1) % l + continue + case 'A': // up arrow + zoom *= 1.25 + goto redraw + case 'B': // down arrow + zoom /= 1.25 + goto redraw + } } if isStopKey(ap) || l == 1 { return 0 @@ -242,7 +280,7 @@ func Main() int { //nolint:funlen,gocognit // color and mode if/else are a bit l if err != nil { return log.FErrf("Error reading image: %v", err) } - if err = ap.ShowImage(background, "\033[34m"); err != nil { + if err = ap.ShowImage(background, 1.0, 0, 0, "\033[34m"); err != nil { return log.FErrf("Error showing image: %v", err) } buf := [256]byte{} @@ -287,7 +325,7 @@ func Main() int { //nolint:funlen,gocognit // color and mode if/else are a bit l if ap.IsResizeSignal(s) { _ = ap.GetSize() ap.ClearScreen() - _ = ap.ShowImage(background, "\033[34m") + _ = ap.ShowImage(background, 1.0, 0, 0, "\033[34m") if !*noboxFlag { drawBox(ap) } diff --git a/ansipixels/image.go b/ansipixels/image.go index a0d6044..3dedbf0 100644 --- a/ansipixels/image.go +++ b/ansipixels/image.go @@ -173,7 +173,7 @@ func toGrey(rgbaImg *image.RGBA, img image.Image) { } } -func resizeAndCenter(img *image.RGBA, maxW, maxH int) *image.RGBA { +func resizeAndCenter(img *image.RGBA, maxW, maxH int, zoom float64, offsetX, offsetY int) *image.RGBA { // Get original image dimensions origBounds := img.Bounds() origW := origBounds.Dx() @@ -183,6 +183,7 @@ func resizeAndCenter(img *image.RGBA, maxW, maxH int) *image.RGBA { scaleW := float64(maxW) / float64(origW) scaleH := float64(maxH) / float64(origH) scale := min(scaleW, scaleH) // Choose the smallest scale to fit within bounds + scale *= zoom // Calculate new dimensions while preserving aspect ratio newW := int(float64(origW) * scale) @@ -191,8 +192,8 @@ func resizeAndCenter(img *image.RGBA, maxW, maxH int) *image.RGBA { canvas := image.NewRGBA(image.Rect(0, 0, maxW, maxH)) // Calculate the offset to center the image - offsetX := (maxW - newW) / 2 - offsetY := (maxH - newH) / 2 + offsetX += (maxW - newW) / 2 + offsetY += (maxH - newH) / 2 // Resize the image resized := image.NewRGBA(image.Rect(0, 0, newW, newH)) @@ -270,13 +271,13 @@ func (ap *AnsiPixels) DecodeImage(inp io.Reader) (*Image, error) { } // Color string is the fallback mono color to use when AnsiPixels.TrueColor is false. -func (ap *AnsiPixels) ShowImage(imagesRGBA *Image, colorString string) error { +func (ap *AnsiPixels) ShowImage(imagesRGBA *Image, zoom float64, offsetX, offsetY int, colorString string) error { err := ap.GetSize() if err != nil { return err } for i, imgRGBA := range imagesRGBA.Images { - img := resizeAndCenter(imgRGBA, ap.W-2*ap.Margin, 2*ap.H-2*ap.Margin) + img := resizeAndCenter(imgRGBA, ap.W-2*ap.Margin, 2*ap.H-2*ap.Margin, zoom, offsetX, offsetY) if ap.Gray { toGrey(img, img) }