mirror of
https://github.com/folke/snacks.nvim
synced 2025-07-15 00:55:11 +00:00
89 lines
2.7 KiB
Lua
89 lines
2.7 KiB
Lua
---@class snacks.image.util
|
|
local M = {}
|
|
|
|
local dims = {} ---@type table<string, snacks.image.Size>
|
|
|
|
--- Get the dimensions of a PNG file
|
|
---@param file string
|
|
---@return snacks.image.Size
|
|
function M.dim(file)
|
|
file = svim.fs.normalize(file)
|
|
if dims[file] then
|
|
return dims[file]
|
|
end
|
|
-- extract header with IHDR chunk
|
|
local fd = assert(io.open(file, "rb"), "Failed to open file: " .. file)
|
|
local header = fd:read(24) ---@type string
|
|
fd:close()
|
|
|
|
-- Check PNG signature
|
|
assert(header:sub(1, 8) == "\137PNG\r\n\26\n", "Not a valid PNG file: " .. file)
|
|
|
|
-- Extract width and height from the IHDR chunk
|
|
local width = header:byte(17) * 16777216 + header:byte(18) * 65536 + header:byte(19) * 256 + header:byte(20)
|
|
local height = header:byte(21) * 16777216 + header:byte(22) * 65536 + header:byte(23) * 256 + header:byte(24)
|
|
dims[file] = { width = width, height = height }
|
|
return dims[file]
|
|
end
|
|
|
|
---@param size snacks.image.Size
|
|
function M.pixels_to_cells(size)
|
|
local terminal = Snacks.image.terminal.size()
|
|
return M.norm({
|
|
width = size.width / terminal.cell_width,
|
|
height = size.height / terminal.cell_height,
|
|
})
|
|
end
|
|
|
|
---@param size snacks.image.Size
|
|
---@return snacks.image.Size
|
|
function M.norm(size)
|
|
return {
|
|
width = math.max(1, math.ceil(size.width)),
|
|
height = math.max(1, math.ceil(size.height)),
|
|
}
|
|
end
|
|
|
|
---@param file string
|
|
---@param cells snacks.image.Size size in rows x columns
|
|
---@param opts? { full?: boolean, info?: snacks.image.Info }
|
|
function M.fit(file, cells, opts)
|
|
opts = opts or {}
|
|
local img_pixels ---@type snacks.image.Size
|
|
if opts.info then
|
|
local terminal = Snacks.image.terminal.size()
|
|
img_pixels = {}
|
|
img_pixels.height = opts.info.size.height / opts.info.dpi.height * 96 * terminal.scale
|
|
img_pixels.width = opts.info.size.width / opts.info.dpi.width * 96 * terminal.scale
|
|
else
|
|
img_pixels = M.dim(file)
|
|
end
|
|
local img_cells = M.pixels_to_cells(img_pixels)
|
|
|
|
local ret = vim.deepcopy(cells)
|
|
-- if not opts.full then
|
|
if img_cells.width <= cells.width and img_cells.height <= cells.height then
|
|
return img_cells
|
|
end
|
|
ret.width = math.min(cells.width, img_cells.width)
|
|
ret.height = math.min(cells.height, img_cells.height)
|
|
-- end
|
|
|
|
local scale = ret.width / ret.height
|
|
local img_scale = img_cells.width / img_cells.height
|
|
local fit_height = math.floor(ret.width / img_scale + 0.5)
|
|
local fit_width = math.floor(ret.height * img_scale + 0.5)
|
|
|
|
if ret.height == fit_height or ret.width == fit_width then
|
|
-- Image fits exactly
|
|
elseif img_scale > scale then
|
|
-- Image is wider relative to height - fit to width
|
|
ret.height = fit_height
|
|
else
|
|
-- Image is taller relative to width - fit to height
|
|
ret.width = fit_width
|
|
end
|
|
return M.norm(ret)
|
|
end
|
|
|
|
return M
|