image.nvim icon indicating copy to clipboard operation
image.nvim copied to clipboard

Help with rendering images within ObsidianVault

Open exosyphon opened this issue 1 year ago • 3 comments

Hi, thank you for all your work in getting images inside of Neovim. I am really excited to try and get this working.

I am able to use the minimal setup and also configured my setup to work with this style image:

This is a remote image

Inside of my ObsidianVault, I have an Attachments folder at the root where all of my images are stored.

How can I configure image.nvim to look in this directory when it sees something like this?

![[Test.png]]

I am probably missing something obvious or unsure how to configure resolve_image_path to make this work.

Thank you again for your time and work on this plugin!

exosyphon avatar Jul 01 '24 13:07 exosyphon

Hey, we could probably add a hook for the Markdown integration, it must be trying to load the image from the same directory as the document when it should search for it in a special place. You can patch it in the markdown integration meanwhile.

3rd avatar Jul 01 '24 19:07 3rd

Will do. Here's what I have hard coded and got it to render correctly.

          elseif current_image and key == "url" then
            current_image.url = "~/Documents/ObsidianVault/Attachments/" .. value
            table.insert(images, current_image)
            current_image = nil
          end

exosyphon avatar Jul 02 '24 12:07 exosyphon

@exosyphon In case that helps, you can use the resolve_image_path parameter in your config for this:

          resolve_image_path = function(document_path, image_path, fallback)
            local working_dir = vim.fn.getcwd()
            -- Format image path for Obsidian notes
            if (working_dir:find("path/to/your/vault")) then
              return working_dir .. "/" .. image_path
            end
            -- Fallback to the default behavior
            return fallback(document_path, image_path)
          end,

xamcost avatar Sep 27 '24 00:09 xamcost

@exosyphon In case that helps, you can use the resolve_image_path parameter in your config for this:

          resolve_image_path = function(document_path, image_path, fallback)
            local working_dir = vim.fn.getcwd()
            -- Format image path for Obsidian notes
            if (working_dir:find("path/to/your/vault")) then
              return working_dir .. "/" .. image_path
            end
            -- Fallback to the default behavior
            return fallback(document_path, image_path)
          end,

This is very helpful! One addition: find has an optional flag for the start position and a flag whether the string should be interpreted literally. The latter is important, so vault paths like obsidian-vault are detected. So change the line to this:

            if (working_dir:find("path/to/match",1,true)) then

lkschu avatar Nov 17 '24 14:11 lkschu

I'm having a problem getting this to work. The return paths are correct, but the plugin is not rendering any images.

resolve_image_path = function(document_path, image_path, fallback)
	local working_dir = vim.fn.getcwd()
	local notes_dir = vim.fn.expand(vim.env.NOTES_DIR)
	-- Format image path for Obsidian notes
	if working_dir:find(notes_dir, 1, true) then
		return vim.fn.shellescape(notes_dir .. '/' .. image_path)
	end
	-- Fallback to the default behavior
	return fallback(document_path, image_path)
end,

Here is an example doc:

File: $NOTES_DIR/folder/something.md

![[assets/something/hashmd5.png]]

image_path -> assets/something/hashmd5.png

The returned value from the resolve_image_path is correct: '/Users/ahmedelgabri/notes/assets/something/hashmd5.png'

If I run file '/Users/ahmedelgabri/notes/assets/something/hashmd5.png' I get this, so the image exists on disk which means the path is correct.

/Users/ahmedelgabri/notes/assets/something/hashmd5.png: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 5170x2802, components 3

But I don't see any previews, if I go an open a different .md file outside of the notes path, for example a README.md inside a repo, I can see image previews just fine.

ahmedelgabri avatar Nov 18 '24 10:11 ahmedelgabri

@ahmedelgabri I investigated your problem a bit. I had to slightly modify your function to set the environment variable to my own Obsidian path:

  vim.env.NOTES_DIR = "/home/fic/second-brain"

