Skip to content

feat: add the ability to generate "collapsed" version of mods#233

Merged
SlayerOrnstein merged 7 commits intoWFCD:mainfrom
SlayerOrnstein:feat-coll
Feb 25, 2026
Merged

feat: add the ability to generate "collapsed" version of mods#233
SlayerOrnstein merged 7 commits intoWFCD:mainfrom
SlayerOrnstein:feat-coll

Conversation

@SlayerOrnstein
Copy link
Member

@SlayerOrnstein SlayerOrnstein commented Feb 25, 2026

What did you fix?

Adds the ability to generate smaller "collapsed" versions

image image image

Reproduction steps


Evidence/screenshot/link to line

Considerations

  • Does this contain a new dependency? No
  • Does this introduce opinionated data formatting or manual data entry? Yes
  • Does this pr include updated data files in a separate commit that can be reverted for a clean code-only pr? Yes
  • Have I run the linter? No
  • Is is a bug fix, feature request, or enhancement? Feature

Summary by CodeRabbit

  • New Features

    • Mod images can be generated in two display variants: collapsed and expanded; optional rank and set-bonus rendering added.
  • Documentation

    • README rewritten with supported mod types, collapsed/expanded examples, and updated usage/output paths.
  • Style

    • Font sizes adjusted for improved readability; new name-font introduced.
  • Bug Fixes

    • Improved image shading and tinting for more consistent color rendering.
  • Tests

    • Tests updated to produce and validate both collapsed and expanded outputs.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Warning

Rate limit exceeded

@SlayerOrnstein has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 52 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c9574e1 and 1d08711.

📒 Files selected for processing (1)
  • README.md
📝 Walkthrough

Walkthrough

Adds a new exported generateCollapsed renderer and accompanying collapsed/expanded workflow; introduces pixel-manipulation helpers (shadePixel, tintPixel, shadeImage), font updates, drawing adjustments, README/example updates, and test changes. A "collpased" typo appears in example/test output paths.

Changes

Cohort / File(s) Summary
Documentation
README.md
Rewrote description to "Assembles Mod Assets into full/collapsed mod images"; added "Supported Mod Types"; added Collapsed/Expanded example images; updated example usage to produce both expanded and collapsed outputs and adjusted paths (typo "collpased" present).
Styling & Constants
src/styling.ts
Bumped titleFont from 20px→22px and descriptionFont 12px→14px; added compatNameFont (16px, weight 500).
Color & Pixel Utilities
src/utils.ts
Simplified hexToRGB; added shadePixel(pixel, percentage) and tintPixel(pixel, tint, percentage?) for per-pixel shading/tinting.
Drawing & Rendering
src/drawers.ts
Reworked header tinting to use tintPixel; added exported shadeImage(image); switched lower tab font to compatNameFont and uppercased compat name; adjusted layout constants (backgroundImage maxWidth 0.80→0.85, bottomImage rankSlotStart 0.43→0.425).
Generation Logic
src/generator.ts
Added exported generateCollapsed(props: GenerateModProps) rendering pipeline; extended GenerateModProps with optional rank? and setBonus?; updated generate flow to register fonts and handle shaded thumbnails.
Tests
tests/generator.spec.ts
Imported generateCollapsed; updated tests to generate and write both expanded and collapsed outputs per mod; updated expectations to account for 2× outputs.

Sequence Diagram

sequenceDiagram
    participant User
    participant Generator
    participant ImageOps
    participant Canvas

    User->>Generator: call generateCollapsed(mod props)
    Generator->>ImageOps: loadImage(imageName)
    ImageOps-->>Generator: Image
    Generator->>ImageOps: shadeImage(Image)
    ImageOps->>ImageOps: apply shadePixel per-pixel
    ImageOps-->>Generator: shaded Image
    Generator->>Canvas: create canvas & drawHeader (tint via tintPixel)
    Generator->>Canvas: drawBottomImage / layout elements
    Generator->>Canvas: export PNG buffer
    Canvas-->>Generator: PNG buffer
    Generator-->>User: return Buffer
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • TobiTenno

Poem

🐰
I hop through pixels, shade and blend,
Two little images from one I send.
Collapsed and expanded, side by side—
Fonts snug, colors tinted, thumbnails dyed.
A carrot-sized commit, with joy I hide.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature addition: the ability to generate collapsed versions of mods, which is the central change across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
tests/generator.spec.ts (1)

30-45: ⚠️ Potential issue | 🟠 Major

Fix the extra loop iteration and tighten collapsed-output assertions.

