Skip to content

feat(handlers): add opacity and the missing sharp operation modifiers - #322

Merged
pi0 merged 6 commits into
mainfrom
feat/new-modifiers
Jul 27, 2026
Merged

feat(handlers): add opacity and the missing sharp operation modifiers#322
pi0 merged 6 commits into
mainfrom
feat/new-modifiers

Conversation

@pi0x

@pi0x pi0x commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Closes the gaps between the modifiers and sharp's operation API, and adds opacity, which sharp does not provide directly.

Resolves #240 (opacity / brightness, plus background so the output is not limited to PNG). #187 (rounded edges) is not addressed here — see the note at the bottom.

New modifiers

Modifier Notes
opacity_0.75 sharp has no opacity operation, so a uniformly transparent single pixel overlay is composited with dest-in (which multiplies the alpha channel). Required argument between 0 and 1.
brightness, saturation, hue, lightness Each maps to the matching modulate option. sharp merges the calls, so /brightness_2,hue_90/ stays a single modulate operation. Addressing one option positionally (/modulate___90/) was awkward.
autoorient, dilate, erode, clahe, linear

No aliases: one spelling per modifier, the American one where sharp offers both. Every modifier name is a single lowercase word, so URL casing never matters.

Existing modifiers

They now take the arguments they were missing:

  • sharpenx1, y2, y3
  • blurprecision, minAmplitude
  • negatealpha
  • normalizelower, upper
  • thresholdgrayscale

background / b gained one more consumer. opacity otherwise produces transparency, which needs an output format with an alpha channel; with a background set it blends the image into it instead (/opacity_0.75,b_fff,f_jpeg/), so the output stays opaque — this is the "avoid limiting the output to PNG" half of #240.

Implementation notes

  • Argument ranges keep mirroring the ones sharp enforces, so invalid input is rejected with a 400 IPX_INVALID_MODIFIER_ARG instead of surfacing as a 500. Two deliberate deviations, both documented: dilate, erode and clahe are capped at 100 rather than sharp's 65536 (the cost grows with the window — dilate_1000 alone takes minutes on a 4K image), and the clahe window is clamped to the source, since libvips rejects one larger than the image.
  • Late libvips failures are now a 400. Some arguments are only validated once the pipeline runs, after every handler has been applied, so they escaped applyHandler's existing wrapping. This also covers pre-existing cases such as /extract_0_0_99999_99999/, which was a 500 on main.
  • Colours are parsed up front. opacity composites its overlay through an SVG, where an unknown name would silently render as black rather than being rejected the way /flatten,b_notacolour/ is.
  • Colours interpolated into the overlay are safe. VColor only lets a hex, functional or named colour through, none of which can contain a quote or an angle bracket.
  • Booleans (negate, threshold) accept true / false and the shorter 1 / 0.
  • IPXModifiers flag modifiers now admit the "" they are actually parsed from, so { animated: "" } type checks.

Deliberately left out

affine, convolve and recomb take a matrix, which fits a URL modifier poorly and is unbounded in cost. boolean takes a second image by path or buffer.

