refactor: extract UI from ImageShowTool into separate component
Browse filesSplit ImageShowTool.ts into utility functions (ImageShowTool.ts) and
React component (UI.tsx), enabling reuse of load/calculate logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
src/tools/ImageShowTool/ImageShowTool.ts
CHANGED
|
@@ -1,108 +1,48 @@
|
|
| 1 |
-
#!/usr/bin/env bun
|
| 2 |
-
/**
|
| 3 |
-
* Visual test: display a local image or URL using ink-picture + Ink.
|
| 4 |
-
*
|
| 5 |
-
* Usage:
|
| 6 |
-
* bun run src/ink-picture/__tests__/LocalPicture.test.tsx [path|url]
|
| 7 |
-
*
|
| 8 |
-
* Examples:
|
| 9 |
-
* bun run src/ink-picture/__tests__/LocalPicture.test.tsx ~/Pictures/IMAGE/image.png
|
| 10 |
-
* bun run src/ink-picture/__tests__/LocalPicture.test.tsx https://example.com/image.jpg
|
| 11 |
-
*/
|
| 12 |
-
|
| 13 |
import { Jimp } from "jimp";
|
| 14 |
-
import {
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
if (isUrl) {
|
| 33 |
return loadImageFromUrl(path);
|
| 34 |
} else {
|
| 35 |
return Jimp.read(path);
|
| 36 |
}
|
| 37 |
}
|
| 38 |
|
| 39 |
-
function
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
const
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
const targetW_chars = Math.floor(cols * 0.1618);
|
| 58 |
-
const targetW_pixels = targetW_chars * CELL_WIDTH;
|
| 59 |
-
const targetH_pixels = Math.floor(targetW_pixels * (origH / origW));
|
| 60 |
-
const minH_pixels = 3 * CELL_HEIGHT;
|
| 61 |
-
const finalH_pixels = Math.max(targetH_pixels, minH_pixels);
|
| 62 |
-
const targetH_chars = Math.ceil(finalH_pixels / CELL_HEIGHT);
|
| 63 |
-
|
| 64 |
-
setDimensions({
|
| 65 |
-
width: targetW_chars,
|
| 66 |
-
height: targetH_chars,
|
| 67 |
-
pixelWidth: targetW_pixels,
|
| 68 |
-
pixelHeight: finalH_pixels,
|
| 69 |
-
});
|
| 70 |
-
} catch (e) {
|
| 71 |
-
console.error(e);
|
| 72 |
-
setErr(true);
|
| 73 |
-
exit();
|
| 74 |
-
}
|
| 75 |
-
})();
|
| 76 |
-
}, []);
|
| 77 |
-
|
| 78 |
-
useEffect(() => {
|
| 79 |
-
const handleSigint = () => exit();
|
| 80 |
-
process.on("SIGINT", handleSigint);
|
| 81 |
-
}, [exit]);
|
| 82 |
-
|
| 83 |
-
if (err) {
|
| 84 |
-
return <Text color="red" > Failed to fetch: { IMAGE_PATH } </Text>;
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
if (!dimensions) {
|
| 88 |
-
return <Text>Loading...</Text>;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
return (
|
| 92 |
-
<Box flexDirection= "column" >
|
| 93 |
-
<InkPictureProvider>
|
| 94 |
-
<Image
|
| 95 |
-
src={ IMAGE_PATH }
|
| 96 |
-
width = { dimensions.width }
|
| 97 |
-
height = { dimensions.height }
|
| 98 |
-
pixelWidth = { dimensions.pixelWidth }
|
| 99 |
-
pixelHeight = { dimensions.pixelHeight }
|
| 100 |
-
alt = { isUrl? "url-image": "local-image" }
|
| 101 |
-
/>
|
| 102 |
-
</InkPictureProvider>
|
| 103 |
-
</Box>
|
| 104 |
-
);
|
| 105 |
}
|
| 106 |
-
|
| 107 |
-
const { waitUntilExit } = render(<App />);
|
| 108 |
-
await waitUntilExit();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { Jimp } from "jimp";
|
| 2 |
+
import { loadImageFromUrl } from "../../ink-picture/utils/jimpURL.ts";
|
| 3 |
+
|
| 4 |
+
export const CELL_WIDTH = 8;
|
| 5 |
+
export const CELL_HEIGHT = 16;
|
| 6 |
|
| 7 |
+
export interface ImageDimensions {
|
| 8 |
+
width: number; // 字符宽度
|
| 9 |
+
height: number; // 字符高度
|
| 10 |
+
pixelWidth: number; // 像素宽度
|
| 11 |
+
pixelHeight: number; // 像素高度
|
| 12 |
+
}
|
| 13 |
|
| 14 |
+
export function getImagePath(args: string[]): string {
|
| 15 |
+
return args[0] || "/home/yuki/Pictures/Wallpapers/3god.jpg";
|
| 16 |
+
}
|
| 17 |
|
| 18 |
+
export function isUrl(path: string): boolean {
|
| 19 |
+
return path.startsWith("http://") || path.startsWith("https://");
|
| 20 |
+
}
|
| 21 |
|
| 22 |
+
export async function loadImage(path: string) {
|
| 23 |
+
if (isUrl(path)) {
|
|
|
|
| 24 |
return loadImageFromUrl(path);
|
| 25 |
} else {
|
| 26 |
return Jimp.read(path);
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
| 30 |
+
export function calculateDimensions(
|
| 31 |
+
imageWidth: number,
|
| 32 |
+
imageHeight: number,
|
| 33 |
+
terminalCols: number
|
| 34 |
+
): ImageDimensions {
|
| 35 |
+
const targetW_chars = Math.floor(terminalCols * 0.1618);
|
| 36 |
+
const targetW_pixels = targetW_chars * CELL_WIDTH;
|
| 37 |
+
const targetH_pixels = Math.floor(targetW_pixels * (imageHeight / imageWidth));
|
| 38 |
+
const minH_pixels = 3 * CELL_HEIGHT;
|
| 39 |
+
const finalH_pixels = Math.max(targetH_pixels, minH_pixels);
|
| 40 |
+
const targetH_chars = Math.ceil(finalH_pixels / CELL_HEIGHT);
|
| 41 |
+
|
| 42 |
+
return {
|
| 43 |
+
width: targetW_chars,
|
| 44 |
+
height: targetH_chars,
|
| 45 |
+
pixelWidth: targetW_pixels,
|
| 46 |
+
pixelHeight: finalH_pixels,
|
| 47 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
|
|
|
|
|
|
|
|