diff --git a/kittens/icat/main.py b/kittens/icat/main.py index 297fa3536..1a0bb1144 100644 --- a/kittens/icat/main.py +++ b/kittens/icat/main.py @@ -46,7 +46,9 @@ Choose where on the screen to display the image. The image will be scaled to fit into the specified rectangle. The syntax for specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements are in cells (i.e. cursor positions) with the origin -:italic:`(0, 0)` at the top-left corner of the screen. +:italic:`(0, 0)` at the top-left corner of the screen. Note that the :option:`--align` +option will horizontally align the image within this rectangle. By default, the image +is horizontally centered within the rectangle. --scale-up diff --git a/tools/cmd/icat/native.go b/tools/cmd/icat/native.go index 2fa7fb7a0..93909b518 100644 --- a/tools/cmd/icat/native.go +++ b/tools/cmd/icat/native.go @@ -50,6 +50,15 @@ func load_one_frame_image(imgd *image_data, src *opened_input) (image.Image, err imgd.canvas_height = img.Bounds().Dy() set_basic_metadata(imgd) } + if imgd.needs_scaling { + if imgd.canvas_width < imgd.available_width && opts.ScaleUp && place != nil { + r := float64(imgd.available_width) / float64(imgd.canvas_width) + imgd.canvas_width, imgd.canvas_height = imgd.available_width, int(r*float64(imgd.canvas_height)) + } + imgd.canvas_width, imgd.canvas_height = images.FitImage(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height) + img = imaging.Resize(img, imgd.canvas_width, imgd.canvas_height, imaging.Lanczos) + imgd.needs_scaling = false + } return img, err } diff --git a/tools/cmd/icat/process_images.go b/tools/cmd/icat/process_images.go index c7030b2ea..4403f2120 100644 --- a/tools/cmd/icat/process_images.go +++ b/tools/cmd/icat/process_images.go @@ -151,6 +151,9 @@ type image_data struct { needs_scaling, needs_conversion bool frames []*image_frame image_number uint32 + cell_x_offset int + move_x_by int + move_to struct{ x, y int } // for error reporting err error diff --git a/tools/cmd/icat/transmit.go b/tools/cmd/icat/transmit.go index 30490109e..93e4d58a9 100644 --- a/tools/cmd/icat/transmit.go +++ b/tools/cmd/icat/transmit.go @@ -10,6 +10,7 @@ import ( "kitty/tools/tui/graphics" "kitty/tools/utils" "kitty/tools/utils/shm" + "math" "math/rand" "os" "path/filepath" @@ -22,6 +23,9 @@ func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics gc.SetAction(graphics.GRT_action_transmit_and_display) gc.SetDataWidth(uint64(frame.width)).SetDataHeight(uint64(frame.height)) gc.SetQuiet(graphics.GRT_quiet_silent) + if frame_num == 0 && imgd.cell_x_offset > 0 { + gc.SetXOffset(uint64(imgd.cell_x_offset)) + } if z_index != 0 { gc.SetZIndex(z_index) } @@ -151,6 +155,44 @@ func transmit_stream(imgd *image_data, frame_num int, frame *image_frame) (err e return nil } +func calculate_in_cell_x_offset(width, cell_width int) int { + extra_pixels := width % cell_width + if extra_pixels == 0 { + return 0 + } + switch opts.Align { + case "left": + return 0 + case "right": + return cell_width - extra_pixels + default: + return (cell_width - extra_pixels) / 2 + } +} + +func place_cursor(imgd *image_data) { + cw := int(screen_size.CellWidth) + imgd.cell_x_offset = calculate_in_cell_x_offset(imgd.canvas_width, cw) + num_of_cells_needed := int(math.Ceil(float64(imgd.canvas_width) / float64(cw))) + if place == nil { + switch opts.Align { + case "center": + imgd.move_x_by = (int(screen_size.WidthCells) - num_of_cells_needed) / 2 + case "right": + imgd.move_x_by = (int(screen_size.WidthCells) - num_of_cells_needed) + } + } else { + imgd.move_to.x = place.left + 1 + imgd.move_to.y = place.top + 1 + switch opts.Align { + case "center": + imgd.move_to.x += (place.width - num_of_cells_needed) / 2 + case "right": + imgd.move_to.x += (place.width - num_of_cells_needed) + } + } +} + func transmit_image(imgd *image_data) { defer func() { for _, frame := range imgd.frames { @@ -189,10 +231,21 @@ func transmit_image(imgd *image_data) { if len(imgd.frames) > 1 { imgd.image_number = rand.Uint32() } + place_cursor(imgd) + lp.QueueWriteString("\r") + if imgd.move_x_by > 0 { + lp.MoveCursorHorizontally(imgd.move_x_by) + } + if imgd.move_to.x > 0 { + lp.MoveCursorTo(imgd.move_to.x, imgd.move_to.y) + } for frame_num, frame := range imgd.frames { err := f(imgd, frame_num, frame) if err != nil { print_error("Failed to transmit %s with error: %v", imgd.source_name, err) } } + if imgd.move_to.x == 0 { + lp.Println() // ensure cursor is on new line + } } diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index 9f6bd3faf..490097e68 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -261,6 +261,12 @@ func (self *Loop) SetCursorVisible(visible bool) { } } +func (self *Loop) MoveCursorTo(x, y int) { + if x > 0 && y > 0 { + self.QueueWriteString(fmt.Sprintf("\x1b[%d;%dH", y, x)) + } +} + func (self *Loop) MoveCursorHorizontally(amt int) { if amt != 0 { suffix := "C" diff --git a/tools/utils/images/to_rgba.go b/tools/utils/images/to_rgba.go index 0b1c0f185..22025fa65 100644 --- a/tools/utils/images/to_rgba.go +++ b/tools/utils/images/to_rgba.go @@ -6,6 +6,7 @@ import ( "fmt" "image" "image/color" + "math" "runtime" "sync" ) @@ -390,3 +391,20 @@ func PasteCenter(background image.Image, img image.Image, opaque_bg *color.NRGBA Paste(background, img, image.Pt(x0, y0), opaque_bg) } + +func FitImage(width, height, pwidth, pheight int) (final_width int, final_height int) { + if height > pheight { + corrf := float64(pheight) / float64(height) + width, height = int(math.Floor(corrf*float64(width))), pheight + } + if width > pwidth { + corrf := float64(pwidth) / float64(width) + width, height = pwidth, int(math.Floor(corrf*float64(height))) + } + if height > pheight { + corrf := float64(pheight) / float64(height) + width, height = int(math.Floor(corrf*float64(width))), pheight + } + + return width, height +}