I was able to fix your function by removing the call to vim.fn.shellescape. In case it matter, I am on Linux. Please try the following:

local function resolver_ahmed_fixed(document_path, image_path, fallback)
  -- I had to set my own vault root, but you
  -- shouldn't need this line yourself 
  -- vim.env.NOTES_DIR = "/home/fic/second-brain"
  local working_dir = vim.fn.getcwd()
  local notes_dir = vim.fn.expand(vim.env.NOTES_DIR)
  -- Format image path for Obsidian notes
  if working_dir:find(notes_dir, 1, true) then
    return (notes_dir .. "/" .. image_path)
  end
  -- Fallback to the default behavior
  return fallback(document_path, image_path)
end

With this I was able to get your function to work with the environment variable.

ficd0 avatar Dec 03 '24 23:12 ficd0

Better Obsidian.nvim Integration

If you are like me, you often open Neovim in some other directory like $HOME, and then use obsidian.nvim's Quick Switcher to open your notes. In that case, the previous solutions won't work; the conditional check will fail because the vault path isn't in Neovim's cwd string.

The following solution works for me:

local function resolver(document_path, image_path, fallback)
  local vault_dir = "path/to/your/vault"
  -- Format path for Obsidian vault
  if document_path:find(vault_dir, 1, true) then
    return vault_dir .. "/" .. image_path
  end
  -- fallback to default
  return fallback(document_path, image_path)
end

Simply checking whether the vault path is inside the document_path yields much more consistent results.

ficd0 avatar Dec 03 '24 23:12 ficd0

@ficcdaf thanks, I tried your fix but it still doesn't work. I don't use Obsidian.nvim, but I will give it a look

ahmedelgabri avatar Dec 12 '24 10:12 ahmedelgabri

I found the way to make it work for me, you can try it. Here's the snipppet:

