fix: preserve image aspect ratio with cover() and dynamic height calculation
Browse filesReplace non-proportional resize() with cover() in useImage hook, and
compute target height from original aspect ratio in LocalPicture test
instead of hardcoding a terminal-rows-based ratio.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
src/ink-picture/__tests__/LocalPicture.test.tsx
CHANGED
|
@@ -2,37 +2,70 @@
|
|
| 2 |
/**
|
| 3 |
* Visual test: display a local image using ink-picture + Ink.
|
| 4 |
*
|
|
|
|
|
|
|
|
|
|
| 5 |
* Usage:
|
| 6 |
* bun run src/ink-picture/__tests__/LocalPicture.test.tsx
|
| 7 |
*/
|
| 8 |
|
| 9 |
-
import
|
| 10 |
-
import {
|
| 11 |
-
import
|
| 12 |
-
|
| 13 |
-
const IMAGE_PATH = '/home/yuki/Pictures/Wallpapers/3god.jpg'
|
| 14 |
|
| 15 |
-
const
|
| 16 |
-
const rows = process.stdout.rows ?? 40
|
| 17 |
-
const imgWidth = Math.floor(cols * 0.6)
|
| 18 |
-
const imgHeight = Math.floor(rows * 0.4)
|
| 19 |
|
| 20 |
function App() {
|
| 21 |
-
const { exit } = useApp()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
useEffect(() => {
|
| 24 |
-
const timer = setTimeout(() => exit(), 3000)
|
| 25 |
-
return () => clearTimeout(timer)
|
| 26 |
-
}, [exit])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
return (
|
| 29 |
<Box flexDirection="column">
|
| 30 |
<InkPictureProvider>
|
| 31 |
-
<Image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
</InkPictureProvider>
|
| 33 |
</Box>
|
| 34 |
-
)
|
| 35 |
}
|
| 36 |
|
| 37 |
-
const { waitUntilExit } = render(<App />)
|
| 38 |
-
await waitUntilExit()
|
|
|
|
| 2 |
/**
|
| 3 |
* Visual test: display a local image using ink-picture + Ink.
|
| 4 |
*
|
| 5 |
+
* Height is auto-calculated from the original aspect ratio:
|
| 6 |
+
* only width is fixed (60% of terminal columns).
|
| 7 |
+
*
|
| 8 |
* Usage:
|
| 9 |
* bun run src/ink-picture/__tests__/LocalPicture.test.tsx
|
| 10 |
*/
|
| 11 |
|
| 12 |
+
import { Jimp } from "jimp";
|
| 13 |
+
import React, { useEffect, useState } from "react";
|
| 14 |
+
import { render, Box, Text, useApp } from "ink";
|
| 15 |
+
import Image, { InkPictureProvider } from "../index.ts";
|
|
|
|
| 16 |
|
| 17 |
+
const IMAGE_PATH = "/home/yuki/Pictures/Wallpapers/3god.jpg";
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
function App() {
|
| 20 |
+
const { exit } = useApp();
|
| 21 |
+
const [dimensions, setDimensions] = useState<{
|
| 22 |
+
width: number;
|
| 23 |
+
height: number;
|
| 24 |
+
} | null>(null);
|
| 25 |
+
const [err, setErr] = useState(false);
|
| 26 |
+
|
| 27 |
+
useEffect(() => {
|
| 28 |
+
(async () => {
|
| 29 |
+
try {
|
| 30 |
+
const image = await Jimp.read(IMAGE_PATH);
|
| 31 |
+
const origW = image.bitmap.width;
|
| 32 |
+
const origH = image.bitmap.height;
|
| 33 |
+
const cols = process.stdout.columns ?? 80;
|
| 34 |
+
const targetW = Math.floor(cols * 0.6);
|
| 35 |
+
const targetH = Math.floor(targetW * (origH / origW) / 2);
|
| 36 |
+
setDimensions({ width: targetW, height: targetH });
|
| 37 |
+
} catch {
|
| 38 |
+
setErr(true);
|
| 39 |
+
}
|
| 40 |
+
})();
|
| 41 |
+
}, []);
|
| 42 |
|
| 43 |
useEffect(() => {
|
| 44 |
+
const timer = setTimeout(() => exit(), 3000);
|
| 45 |
+
return () => clearTimeout(timer);
|
| 46 |
+
}, [exit]);
|
| 47 |
+
|
| 48 |
+
if (err) {
|
| 49 |
+
return <Text color="red">Failed to load image</Text>;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
if (!dimensions) {
|
| 53 |
+
return <Text>Loading...</Text>;
|
| 54 |
+
}
|
| 55 |
|
| 56 |
return (
|
| 57 |
<Box flexDirection="column">
|
| 58 |
<InkPictureProvider>
|
| 59 |
+
<Image
|
| 60 |
+
src={IMAGE_PATH}
|
| 61 |
+
width={dimensions.width}
|
| 62 |
+
height={dimensions.height}
|
| 63 |
+
alt="3god"
|
| 64 |
+
/>
|
| 65 |
</InkPictureProvider>
|
| 66 |
</Box>
|
| 67 |
+
);
|
| 68 |
}
|
| 69 |
|
| 70 |
+
const { waitUntilExit } = render(<App />);
|
| 71 |
+
await waitUntilExit();
|
src/ink-picture/hooks/useImage.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function useImage<T extends "pixels" | "png" = "pixels">(options: {
|
|
| 43 |
}
|
| 44 |
|
| 45 |
setError(false);
|
| 46 |
-
image.
|
| 47 |
|
| 48 |
if (mode === "png") {
|
| 49 |
const result = await getPngBuffer(image);
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
setError(false);
|
| 46 |
+
image.cover({ w: pixelWidth, h: pixelHeight });
|
| 47 |
|
| 48 |
if (mode === "png") {
|
| 49 |
const result = await getPngBuffer(image);
|