diff --git a/lua/image/backends/kitty/init.lua b/lua/image/backends/kitty/init.lua index 4d346e3..75963b6 100644 --- a/lua/image/backends/kitty/init.lua +++ b/lua/image/backends/kitty/init.lua @@ -59,9 +59,9 @@ backend.render = function(image, x, y, width, height) transmitted_images[image.id] = true end else - if transmitted_images[image.id] ~= image.crop_hash then + if transmitted_images[image.id] ~= image.crop_resize_hash then transmit() - transmitted_images[image.id] = image.crop_hash + transmitted_images[image.id] = image.crop_resize_hash end end @@ -113,7 +113,7 @@ backend.render = function(image, x, y, width, height) if y + height > image.bounds.bottom then pixel_height = (image.bounds.bottom - y + 1) * term_size.cell_height end -- crop right - if x + width > image.bounds.right then pixel_width = (image.bounds.right - x) * term_size.cell_width end + -- if x + width > image.bounds.right then pixel_width = (image.bounds.right - x) * term_size.cell_width end display_payload.display_width = pixel_width display_payload.display_height = pixel_height diff --git a/lua/image/image.lua b/lua/image/image.lua index 82926d8..f0f0503 100644 --- a/lua/image/image.lua +++ b/lua/image/image.lua @@ -191,7 +191,7 @@ local from_file = function(path, options, state) }, with_virtual_padding = opts.with_virtual_padding or false, is_rendered = false, - crop_hash = nil, + crop_resize_hash = nil, }, state) return instance diff --git a/lua/image/renderer.lua b/lua/image/renderer.lua index ab48b75..f929829 100644 --- a/lua/image/renderer.lua +++ b/lua/image/renderer.lua @@ -152,18 +152,35 @@ local render = function(image) local botline = win_info.botline -- bail if out of bounds - if image.geometry.y + 1 < topline or image.geometry.y > botline then prevent_rendering = true end + if image.geometry.y + 1 < topline or image.geometry.y > botline then + -- utils.debug("prevent rendering 1", image.id) + prevent_rendering = true + end -- extmark offsets if image.with_virtual_padding then - -- bail if the image is above the top of the window and there's no topfill - if topfill == 0 and image.geometry.y < topline then prevent_rendering = true end + -- bail if the image is above the top of the window at least by one line + if topfill == 0 and image.geometry.y < topline then + -- utils.debug("prevent rendering 2", image.id) + prevent_rendering = true + end -- bail if the image + its height is above the top of the window + topfill - if image.geometry.y + height + 1 < topline + topfill then prevent_rendering = true end + -- if image.geometry.y + height + 1 < topline + topfill then + -- utils.debug("prevent rendering 3", image.id, { + -- y = image.geometry.y, + -- height = height, + -- topline = topline, + -- topfill = topfill, + -- }) + -- prevent_rendering = true + -- end -- bail if the image is below the bottom of the window - if image.geometry.y > botline then prevent_rendering = true end + if image.geometry.y > botline then + -- utils.debug("prevent rendering 4", image.id) + prevent_rendering = true + end -- offset by topfill if the image started above the top of the window if not prevent_rendering then @@ -231,7 +248,7 @@ local render = function(image) or absolute_x > bounds.right then if image.is_rendered then - -- utils.debug("deleting out of bounds image", { id = image.id, x = x, y = y, width = width, height = height, bounds = bounds }) + -- utils.debug("deleting out of bounds image", { id = image.id, x = absolute_x, y = absolute_y, width = width, height = height, bounds = bounds }) state.backend.clear(image.id, true) else state.images[image.id] = image @@ -251,17 +268,20 @@ local render = function(image) return true end - -- crop - if not state.backend.features.crop then - local pixel_width = width * term_size.cell_width - local pixel_height = height * term_size.cell_height - local crop_offset_top = 0 - local needs_crop = false + -- handle crop/resize + local pixel_width = width * term_size.cell_width + local pixel_height = height * term_size.cell_height + local crop_offset_top = 0 + local cropped_pixel_height = height * term_size.cell_height + local needs_crop = false + local needs_resize = false + -- compute crop top/bottom + if not state.backend.features.crop then -- crop top if absolute_y < bounds.top then local visible_rows = height - (bounds.top - absolute_y) - pixel_height = visible_rows * term_size.cell_height + cropped_pixel_height = visible_rows * term_size.cell_height crop_offset_top = (bounds.top - absolute_y) * term_size.cell_height absolute_y = bounds.top needs_crop = true @@ -269,31 +289,63 @@ local render = function(image) -- crop bottom if absolute_y + height > bounds.bottom then - pixel_height = (bounds.bottom - absolute_y + 1) * term_size.cell_height + cropped_pixel_height = (bounds.bottom - absolute_y + 1) * term_size.cell_height needs_crop = true end - -- crop right - if absolute_x + width > bounds.right then - pixel_width = (bounds.right - absolute_x) * term_size.cell_width - needs_crop = true - end + -- -- crop right + -- if absolute_x + width > bounds.right then + -- pixel_width = (bounds.right - absolute_x) * term_size.cell_width + -- needs_crop = true + -- end + end - -- perform crop - local crop_hash = needs_crop and ("%d-%d-%d-%d"):format(pixel_width, pixel_height, 0, crop_offset_top) or nil - if crop_hash or (image.crop_hash ~= crop_hash) then - local cropped_image = magick.load_image(image.path) - cropped_image:set_format("png") - cropped_image:crop(pixel_width, pixel_height, 0, crop_offset_top) - local tmp_path = state.tmp_dir .. "/" .. utils.random.id() .. ".png" - cropped_image:write(tmp_path) - cropped_image:destroy() - image.cropped_path = tmp_path - image.crop_hash = crop_hash + -- compute resize + if image.image_width > pixel_width then needs_resize = true end + utils.debug( + ("image %s: pixel_width: %d, image_width: %d, needs_resize: %s"):format( + image.path, + pixel_width, + image.image_width, + tostring(needs_resize) + ) + ) + + -- perform resize / crop + local crop_resize_hash = ("%d-%d-%d-%d-%d-%d"):format( + pixel_width, + cropped_pixel_height, + 0, + crop_offset_top, + pixel_width, + pixel_height + ) or nil + + -- utils.debug("prev hash:", image.crop_resize_hash, "next hash:", crop_resize_hash) + + -- TODO: make this non-blocking + if (needs_crop or needs_resize) and (image.crop_resize_hash ~= crop_resize_hash) then + local cropped_image = magick.load_image(image.path) + cropped_image:set_format("png") + if needs_resize then + -- resize to target width + cropped_image:scale(pixel_width, pixel_height) + -- utils.debug(("resizing image %s to %dx%d"):format(image.path, pixel_width, pixel_height)) + end + if needs_crop then + -- crop + cropped_image:crop(pixel_width, cropped_pixel_height, 0, crop_offset_top) + -- utils.debug(("cropping image %s to %dx%d"):format(image.path, pixel_width, cropped_pixel_height)) end + -- TODO make this temp path persistent per image to avoid generating a file for each resize/crop + local tmp_path = state.tmp_dir .. "/" .. utils.random.id() .. ".png" + cropped_image:write(tmp_path) + cropped_image:destroy() + image.cropped_path = tmp_path + image.crop_resize_hash = crop_resize_hash end - -- utils.debug(("(5) x: %d, y: %d, width: %d, height: %d y_offset: %d"):format(x, y, width, height, y_offset)) + -- utils.debug(("render x: %d, y: %d, width: %d, height: %d y_offset: %d"):format(x, y, width, height, y_offset)) image.bounds = bounds state.backend.render(image, absolute_x, absolute_y, width, height) image.rendered_geometry = rendered_geometry diff --git a/lua/types.lua b/lua/types.lua index ef2b490..c25543c 100644 --- a/lua/types.lua +++ b/lua/types.lua @@ -97,7 +97,7 @@ ---@field rendered_geometry ImageGeometry ---@field bounds ImageBounds ---@field is_rendered boolean ----@field crop_hash? string +---@field crop_resize_hash? string ---@field global_state State ---@field render fun(self: Image, geometry?: ImageGeometry) ---@field clear fun(self: Image)