Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 6.1.0-dev

- Added percentage-based dimensions to the `resize` command ([#49](https://github.com/vseventer/sharp-cli/issues/49)).

## 6.0.0 (August 21, 2026)

- Added [`--limitInputChannels`](https://sharp.pixelplumbing.com/api-constructor/) input option.
Expand Down
32 changes: 26 additions & 6 deletions cmd/resizing/resize.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,31 @@
import constants from "../../lib/constants.js";
import { pick } from "../../lib/utils.js";

// Helpers.
const resolveDimension = (dimension, metadataDimension) => {
if (dimension == null) return null;
if (typeof dimension === "string" && dimension.endsWith("%")) {
if (metadataDimension == null) {
throw new Error("Input metadata is required to resolve percentage");
}
return Math.round(
(metadataDimension * Number(dimension.slice(0, -1))) / 100,
);
}
return Number(dimension);
};

// Configure.
const positionals = {
width: {
default: null,
desc: "Pixels wide the resultant image should be",
type: "number",
desc: "Pixels or percentage wide the resultant image should be",
type: "string",
},
height: {
default: null,
desc: "Pixels high the resultant image should be",
type: "number",
desc: "Pixels or percentage high the resultant image should be",
type: "string",
},
};

Expand Down Expand Up @@ -94,6 +108,10 @@ const builder = (yargs) => {
"$0 resize --height 100",
"The output will be 100 pixels high, auto-scaled width",
)
.example(
"$0 resize 50%",
"The output will be 50% of the input width, auto-scaled height",
)
.example(
'$0 resize 200 300 --background rgba(255,255,255,0.5) --fit contain --kernel nearest --position "right top"',
"The output will be 200 pixels wide and 300 pixels high containing a nearest-neighbour scaled version contained within the north-east corner of a semi-transparent white canvas",
Expand Down Expand Up @@ -132,8 +150,10 @@ const handler = (args) => {
// @see https://sharp.pixelplumbing.com/api-resize#resize
return args["#queue"].push([
"resize",
(sharp) => {
return sharp.resize(args.width, args.height, pick(args, optionNames));
(sharp, { metadata } = {}) => {
const width = resolveDimension(args.width, metadata?.width);
const height = resolveDimension(args.height, metadata?.height);
return sharp.resize(width, height, pick(args, optionNames));
},
]);
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sharp-cli",
"version": "6.0.0",
"version": "6.1.0-dev",
"description": "CLI for sharp.",
"keywords": [
"cli",
Expand Down
32 changes: 28 additions & 4 deletions test/cmd/resizing/resize.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function register() {

it("must set the width flag", () => {
const args = cli.parsed.argv;
assert.equal(args["width"], width);
assert.equal(args["width"], String(width));
});
it("must update the pipeline", () => {
const pipeline = getPipeline(cli.parsed.argv);
Expand All @@ -80,7 +80,7 @@ export default function register() {

it("must set the height flag", () => {
const args = cli.parsed.argv;
assert.equal(args["height"], height);
assert.equal(args["height"], String(height));
});
it("must update the pipeline", () => {
const pipeline = getPipeline(cli.parsed.argv);
Expand All @@ -100,8 +100,8 @@ export default function register() {
// Tests.
it("must set the width and height flags", () => {
const args = cli.parsed.argv;
assert.equal(args["width"], width);
assert.equal(args["height"], height);
assert.equal(args["width"], String(width));
assert.equal(args["height"], String(height));
});
it("must update the pipeline", () => {
const pipeline = getPipeline(cli.parsed.argv);
Expand All @@ -114,6 +114,30 @@ export default function register() {
});
});

describe("percentage dimensions", () => {
const metadata = { height: 331, width: 500 };

it("must resize width relative to input metadata", async () => {
await cli.parseAsync(["resize", "50%"]);
const pipeline = drain(cli.parsed.argv, { metadata });
sinon.assert.calledWithMatch(pipeline.resize, 250, null);
});

it("must resize width and height relative to input metadata", async () => {
await cli.parseAsync(["resize", "50%", "75%"]);
const pipeline = drain(cli.parsed.argv, { metadata });
sinon.assert.calledWithMatch(pipeline.resize, 250, 248);
});

it("must fail when input metadata is not set", async () => {
await cli.parseAsync(["resize", "50%"]);
assert.throws(
() => drain(cli.parsed.argv),
"metadata is required to resolve percentage",
);
});
});

describe("[options]", () => {
// @see https://sharp.pixelplumbing.com/api-resize#resize
describe("--background", () => {
Expand Down
Loading