function image_plugin(imagetools_plugin) {
	// TODO: clear this map in dev mode to avoid memory leak
	/**
	 * URL to image details
	 * @type {Map<string, import('vite-imagetools').Picture>}
	 */
	const images = new Map();

	/** @type {import('vite').ResolvedConfig} */
	let vite_config;

	/** @type {Partial<import('@sveltejs/vite-plugin-svelte').SvelteConfig | undefined>} */
	let svelte_config;

	const name = 'vite-plugin-enhanced-img-markup';

	return {
		name,
		enforce: 'pre',
		async configResolved(config) {
			vite_config = config;
			for (const plugin of config.plugins || []) {
				if (plugin.name === name) {
					break;
				}
				if (plugin.name === 'vite-plugin-svelte') {
					throw new Error(
						'@sveltejs/enhanced-img must come before the Svelte or SvelteKit plugins'
					);
				}
			}
			svelte_config = await loadSvelteConfig();
			if (!svelte_config) throw new Error('Could not load Svelte config file');
		},
		async transform(content, filename) {
			const plugin_context = this;
			const extensions = svelte_config?.extensions || ['.svelte'];
			if (extensions.some((ext) => filename.endsWith(ext))) {
				if (!content.includes('<enhanced:img')) {
					return;
				}

				const s = new MagicString(content);
				const ast = parse(content, { filename, modern: true });

				/**
				 * Import path to import name
				 * e.g. ./foo.png => __IMPORTED_ASSET_0__
				 * @type {Map<string, string>}
				 */
				const imports = new Map();

				/**
				 * @param {import('svelte/compiler').AST.RegularElement} node
				 * @param {AST.Text | AST.ExpressionTag} src_attribute
				 * @returns {Promise<void>}
				 */
				async function update_element(node, src_attribute) {
					if (src_attribute.type === 'ExpressionTag') {
						const start =
							'end' in src_attribute.expression
								? src_attribute.expression.end
								: src_attribute.expression.range?.[0];
						const end =
							'start' in src_attribute.expression
								? src_attribute.expression.start
								: src_attribute.expression.range?.[1];

						if (typeof start !== 'number' || typeof end !== 'number') {
							throw new Error('ExpressionTag has no range');
						}
						const src_var_name = content.substring(start, end).trim();

						s.update(node.start, node.end, dynamic_img_to_picture(content, node, src_var_name));
						return;
					}

					const original_url = src_attribute.raw.trim();
					let url = original_url;

					if (OPTIMIZABLE.test(url)) {
						const sizes = get_attr_value(node, 'sizes');
						const width = get_attr_value(node, 'width');
						url += url.includes('?') ? '&' : '?';
						if (sizes && 'raw' in sizes) {
							url += 'imgSizes=' + encodeURIComponent(sizes.raw) + '&';
						}
						if (width && 'raw' in width) {
							url += 'imgWidth=' + encodeURIComponent(width.raw) + '&';
						}
						url += 'enhanced';
					}

					// resolves the import so that we can build the entire picture template string and don't
					// need any logic blocks
					const resolved_id = (await plugin_context.resolve(url, filename))?.id;
					if (!resolved_id) {
						const query_index = url.indexOf('?');
						const file_path = query_index >= 0 ? url.substring(0, query_index) : url;
						if (existsSync(path.resolve(vite_config.publicDir, file_path))) {
							throw new Error(
								`Could not locate ${file_path}. Please move it to be located relative to the page in the routes directory or reference it beginning with /static/. See https://vitejs.dev/guide/assets for more details on referencing assets.`
							);
						}
						throw new Error(
							`Could not locate ${file_path}. See https://vitejs.dev/guide/assets for more details on referencing assets.`
						);
					}

					if (OPTIMIZABLE.test(url)) {
						let image = images.get(resolved_id);
						if (!image) {
							image = await process_id(resolved_id, plugin_context, imagetools_plugin);
							images.set(resolved_id, image);
						}
						s.update(node.start, node.end, img_to_picture(content, node, image));
					} else {
						const metadata = await sharp(resolved_id).metadata();
						// this must come after the await so that we don't hand off processing between getting
						// the imports.size and incrementing the imports.size
						const name = imports.get(original_url) || '__IMPORTED_ASSET_' + imports.size + '__';
						const new_markup = `<img ${serialize_img_attributes(content, node.attributes, {
							src: `{${name}}`,
							width: metadata.width || 0,
							height: metadata.height || 0
						})} />`;
						s.update(node.start, node.end, new_markup);
						imports.set(original_url, name);
					}
				}

				/**
				 * @type {Array<ReturnType<typeof update_element>>}
				 */
				const pending_ast_updates = [];

				walk(/** @type {import('svelte/compiler').AST.TemplateNode} */ (ast), null, {
					RegularElement(node, { next }) {
						if ('name' in node && node.name === 'enhanced:img') {
							// Compare node tag match
							const src = get_attr_value(node, 'src');

							if (!src || typeof src === 'boolean') return;

							pending_ast_updates.push(update_element(node, src));

							return;
						}

						next();
					}
				});

				await Promise.all(pending_ast_updates);

				// add imports
				let text = '';
				if (imports.size) {
					for (const [path, import_name] of imports.entries()) {
						text += `\timport ${import_name} from "${path}";\n`;
					}
				}

				if (ast.instance) {
					// @ts-ignore
					s.appendLeft(ast.instance.content.start, text);
				} else {
					s.prepend(`<script>${text}</script>\n`);
				}

				if (ast.css) {
					const css = content.substring(ast.css.start, ast.css.end);
					const modified = css.replaceAll('enhanced\\:img', 'img');
					if (modified !== css) {
						s.update(ast.css.start, ast.css.end, modified);
					}
				}

				return {
					code: s.toString(),
					map: s.generateMap()
				};
			}
		}
	};
}
Creates the Svelte image plugin.
@param {import('vite').Plugin<void>} imagetools_plugin
@returns {import('vite').Plugin<void>}