Line 30 should use < mods.length to avoid the out-of-range iteration; also assert modMin and fix the _collpased typo at Line 44.

✅ Proposed patch
-    for (let i = 0; i <= mods.length; i += 1) {
+    for (let i = 0; i < mods.length; i += 1) {
       await Promise.all(
         formats.map(async (format) => {
@@
-          const modFull = await generate({ mod, rank: mod.fusionLimit, output: { format } });
-          const modMin = await generateCollapsed({ mod, rank: mod.fusionLimit, output: { format } });
+          const modFull = await generate({ mod, rank: mod.fusionLimit, output: { format } });
+          const modMin = await generateCollapsed({ mod, rank: mod.fusionLimit, output: { format } });
           assert.ok(modFull);
+          assert.ok(modMin);
@@
-          if (modMin) await writeFile(join(imagePath, `${mod.name.replaceAll(' ', '_')}_collpased.${format}`), modMin);
+          if (modMin) await writeFile(join(imagePath, `${mod.name.replaceAll(' ', '_')}_collapsed.${format}`), modMin);
         })
       );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/generator.spec.ts` around lines 30 - 45, The for-loop iterates one too
many times and the collapsed output assertions/filename are incorrect: change
the loop condition to use i < mods.length (instead of <=) to avoid out-of-range
access when calling find.findItem(mods[i]), add an assertion assert.ok(modMin)
after generating modMin (from generateCollapsed) to tighten the test, and fix
the output filename typo from "_collpased" to "_collapsed" when calling
writeFile; reference the generate, generateCollapsed, modFull, modMin, mods,
find.findItem, and writeFile usages to locate the changes.
🧹 Nitpick comments (2)
src/utils.ts (2)

208-214: Add fail-fast validation in hexToRGB.

This helper is exported; validating input format prevents silent NaN channel values when callers pass malformed hex.

♻️ Proposed patch
 export const hexToRGB = (hex: string): RGB => {
+  if (!/^#[0-9a-fA-F]{6}$/.test(hex)) {
+    throw new Error(`Invalid hex color: ${hex}`);
+  }
   const r = parseInt(hex.slice(1, 3), 16);
   const g = parseInt(hex.slice(3, 5), 16);
   const b = parseInt(hex.slice(5, 7), 16);

   return { r, g, b };
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils.ts` around lines 208 - 214, The exported helper hexToRGB currently
parses slices without validating input, which can produce NaN for malformed hex
strings; update hexToRGB to validate the input first (e.g. test against a regex
like /^#([0-9A-Fa-f]{6})$/), throw a clear Error when the format is invalid, and
only then parse the r,g,b channels with parseInt and return the RGB object so
callers fail fast on bad input.

217-237: Clamp percentage/channel outputs in pixel helpers for safer reuse.

shadePixel/tintPixel currently rely on caller discipline; clamping avoids out-of-range values when these utilities are reused.

♻️ Proposed patch
 export const shadePixel = (pixel: RGB, percentage: number): RGB => {
-  const redShade = Math.round(Math.max(0, pixel.r - pixel.r * percentage));
-  const greenShade = Math.round(Math.max(0, pixel.g - pixel.g * percentage));
-  const blueShade = Math.round(Math.max(0, pixel.b - pixel.b * percentage));
+  const p = Math.min(1, Math.max(0, percentage));
+  const redShade = Math.round(Math.min(255, Math.max(0, pixel.r - pixel.r * p)));
+  const greenShade = Math.round(Math.min(255, Math.max(0, pixel.g - pixel.g * p)));
+  const blueShade = Math.round(Math.min(255, Math.max(0, pixel.b - pixel.b * p)));

   return { r: redShade, g: greenShade, b: blueShade };
 };
@@
 export const tintPixel = (pixel: RGB, tint: RGB, percentage: number = 0.8): RGB => {
   const maxColors = 255;
+  const p = Math.min(1, Math.max(0, percentage));
@@
-  const tintedRed = Math.min(maxColors, pixel.r * originalWeight + tint.r * brightness * percentage);
-  const tintedGreen = Math.min(maxColors, pixel.g * originalWeight + tint.g * brightness * percentage);
-  const tintedBlue = Math.min(maxColors, pixel.b * originalWeight + tint.b * brightness * percentage);
+  const tintedRed = Math.round(Math.min(maxColors, Math.max(0, pixel.r * originalWeight + tint.r * brightness * p)));
+  const tintedGreen = Math.round(Math.min(maxColors, Math.max(0, pixel.g * originalWeight + tint.g * brightness * p)));
+  const tintedBlue = Math.round(Math.min(maxColors, Math.max(0, pixel.b * originalWeight + tint.b * brightness * p)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils.ts` around lines 217 - 237, Clamp inputs and outputs in shadePixel
and tintPixel: validate/normalize the percentage parameter to the 0..1 range
(e.g., percentage = Math.max(0, Math.min(1, percentage)) within each function),
ensure computed channel values are integers and clamped to 0..255 before
returning (use Math.round and Math.max/Math.min on each of red/green/blue
results), and apply the same clamping for tintPixel's computed
tintedRed/tintedGreen/tintedBlue so both helpers always return valid RGB values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 43-48: The README example has a typo and path reuse: replace the
misspelled variable "collpased" with the correct "collapsed" (result of
generateCollapsed) and write the two outputs to distinct files so the expanded
and collapsed images aren't overwritten; update the writeFileSync calls that use
the variables from generate(...) and generateCollapsed(...) accordingly (look
for generate, generateCollapsed, expanded, collapsed, and writeFileSync).
- Around line 7-25: Fix the README typos and heading level: change the "###
Collapsed" heading to "## Collapsed", correct all occurrences of "collpased" to
"collapsed" (including alt text and any image path tokens like
"Augur_Message_collpased.png" if those filenames are meant to be "collapsed"),
and change the misspelled "Auger Message" to "Augur Message" in the Expanded
image captions; update the image paths/captions consistently so alt text,
visible text, and file names match (refer to the strings "### Collapsed",
"collpased", "Augur_Message_collpased.png", and "Auger Message"/"Augur Message"
to locate edits).

In `@src/generator.ts`:
- Around line 25-49: generateCollapsed currently performs canvas/text operations
without registering custom fonts, causing fallback metrics; call registerFonts()
at the start of the generateCollapsed function (before createCanvas or any
drawing/text calls) so that text rendering (e.g., context.font and
context.fillText) uses the intended fonts; ensure the call mirrors how generate
uses registerFonts() to guarantee correct metrics and appearance.

---

Outside diff comments:
In `@tests/generator.spec.ts`:
- Around line 30-45: The for-loop iterates one too many times and the collapsed
output assertions/filename are incorrect: change the loop condition to use i <
mods.length (instead of <=) to avoid out-of-range access when calling
find.findItem(mods[i]), add an assertion assert.ok(modMin) after generating
modMin (from generateCollapsed) to tighten the test, and fix the output filename
typo from "_collpased" to "_collapsed" when calling writeFile; reference the
generate, generateCollapsed, modFull, modMin, mods, find.findItem, and writeFile
usages to locate the changes.

---

Nitpick comments:
In `@src/utils.ts`:
- Around line 208-214: The exported helper hexToRGB currently parses slices
without validating input, which can produce NaN for malformed hex strings;
update hexToRGB to validate the input first (e.g. test against a regex like
/^#([0-9A-Fa-f]{6})$/), throw a clear Error when the format is invalid, and only
then parse the r,g,b channels with parseInt and return the RGB object so callers
fail fast on bad input.
- Around line 217-237: Clamp inputs and outputs in shadePixel and tintPixel:
validate/normalize the percentage parameter to the 0..1 range (e.g., percentage
= Math.max(0, Math.min(1, percentage)) within each function), ensure computed
channel values are integers and clamped to 0..255 before returning (use
Math.round and Math.max/Math.min on each of red/green/blue results), and apply
the same clamping for tintPixel's computed tintedRed/tintedGreen/tintedBlue so
both helpers always return valid RGB values.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2a6c9 and 7545ed2.

⛔ Files ignored due to path filters (14)
  • assets/readme/Afterburn.png is excluded by !**/*.png
  • assets/readme/Afterburn_collpased.png is excluded by !**/*.png
  • assets/readme/Archgun_Riven_Mod.png is excluded by !**/*.png
  • assets/readme/Archon_Intensify.png is excluded by !**/*.png
  • assets/readme/Augur_Message.png is excluded by !**/*.png
  • assets/readme/Augur_Message_collpased.png is excluded by !**/*.png
  • assets/readme/Augur_Message_empty.png is excluded by !**/*.png
  • assets/readme/Augur_Message_filled.png is excluded by !**/*.png
  • assets/readme/Primed_Flow.png is excluded by !**/*.png
  • assets/readme/Primed_Flow_collpased.png is excluded by !**/*.png
  • assets/readme/Steel_Charge.png is excluded by !**/*.png
  • assets/readme/Steel_Charge_collpased.png is excluded by !**/*.png
  • assets/readme/Streamline.png is excluded by !**/*.png
  • assets/readme/Vitality.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • README.md
  • src/drawers.ts
  • src/generator.ts
  • src/styling.ts
  • src/utils.ts
  • tests/generator.spec.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
README.md (2)

47-47: ⚠️ Potential issue | 🟡 Minor

Fix variable typo in the collapsed output write.

Line 47 writes collpased, which is undefined.

Suggested patch
-writeFileSync('directory/collapsed.png', collpased);
+writeFileSync('directory/collapsed.png', collapsed);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 47, The README has a typo in the writeFileSync call: it
uses the undefined variable name "collpased"; change the variable passed to
writeFileSync('directory/collapsed.png', collpased) to the correct variable
"collapsed" so the call becomes writeFileSync('directory/collapsed.png',
collapsed), ensuring the correct image/buffer variable used by the surrounding
code (search for usages/definition of "collapsed" in the same example to confirm
the correct identifier).

7-7: ⚠️ Potential issue | 🟡 Minor

Standardize collapsed spelling across docs and asset references.

Line 7 and Lines 15-18 still use collpased. This leaves inconsistent docs wording and likely broken references if assets are named with collapsed.

Suggested patch
-Assembles [Mod Assets](https://wiki.warframe.com/w/Mod/Assets) into full/collpased mod images using warframe-items
+Assembles [Mod Assets](https://wiki.warframe.com/w/Mod/Assets) into full/collapsed mod images using warframe-items
@@
-![Augur Message](/assets/readme/Augur_Message_collpased.png)
-![Steel Charge](/assets/readme/Steel_Charge_collpased.png)
-![Afterburn](/assets/readme/Afterburn_collpased.png)
-![Primed Flow](/assets/readme/Primed_Flow_collpased.png)
+![Augur Message](/assets/readme/Augur_Message_collapsed.png)
+![Steel Charge](/assets/readme/Steel_Charge_collapsed.png)
+![Afterburn](/assets/readme/Afterburn_collapsed.png)
+![Primed Flow](/assets/readme/Primed_Flow_collapsed.png)

Also applies to: 15-18

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 7, Replace the misspelled token "collpased" with the
correct "collapsed" wherever it appears (e.g., in the README description and the
asset reference lines), ensuring link text, headings, and any asset name
references use "collapsed" consistently; search for the exact string "collpased"
and update all occurrences to "collapsed" so documentation and asset references
match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 43-44: Update calls to generate and generateCollapsed to use the
new async object-based API: pass a GenerateModProps object (e.g., { mod, rank: 3
}) instead of positional args, and await both functions since they return
Promises; change any places that directly log the return value (like line 46) to
await the result before using it. Locate usages by the function names generate
and generateCollapsed and ensure the argument shape matches GenerateModProps and
that the calls are awaited.

In `@src/generator.ts`:
- Around line 52-57: In generateCollapsed the top image is always drawn at x=0
which left-aligns oversized tops and mispositions the mod-set header; compute a
topX = (canvas.width - top.width) / 2 and use context.drawImage(top, topX, 0,
...) so the top is centered (negative topX is fine when top.width >
canvas.width), then update the header draw call to use that topX (e.g.
context.drawImage(await drawHeader(...), topX + top.width * 0.35, 0, ...) so
header alignment follows the centered top; adjust any width/height scaling
arguments accordingly.

---

Duplicate comments:
In `@README.md`:
- Line 47: The README has a typo in the writeFileSync call: it uses the
undefined variable name "collpased"; change the variable passed to
writeFileSync('directory/collapsed.png', collpased) to the correct variable
"collapsed" so the call becomes writeFileSync('directory/collapsed.png',
collapsed), ensuring the correct image/buffer variable used by the surrounding
code (search for usages/definition of "collapsed" in the same example to confirm
the correct identifier).
- Line 7: Replace the misspelled token "collpased" with the correct "collapsed"
wherever it appears (e.g., in the README description and the asset reference
lines), ensuring link text, headings, and any asset name references use
"collapsed" consistently; search for the exact string "collpased" and update all
occurrences to "collapsed" so documentation and asset references match.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7545ed2 and c9574e1.

📒 Files selected for processing (2)
  • README.md
  • src/generator.ts

@SlayerOrnstein SlayerOrnstein enabled auto-merge (squash) February 25, 2026 17:27
@SlayerOrnstein SlayerOrnstein merged commit 10e0b52 into WFCD:main Feb 25, 2026
2 checks passed
@SlayerOrnstein SlayerOrnstein deleted the feat-coll branch February 26, 2026 02:02
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.

2 participants