On rounded corners (#187)

round was implemented here and then reverted (62f46b1). It is the only modifier in this set that cannot be expressed as operations on the pipeline: sharp has no rounded corners operation, and the mask has to match the output dimensions, which are only known once the pipeline has run. That forced a raw round-trip and a second sharp pipeline, and with it a set of problems none of the other modifiers have — an unstreamed full-size buffer to bound (an animated image stacks every frame, so maxOutputDimension does not cover it), frame delays and loop count to carry over by hand, and an extra decode per request. Left for a follow-up that can do it in one pass; #187 stays open.

Tests

opacity is composited rather than mapped to a sharp operation, so it is asserted on the pixels themselves (the alpha is halved; with a background the result is the blend and stays opaque). Every other new modifier and argument is exercised end-to-end against real sharp — mocking it hides its own validation — plus the usual valid/invalid argument tables.

361 passing, eslint and prettier clean. tsc --noEmit reports only the three pre-existing examples/*.ts errors that need pnpm build first.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added modifiers: autoOrient, dilate, erode, clahe, linear, and per-channel brightness, saturation, hue, lightness.
    • Extended sharpen, blur, and opacity capabilities.
  • Bug Fixes
    • Improved modifier validation with earlier, clearer HTTP 400 errors for invalid/missing arguments.
    • Enhanced opacity compositing (including proper background blending and safer alpha handling).
    • Updated negate, normalize, threshold, and background/tint parsing (now accepts true/false in addition to 1/0).
  • Documentation
    • Refreshed the Modifiers section with updated rules, constraints, and aliases.

…difiers

Closes the gaps between the modifiers and sharp's operation API, and adds
the two operations sharp does not provide directly.

New modifiers:

- `opacity` (#240), composited since sharp has no opacity operation. The
  image is made transparent, or blended into the `background` colour when
  one is set so that formats without an alpha channel work too.
- `round` (#187), rounds the corners with a given radius, or as round as
  the image can take without one (a circle when square, a pill otherwise).
  The mask has to match the output dimensions, which are only known once
  the pipeline has run, so it is applied last. Cut off corners are made
  transparent, or filled with the `background` colour when one is set.
- `brightness` (#240), `saturation`, `hue` and `lightness`, each mapping to
  the matching `modulate` option. sharp merges the calls, so combining them
  stays a single operation.
- `autoOrient`, `dilate`, `erode`, `clahe` and `linear`.
- `autoorient`, `normalise` and `greyscale` aliases.

Existing modifiers now take the arguments they were missing: `sharpen`
(`x1`, `y2`, `y3`), `blur` (`precision`, `minAmplitude`), `negate`
(`alpha`), `normalize` (`lower`, `upper`) and `threshold` (`greyscale`).

Ranges keep mirroring the ones sharp enforces, so invalid arguments are
still rejected with a `400` rather than surfacing as a `500`. `dilate` and
`erode` are capped below sharp's maximum, in line with `median`, since the
mask grows with the width.

`affine`, `convolve`, `recomb` and `boolean` are deliberately left out:
the first three take a matrix, which fits a URL modifier poorly and is
unbounded in cost, and `boolean` takes a second image by path or buffer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pi0x
pi0x requested a review from pi0 as a code owner July 26, 2026 23:26
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Modifier support now includes additional Sharp operations, boolean argument parsing, opacity compositing, eager background validation, normalized modifier errors, expanded public types, updated documentation, and broader unit and integration coverage.

Changes

Image modifier expansion

Layer / File(s) Summary
Modifier contracts and error normalization
src/handlers/utils.ts, src/ipx.ts
Boolean arguments, presence-based flag types, new modifier fields, and IPX_INVALID_MODIFIER error conversion are added.
Sharp handler operations
src/handlers/handlers.ts
Orientation, blur, sharpening, morphology, contrast, channel modulation, threshold, normalization, and linear handlers are added or updated.
Opacity compositing and pipeline validation
src/handlers/handlers.ts, src/handlers/utils.ts, src/ipx.ts
Opacity uses tiled overlays and alpha handling; background values and final Sharp conversion errors are normalized as modifier errors.
Handler and integration validation
test/handlers/handlers.test.ts, test/index.test.ts, README.md
Tests and modifier documentation cover expanded arguments, aliases, opacity blending, and invalid-input responses.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant IPX
  participant Handler
  participant opacityOverlay
  participant Sharp
  Request->>IPX: Submit image modifiers
  IPX->>Handler: Apply modifier arguments
  Handler->>opacityOverlay: Build opacity overlay
  opacityOverlay->>Sharp: Composite overlay
  Sharp->>IPX: Return image buffer or modifier error
Loading

Possibly related PRs

  • unjs/ipx#248: Both changes update IPXModifiers presence-style flag typing for flatten/unflatten-related modifiers.
  • unjs/ipx#321: Both changes expand modifier argument mapping and normalize modifier errors.

Suggested reviewers: pi0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding opacity and missing Sharp operation modifiers.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/new-modifiers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/handlers/handlers.ts (1)

434-444: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

normalize passes explicit undefined keys while the neighbouring handlers deliberately omit them.

blur, negate and threshold all strip undefined args because "sharp validates with key in options". normalize does the opposite. It currently works (tests pass), but the inconsistency invites a future regression if sharp tightens validation here.

♻️ Optional consistency tweak
   apply: (_context, pipe, lower, upper) => {
-    return pipe.normalize({ lower, upper });
+    return pipe.normalize({
+      ...(lower === undefined ? {} : { lower }),
+      ...(upper === undefined ? {} : { upper }),
+    });
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/handlers.ts` around lines 434 - 444, Update the normalize
handler’s apply function to omit lower or upper when their arguments are
undefined, matching the established behavior in blur, negate, and threshold
while preserving provided values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ipx.ts`:
- Around line 69-99: Update the flag modifier types in the relevant options
definition, including autoOrient, autoorient, flip, flop, greyscale, animated,
a, enlarge, flatten, and unflatten, to accept the empty string alongside true
and "true". Define and reuse a shared FlagModifier alias for consistency across
the entire flag family.

---

Nitpick comments:
In `@src/handlers/handlers.ts`:
- Around line 434-444: Update the normalize handler’s apply function to omit
lower or upper when their arguments are undefined, matching the established
behavior in blur, negate, and threshold while preserving provided values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1feae65e-302e-4c77-a4e7-159a611b57e6

📥 Commits

Reviewing files that changed from the base of the PR and between 97e6eb7 and 623da37.

📒 Files selected for processing (7)
  • README.md
  • src/handlers/handlers.ts
  • src/handlers/utils.ts
  • src/ipx.ts
  • src/types.ts
  • test/handlers/handlers.test.ts
  • test/index.test.ts

Comment thread src/ipx.ts Outdated
Follow-up to the review of the modifiers added in the previous commit.

- `round` had to be bounded. It materializes the whole output as raw
  pixels, unlike the rest of the pipeline, which libvips streams, and
  `maxOutputDimension` only bounds a single page: an animated image
  stacks every frame into one tall image, so `/a,enlarge,s_4096x4096,
  round_20/x.gif` allocated 1.5GB and then failed with a `500` because
  the mask overlay busted the sharp input limit. The output size is now
  bounded from the source dimensions and page count before anything is
  allocated (`400 IPX_OUTPUT_TOO_LARGE`, 1.6GB peak down to nothing),
  the overlay opts out of the input limit, and a second check on the
  measured size covers the rest.
- `round` dropped the frame delays and loop count of an animated image,
  since they do not survive the raw round-trip. They are carried over to
  the output when it is a gif or webp.
- `clahe` accepted a window larger than the image, which libvips rejects
  once it runs ("window too large") -- an unhandled `500`. It is now
  clamped to the source, and any other late libvips failure is turned
  into a `400`, which also covers pre-existing ones such as an `extract`
  outside the image.
- `clahe`, `dilate` and `erode` are capped at `100` rather than sharp's
  `65536`: the cost grows with the window, and `dilate_1000` alone took
  minutes.
- An unknown `background` colour silently rendered as black in the
  `round` and `opacity` overlays instead of being rejected. Colours are
  now parsed up front, so they fail the same way everywhere.
- `linear` and `brightness` / `saturation` no longer reject values sharp
  accepts.
- `IPXModifiers` flag modifiers admit the `""` they are parsed from, so
  `{ animated: "" }` type checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pi0x

pi0x commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Review pass on 27b08ea — every finding was reproducible and is fixed.

round was not bounded (the serious one). It materializes the whole output as raw pixels, unlike the rest of the pipeline which libvips streams, and maxOutputDimension only bounds a single page — an animated image stacks every frame into one tall image. /a,enlarge,s_4096x4096,round_20/giphy.gif allocated ~1.5GB and then 500'd, because the mask overlay busted sharp's default input limit. Now:

  • the output size is bounded from the source dimensions and page count before anything is allocated (400 IPX_OUTPUT_TOO_LARGE) — peak RSS on that request went from 1608MB/12s to nothing/2ms, and /a,round_20/giphy.gif is unaffected;
  • the overlay opts out of the input limit, and a second check on the measured size backs the first one up.

round dropped animation timing. The raw round-trip carries no frame delays or loop count, so a rounded GIF played at full speed and stopped after one pass. They are now carried over for gif and webp output. The existing test only asserted pages/pageHeight and passed straight through this — it now asserts delay and loop too, for both formats.

clahe accepted a window larger than the image, which libvips rejects only once it runs (hist_local: window too large) — an unhandled 500 from /clahe_5000/. The window is clamped to the source, and any late libvips failure is now turned into a 400, which also covers pre-existing ones such as /extract_0_0_99999_99999/.

Cost caps. clahe, dilate and erode are capped at 100 instead of sharp's 65536dilate_1000 took minutes on a 4K image. Documented as a deliberate deviation.

Unknown colours rendered as black. b_notacolour reaches librsvg through the round / opacity overlays, which silently falls back to black instead of erroring; /flatten,b_notacolour/ correctly 400s. Colours are now parsed up front, so every modifier fails the same way.

Ranges. linear's a/b and brightness/saturation no longer reject values sharp accepts.

Types. IPXModifiers flag modifiers now admit the "" they are actually parsed from, so { animated: "" } type checks — tsc --noEmit is clean again apart from the three pre-existing examples/*.ts errors.

Not changed: the tiled per-page overlay looked like a cheaper alternative for animated masks, but tile + dest-in only applies for odd page counts (verified 1–20), so the per-page rects stay.

378 passing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/ipx.ts (1)

88-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

FlagModifier | string collapses to true | string.

The bare string swallows the ""/"true" literals, losing the editor hints that (string & {}) preserves elsewhere in this interface (see kernel, fit).

♻️ Suggested tweak
-  negate: FlagModifier | string;
-  normalize: FlagModifier | string;
+  negate: FlagModifier | (string & {});
+  normalize: FlagModifier | (string & {});
   // alias for normalize
-  normalise: FlagModifier | string;
+  normalise: FlagModifier | (string & {});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ipx.ts` around lines 88 - 91, Update the FlagModifier fields negate,
normalize, and normalise to use the same string-literal-preserving type pattern
as kernel and fit, such as a string intersection that retains editor hints,
instead of a bare string union that collapses to true | string.
test/index.test.ts (1)

110-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

round: "" may not exercise the rounding branch.

src/ipx.ts gates on handlerContext.round !== undefined. If a bare flag maps to undefined, this case only asserts that a buffer comes back. Consider asserting a transparent corner pixel here (or confirming the default radius) so the no-arg form is genuinely covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.test.ts` at line 110, Update the test case using round: { round:
"", format: "png" } to verify the rounding behavior rather than only successful
output. Assert a transparent corner pixel or the expected default radius,
ensuring the bare round flag maps to the branch guarded by handlerContext.round
!== undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/handlers/utils.ts`:
- Around line 317-320: Update the pre-allocation estimate around the output
destructuring and width/height calculations so arbitrary-angle rotate operations
are treated as canGrow, or otherwise account for their rotated bounding-box
dimensions. Ensure animated inputs cannot materialize a raw buffer whose
estimated area exceeds max * max before applyRoundedCorners performs its
measured check.

---

Nitpick comments:
In `@src/ipx.ts`:
- Around line 88-91: Update the FlagModifier fields negate, normalize, and
normalise to use the same string-literal-preserving type pattern as kernel and
fit, such as a string intersection that retains editor hints, instead of a bare
string union that collapses to true | string.

In `@test/index.test.ts`:
- Line 110: Update the test case using round: { round: "", format: "png" } to
verify the rounding behavior rather than only successful output. Assert a
transparent corner pixel or the expected default radius, ensuring the bare round
flag maps to the branch guarded by handlerContext.round !== undefined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 23961a10-8a17-4ec3-a119-52ba65a7dbc1

📥 Commits

Reviewing files that changed from the base of the PR and between 623da37 and 27b08ea.

📒 Files selected for processing (6)
  • README.md
  • src/handlers/handlers.ts
  • src/handlers/utils.ts
  • src/ipx.ts
  • test/handlers/handlers.test.ts
  • test/index.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/handlers/handlers.test.ts
  • src/handlers/handlers.ts

Comment thread src/handlers/utils.ts Outdated
`round` is the only modifier here that cannot be expressed as operations
on the pipeline: sharp has no rounded corners operation, and the mask it
has to be composited from must match the *output* dimensions, which are
only known once the pipeline has run. That forced a raw round-trip and a
second sharp pipeline, and with it a set of problems the rest of the
modifiers do not have -- an unstreamed full-size buffer to bound, frame
timing to carry over by hand, and an extra decode per request.

Removed until it can be done in one pass. #187 stays open.

Everything the review turned up that is not specific to it stays: the
`clahe` window clamp and cost caps, late libvips failures surfacing as a
`400`, eager colour parsing (`opacity` composites an SVG overlay too, so
an unknown name would otherwise render as black), the `linear` and
`brightness` / `saturation` ranges, and the `IPXModifiers` flag types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pi0x pi0x changed the title feat(handlers): add opacity, round and the missing sharp operation modifiers feat(handlers): add opacity and the missing sharp operation modifiers Jul 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ipx.ts (1)

461-477: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate background with the same parser used for the SVG overlay.

opacityOverlay() interpolates background directly into an SVG fill attribute and passes it through Sharp’s SVG loader, while this validation only uses Sharp’s create.background parser. Reject any color accepted by VColor() that Sharp can later fail to parse when rasterizing the SVG, or validate at VColor() with the SVG/Sharp rasterization parser.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ipx.ts` around lines 461 - 477, Update the background validation in the
handler flow around opacityOverlay and VColor to use the same SVG/Sharp
rasterization parsing path that ultimately processes the interpolated fill
value, rather than only Sharp’s create.background parser. Ensure colors accepted
by VColor cannot later fail during SVG rasterization, while preserving the
existing asModifierError conversion for validation failures.
🧹 Nitpick comments (1)
src/handlers/utils.ts (1)

300-324: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Compositing math checks out.

The over+fill-opacity branch and the dest-in+alpha branch both correctly implement image·opacity + background·(1-opacity) and "scale existing alpha by opacity" respectively.

One nit: opacityOverlay doesn't clamp/validate opacity itself (e.g. 1 - opacity could go negative if a caller passes an out-of-range value directly, since this is an exported helper). If range validation is guaranteed to happen in the calling handler before this is invoked, this is fine to leave as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/utils.ts` around lines 300 - 324, Update the exported
opacityOverlay function to validate or clamp opacity to the supported range
before using it in fill-opacity or overlay alpha calculations, ensuring direct
callers cannot produce out-of-range values. Preserve the existing background and
dest-in compositing behavior for valid opacity values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/ipx.ts`:
- Around line 461-477: Update the background validation in the handler flow
around opacityOverlay and VColor to use the same SVG/Sharp rasterization parsing
path that ultimately processes the interpolated fill value, rather than only
Sharp’s create.background parser. Ensure colors accepted by VColor cannot later
fail during SVG rasterization, while preserving the existing asModifierError
conversion for validation failures.

---

Nitpick comments:
In `@src/handlers/utils.ts`:
- Around line 300-324: Update the exported opacityOverlay function to validate
or clamp opacity to the supported range before using it in fill-opacity or
overlay alpha calculations, ensuring direct callers cannot produce out-of-range
values. Preserve the existing background and dest-in compositing behavior for
valid opacity values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 220c12cf-c78b-4386-a3a4-c592368a6f58

📥 Commits

Reviewing files that changed from the base of the PR and between 27b08ea and 62f46b1.

📒 Files selected for processing (6)
  • README.md
  • src/handlers/handlers.ts
  • src/handlers/utils.ts
  • src/ipx.ts
  • test/handlers/handlers.test.ts
  • test/index.test.ts
💤 Files with no reviewable changes (2)
  • test/handlers/handlers.test.ts
  • src/handlers/handlers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

`autoorient`, `normalise` and `greyscale` were aliases carrying their
own weight in `IPXModifiers` and the docs while adding nothing: one
spelling per modifier is enough. `autoOrient`, `normalize` and
`grayscale` are the names kept.

The `threshold` boolean argument is renamed `grayscale` to match. sharp
accepts either spelling for it, so the behaviour is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/handlers/handlers.ts (1)

422-466: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the no-argument normalize form.

normalize requires both lower and upper, so a bare modifier path fails before Sharp runs. Sharp’s normalize API accepts omitted options with default bounds, so accept the empty form and only validate the supplied range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/handlers/handlers.ts` around lines 422 - 466, Update the normalize
handler’s argument definition and apply logic to accept a no-argument form,
invoking Sharp’s default normalize behavior when both lower and upper are
omitted. Validate and pass the supplied lower/upper range when provided, while
preserving the existing invalid-range rejection behavior.

Source: MCP tools

🧹 Nitpick comments (1)
test/index.test.ts (1)

176-185: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the opaque-output opacity path.

This test hard-codes format: "png", so it does not exercise the advertised background compositing behavior for an opaque output. Add a jpg/jpeg case or parameterize the test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.test.ts` around lines 176 - 185, Add coverage for the
opaque-output background compositing path in the test describing blending into
the background: use a JPEG format, or parameterize the existing PNG case to
include JPEG, while preserving the transparent PNG assertion and verifying the
advertised green background result for the opaque output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 269: Update the threshold documentation row to hyphenate “single-channel
grayscale” in the description, leaving the surrounding wording and examples
unchanged.

---

Outside diff comments:
In `@src/handlers/handlers.ts`:
- Around line 422-466: Update the normalize handler’s argument definition and
apply logic to accept a no-argument form, invoking Sharp’s default normalize
behavior when both lower and upper are omitted. Validate and pass the supplied
lower/upper range when provided, while preserving the existing invalid-range
rejection behavior.

---

Nitpick comments:
In `@test/index.test.ts`:
- Around line 176-185: Add coverage for the opaque-output background compositing
path in the test describing blending into the background: use a JPEG format, or
parameterize the existing PNG case to include JPEG, while preserving the
transparent PNG assertion and verifying the advertised green background result
for the opaque output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32852e55-ff4a-4ea3-be1f-fc7569d0c271

📥 Commits

Reviewing files that changed from the base of the PR and between 62f46b1 and 33f29ad.

📒 Files selected for processing (5)
  • README.md
  • src/handlers/handlers.ts
  • src/ipx.ts
  • test/handlers/handlers.test.ts
  • test/index.test.ts
💤 Files with no reviewable changes (1)
  • src/ipx.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/handlers/handlers.test.ts

Comment thread README.md
Modifier names are matched case sensitively, and `autoOrient` was the
only one that was not a single lowercase word, so it was the only one
where the casing in a URL mattered. Renamed to match the rest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`clahe` took ~4.3s on the full 3840x2160 source locally and tipped over
the 5s default timeout on CI, which is slower and runs under coverage
instrumentation. `opacity` was next in line at ~3.8s, spent encoding a
full size png.

Both are in the table that exercises argument handling against real
sharp, so the source size is incidental: resizing first takes them to
~40ms without changing what they cover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.53086% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.97%. Comparing base (97e6eb7) to head (0d5bc68).

Files with missing lines Patch % Lines
src/handlers/utils.ts 90.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #322      +/-   ##
==========================================
+ Coverage   91.33%   91.97%   +0.64%     
==========================================
  Files           9        9              
  Lines         669      735      +66     
  Branches      196      218      +22     
==========================================
+ Hits          611      676      +65     
- Misses         49       50       +1     
  Partials        9        9              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pi0
pi0 merged commit e8fe081 into main Jul 27, 2026
10 checks passed
@pi0
pi0 deleted the feat/new-modifiers branch July 27, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add new modifiers for opacity and brightness

2 participants