config = function()
    require("image").setup({
        backend = "kitty",
        integrations = {
            markdown = {
                enabled = true,
                clear_in_insert_mode = false,
                download_remote_images = true,
                only_render_image_at_cursor = true,
                filetypes = { "markdown", "vimwiki" }, -- markdown extensions (ie. quarto) can go here
                -- Custom function to resolve image paths
                resolve_image_path = function(document_path, image_path, fallback)
                    -- Define the absolute path to your Assets directory
                    local assets_dir = vim.fn.expand("~/path/to/your/images/dir") -- not the path to vault, but to the assets dir

                    -- Check if the image_path is already an absolute path
                    if image_path:match("^/") then
                        -- If it's an absolute path, leave it unchanged
                        return image_path
                    end

                    -- Construct the new image path by prepending the Assets directory
                    local new_image_path = assets_dir .. "/" .. image_path

                    -- Check if the constructed path exists
                    if vim.fn.filereadable(new_image_path) == 1 then
                        return new_image_path
                    else
                        -- If the file doesn't exist in Assets, fallback to default behavior
                        return fallback(document_path, image_path)
                    end
                end,
            },

iamKimlong avatar Dec 30 '24 14:12 iamKimlong

Sorry cos this is closed, I do it like this.. given that it is about obsidian integration, if you setup obsidian attachments like:

-- Specify how to handle attachments.
attachments = {
    img_folder = "assets/imgs",
    ---@param client obsidian.Client
    ---@param path obsidian.Path the absolute path to the image file
    ---@return string
    img_text_func = function(client, path)
        path = client:vault_relative_path(path) or path
        return string.format("![%s](%s)", path.name, path)
    end,
},

you can use the command ObsidianImgPaste to paste images from clipboard. and it will create a link like setup before in img_text_func.

and you could use this configuration in image:

require("image").setup({
    backend = "kitty",
    kitty_method = "normal",
    integrations = {
        markdown = {
            enabled = true,
            resolve_image_path = function(document_path, image_path, fallback)
                local obsidian_client = require("obsidian").get_client()
                    local new_image_path = obsidian_client:vault_relative_path(image_path).filename
                    if vim.fn.filereadable(new_image_path) == 1 then
                        return new_image_path
                    else
                        return fallback(document_path, image_path)
                    end
            end,

so if you change the assets directory in obsidian it keeps the integration with image. (you can add some checks on the require, in my case obsidian is loaded on markdown files so it just works)

marcocofano avatar Jan 07 '25 16:01 marcocofano

@marcocofano I had to change the integration, the vault_relative_path of an asset doesn't return for me the subfolder...

					local full_path = vault_img_folder .. "/" .. image_path
					local new_image_path = obsidian_client:vault_relative_path(full_path).filename
					vim.notify("new image path " .. new_image_path)

edmondop avatar Jan 20 '25 15:01 edmondop

For anyone needing to format Obsidian links that have size properties set (if you use the Image Converter plugin these are set):

resolve_image_path = function(document_path, image_path, fallback)
	local working_dir = vim.fn.getcwd()
	-- Format image path for Obsidian notes
	local obsidian_client = require("obsidian").get_client()
	if working_dir:find(obsidian_client:vault_root().filename) then
		if image_path:find("|") then
			image_path = vim.split(image_path, "|")[1]
		end
		local assets_dir = "assets"
		local result =
			string.format("%s/%s/%s", obsidian_client:vault_root().filename, assets_dir, image_path)
		return result
	end
	-- Fallback to the default behavior
	return fallback(document_path, image_path)
end,

jonathanmorris180 avatar Feb 04 '25 21:02 jonathanmorris180

Not to necro this too hard, but I found the other solutions all didn't quite work for me. So, expanding a little on @ficcdaf's solution, I got

resolve_image_path = function(document_path, image_path, fallback)
    local obs = require("obsidian").get_client()
    local vault_root = obs:vault_root().filename

    -- If we're in a vault, try using vault behavior
    if document_path:find(vault_root, 1, 1) then
        local obs_rel = obs:vault_relative_path(image_path)

        if (
            obs_rel
            and vim.fn.filereadable(vault_root .. "/" .. obs_rel.filename) == 1
        ) then
            return vault_root .. "/" .. obs_rel.filename
        end

        local attachment_path = document_path:match("(.*/)") .. "attachments/" .. image_path
  
        if vim.fn.filereadable(attachment_path) then
            return attachment_path
        end
    end

    return fallback(document_path, image_path)
end,

I just added a little bit of fallibility so absolute paths work or things that actually were relative to the document path itself. obsidian.Client.vault_relative_path is also documented as having an optional return, though I'm not sure when that actually occurs.

Also added a thing for the subfolder under current folder behavior from the Obsidian editor.

There's still the minor note of this not handling base64 images in markdown correctly atm though, as the fallback is just resolve_absolute_path.

fifty-six avatar Apr 09 '25 21:04 fifty-six

For any still finding their way here:

Obsidian.nvim supports images via snack.nvim, handles all these paths out of the box https://github.com/obsidian-nvim/obsidian.nvim/wiki/Images

WyattJordan avatar Sep 16 '25 19:09 WyattJordan

If for any reason you don't want to install the obsidian plugin but still browse obsidian vaults without specifying the list of vault names, another interesting function is:

resolve_image_path = function(document_path, image_path, fallback)
	local path = fallback(document_path, image_path)
	local working_dir = vim.fn.getcwd()
	local obsidian_conf = working_dir .. "/.obsidian/app.json"
	if vim.uv.fs_stat(obsidian_conf) then
		local fh = io.open(obsidian_conf, "r")
		local json = nil
		if fh then
			json = 	fh:read("a")
			fh:close()
			json = vim.json.decode(json)
		end
		if json and json.attachmentFolderPath then
			path = working_dir .. "/" .. json.attachmentFolderPath .. "/" .. image_path
		end
	end
	return path
end

This works if your working directory is the vaults directory. I find that useful to quickly browse notes with a file picker.

tenllado avatar Nov 14 '25 09:11 tenllado