Skip to content

Commit

Permalink
feat(porter-duff): add darken/dissolve/opacity, optimize int ops, upd…
Browse files Browse the repository at this point in the history
…ate readme
  • Loading branch information
postspectacular committed Jul 29, 2019
1 parent b019bc0 commit c42b795
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 19 deletions.
Binary file added assets/porter-duff-custom.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 40 additions & 2 deletions packages/porter-duff/README.md
Expand Up @@ -16,6 +16,9 @@ This project is part of the
- [Usage examples](#usage-examples)
- [API](#api)
- [Operators](#operators)
- [Custom operators](#custom-operators)
- [Additional operators / modifiers](#additional-operators--modifiers)
- [Pre/post-multiplied colors](#prepost-multiplied-colors)
- [Authors](#authors)
- [License](#license)

Expand All @@ -35,8 +38,6 @@ package (prior to v1.0.0).

![porter-duff compositing modes](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/porter-duff2.png)

([Image source](http://www.svgopen.org/2005/papers/abstractsvgopen/#PorterDuffMap))

### References

- https://keithp.com/~keithp/porterduff/p253-porter.pdf (original paper)
Expand Down Expand Up @@ -106,6 +107,43 @@ Consult above diagram for expected results.
- `XOR`
- `PLUS`

### Custom operators

New operators (e.g. for blend modes) can be easily defined via `porterDuff` / `porterDuffInt`. Both functions take 2 function arguments to extract blend coefficients from the src & dest colors:

```ts
// coefficient functions take the normalized alpha values
// of both colors as arguments, but unused here...
const customOp = porterDuffInt(() => -0.5, () => 1);
```

![custom operator](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/porter-duff-custom.png)

### Additional operators / modifiers

The following modifiers are also discussed in the original Porter-Duff paper (linked above).

- `darken` / `darkenInt`
- `dissolve` / `dissolveInt`
- `opacity` / `opacityInt`

### Pre/post-multiplied colors

All Porter-Duff operators expect colors with **pre-multiplied** alpha.
Premultiplication is also recommended for WebGL textures (especially
when using mipmaps). For that purpose the following helpers might be
useful:

- `premultiply` / `premultiplyInt`
- `postmultiply` / `postmultiplyInt`
- `isPremultiplied` / `isPremultipliedInt`

Furthermore, existing PD operators can be wrapped with automatic
pre/post-multiplies using `porterDuffP` / `porterDuffPInt` (see example
above).

Note: HTML Canvas `ImageData` is using non-premultiplied colors.

## Authors

- Karsten Schmidt
Expand Down
102 changes: 85 additions & 17 deletions packages/porter-duff/src/porter-duff.ts
@@ -1,4 +1,5 @@
import { Fn2, Fn3 } from "@thi.ng/api";
import { clamp } from "@thi.ng/math";
import { Color, ReadonlyColor } from "./api";
import {
postmultiply,
Expand Down Expand Up @@ -28,8 +29,6 @@ export const ONE_MINUS_B = (_: number, b: number) => 1 - b;
*
* Reference:
* https://keithp.com/~keithp/porterduff/p253-porter.pdf
* https://drafts.fxtf.org/compositing-1/#advancedcompositing
* http://www.svgopen.org/2005/papers/abstractsvgopen/#PorterDuffMap
*
* @param fa fn for src coeff
* @param fb fn for dest coeff
Expand All @@ -38,8 +37,8 @@ export const porterDuff = (
fa: Fn2<number, number, number>,
fb: Fn2<number, number, number>
) => (out: Color | null, src: ReadonlyColor, dest: ReadonlyColor) => {
const sa = src[3] / 255;
const sb = dest[3] / 255;
const sa = src[3];
const sb = dest[3];
const aa = fa(sa, sb);
const bb = fb(sa, sb);
return setC4(
Expand All @@ -59,18 +58,15 @@ export const porterDuffInt = (
const sb = (b >>> 24) / 255;
const aa = fa(sa, sb);
const bb = fb(sa, sb);
const $ = aa
? bb
? (shift: number) =>
min(
255,
((a >>> shift) & 0xff) * aa + ((b >>> shift) & 0xff) * bb
) << shift
: (shift: number) => min(255, ((a >>> shift) & 0xff) * aa) << shift
: bb
? (shift: number) => min(255, ((b >>> shift) & 0xff) * bb) << shift
: () => 0;
return ($(24) | $(16) | $(8) | $(0)) >>> 0;
return (
(clamp(((a >>> 24) & 0xff) * aa + ((b >>> 24) & 0xff) * bb, 0, 255) <<
24) |
(clamp(((a >>> 16) & 0xff) * aa + ((b >>> 16) & 0xff) * bb, 0, 255) <<
16) |
(clamp(((a >>> 8) & 0xff) * aa + ((b >>> 8) & 0xff) * bb, 0, 255) <<
8) |
clamp((a & 0xff) * aa + (b & 0xff), 0, 255)
);
};

/**
Expand Down Expand Up @@ -109,7 +105,7 @@ export const porterDuffPInt = (mode: Fn2<number, number, number>) => (
* @param src
* @param dest
*/
export const CLEAR = (out: Color, _: ReadonlyColor, dest: ReadonlyColor) =>
export const CLEAR_F = (out: Color, _: ReadonlyColor, dest: ReadonlyColor) =>
setN4(out || dest, 0);

/**
Expand Down Expand Up @@ -298,3 +294,75 @@ export const XOR_I = porterDuffInt(ONE_MINUS_B, ONE_MINUS_A);
* are added.
*/
export const PLUS_I = porterDuffInt(ONE, ONE);

/**
* Porter-Duff darken modifier. Multiplies RGB components of `src` with
* `t`. Alpha remains unchanged. Writes results to `out`, or if `null`
* modifies `src` in-place.
*
* @param out
* @param src
* @param t
*/
export const darken = (out: Color | null, src: ReadonlyColor, t: number) =>
setC4(out || src, src[0] * t, src[1] * t, src[2] * t, src[3]);

/**
* Porter-Duff dissolve modifier. Multiplies all components of `src`
* with `t`. Clamps alpha to [0..1] range, RGB unclamped. Writes results
* to `out`, or if `null` modifies `src` in-place.
*
* @param out
* @param src
* @param t
*/
export const dissolve = (out: Color | null, src: ReadonlyColor, t: number) =>
setC4(out || src, src[0] * t, src[1] * t, src[2] * t, min(1, src[3] * t));

/**
* Porter-Duff opacity modifier. Multiplies alpha component of `src`
* with `t`, clamped to [0..1] range. Writes results to `out`, or if
* `null` modifies `src` in-place.
*
* @param out
* @param src
* @param t
*/
export const opacity = (out: Color | null, src: ReadonlyColor, t: number) =>
setC4(out || src, src[0], src[1], src[2], min(1, src[3] * t));

/**
* Porter-Duff darken modifier for packed ints. Multiplies RGB
* components of `src` with `t` ([0..1] range).
*
* @param src
* @param t
*/
export const darkenInt = (src: number, t: number) =>
(src & 0xff000000) |
(min(0xff, ((src >>> 16) & 0xff) * t) << 16) |
(min(0xff, ((src >>> 8) & 0xff) * t) << 8) |
min(0xff, (src & 0xff) * t);

/**
* Porter-Duff dissolve modifier for packed ints. Multiplies all
* components of `src` with `t` ([0..1] range).
*
* @param src
* @param t
*/
export const dissolveInt = (src: number, t: number) =>
(min(0xff, ((src >>> 24) & 0xff) * t) << 24) |
(min(0xff, ((src >>> 16) & 0xff) * t) << 16) |
(min(0xff, ((src >>> 8) & 0xff) * t) << 8) |
min(0xff, (src & 0xff) * t);

/**
* Porter-Duff opacity modifier for packed ints. Multiplies alpha
* component of `src` with `t` ([0..1] range).
*
* @param src
* @param t
*/
export const opacityInt = (src: number, t: number) =>
(min(0xff, ((src >>> 24) & 0xff) * t) << 24) | (src & 0xffffff);

0 comments on commit c42b795

Please sign in to comment.