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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
82 instead of 95 ms), with identical output: the predictors run as one loop per mode instead of a switch per pixel,
residual costs come from a table, and the LZ77 search hashes every pixel pair once.

### Fixes

- The outline of `Image.renderText` (`strokeWidth`) showed through translucent text, it now only shows around the
glyphs (opaque text renders as before).

## 3.2.1

### Performance
Expand Down
8 changes: 5 additions & 3 deletions src/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2095,14 +2095,14 @@ export class Image {

// the outline goes around the color glyphs as well
let shape = coverage;
if (colored && strokeWidth) {
if (colored) {
shape = coverage.slice();
for (let i = 0; i < shape.length; i++) shape[i] = Math.max(shape[i], colored[i * 4 + 3]);
}
const stroke = strokeWidth ? outline(shape, width, height, strokeWidth) : null;
const [sr, sg, sb, sa] = Image.colorToRGBA(strokeColor);

// color glyphs over the text (premultiplied, with the text's alpha), both over their outline
// color glyphs over the text (premultiplied, with the text's alpha), both within their outline
for (let i = 0; i < coverage.length; i++) {
let af = coverage[i] / 255 * a / 255, pr = r * af, pg = g * af, pb = b * af;
if (colored && colored[i * 4 + 3]) {
Expand All @@ -2113,7 +2113,9 @@ export class Image {
af = ca + af * keep;
}
if (stroke) {
const as = stroke[i] * sa / 255 * (1 - af);
// the outline shows around the glyphs only, not through translucent ones (for opaque text it's the
// same as drawing the glyphs over it)
const as = stroke[i] * sa / 255 * (1 - shape[i] / 255);
pr += sr * as;
pg += sg * as;
pb += sb * as;
Expand Down
12 changes: 12 additions & 0 deletions tests/regressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,18 @@ const rgba = (image, x, y) => Image.colorToRGBA(image.getPixelAt(x, y));
if (color === 0xffffffff) white++;
}
assert(red > 100 && white > 100, `outlined text should contain both colors (${red} red, ${white} white)`);

// the stroke goes around translucent text, it doesn't show through it
const translucent = await Image.renderText(font, 40, 'Hi', 0x0000ff80, undefined, {strokeWidth: 3, strokeColor: 0xff0000ff});
const fill = await Image.renderText(font, 40, 'Hi', 0x0000ff80);
let inside = 0, tinted = 0;
// pixels the glyphs cover fully (the stroked image is 3 pixels larger on every side)
for (const [x, y, color] of fill.iterateWithColors()) {
if (color !== 0x0000ff80) continue;
inside++;
if (translucent.getPixelAt(x + 3, y + 3) !== 0x0000ff80) tinted++;
}
assert(inside > 100 && !tinted, `translucent text should show no stroke inside (${tinted} of ${inside} pixels tinted)`);
}

// PNGs are oriented by their eXIf chunk, ImageMagick's Exif text or XMP, in that order
Expand Down
Loading