@prosopo/puzzle-assets 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +23 -0
- package/.turbo/turbo-build$colon$tsc.log +11 -0
- package/.turbo/turbo-build.log +25 -0
- package/CHANGELOG.md +82 -0
- package/LICENSE +201 -0
- package/dist/browser.d.ts +3 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -4
- package/dist/browser.js.map +1 -1
- package/dist/cjs/compose.cjs +117 -1
- package/dist/cjs/contrast.cjs +142 -0
- package/dist/cjs/index.cjs +24 -6
- package/dist/cjs/notch.cjs +115 -93
- package/dist/cjs/palette.cjs +36 -3
- package/dist/compose.d.ts +4 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +117 -2
- package/dist/compose.js.map +1 -1
- package/dist/contrast.d.ts +9 -0
- package/dist/contrast.d.ts.map +1 -0
- package/dist/contrast.js +137 -0
- package/dist/contrast.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/index.js.map +1 -1
- package/dist/notch.d.ts.map +1 -1
- package/dist/notch.js +115 -93
- package/dist/notch.js.map +1 -1
- package/dist/palette.d.ts +1 -0
- package/dist/palette.d.ts.map +1 -1
- package/dist/palette.js +36 -4
- package/dist/palette.js.map +1 -1
- package/dist/prng.d.ts.map +1 -1
- package/dist/prng.js.map +1 -1
- package/dist/tests/pieceReadability.unit.test.d.ts +2 -0
- package/dist/tests/pieceReadability.unit.test.d.ts.map +1 -0
- package/dist/tests/pieceReadability.unit.test.js +320 -0
- package/dist/tests/pieceReadability.unit.test.js.map +1 -0
- package/dist/tests/renderReplay.unit.test.d.ts +2 -0
- package/dist/tests/renderReplay.unit.test.d.ts.map +1 -0
- package/dist/tests/renderReplay.unit.test.js +77 -0
- package/dist/tests/renderReplay.unit.test.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +59 -60
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
$ NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
2
|
+
ViteCommonJSConfig: .
|
|
3
|
+
{ tsConfigPaths: [] }
|
|
4
|
+
{ externals: [] }
|
|
5
|
+
[33mThe plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the [1mresolve.tsconfigPaths[22m option. You can remove the plugin and set [1mresolve.tsconfigPaths: true[22m in your Vite config instead.[39m
|
|
6
|
+
[36mvite v8.1.5 [32mbuilding ssr environment for production...[36m[39m
|
|
7
|
+
Bundle build started
|
|
8
|
+
[2K
|
|
9
|
+
✓ 10 modules transformed.
|
|
10
|
+
rendering chunks...
|
|
11
|
+
computing gzip size...
|
|
12
|
+
dist/cjs/_virtual/_rolldown/runtime.cjs 1.00 kB │ gzip: 0.50 kB
|
|
13
|
+
dist/cjs/prng.cjs 1.47 kB │ gzip: 0.75 kB
|
|
14
|
+
dist/cjs/encode.cjs 1.91 kB │ gzip: 0.90 kB
|
|
15
|
+
dist/cjs/palette.cjs 4.07 kB │ gzip: 1.81 kB
|
|
16
|
+
dist/cjs/contrast.cjs 4.18 kB │ gzip: 1.92 kB
|
|
17
|
+
dist/cjs/background.cjs 4.30 kB │ gzip: 1.84 kB
|
|
18
|
+
dist/cjs/index.cjs 4.44 kB │ gzip: 1.54 kB
|
|
19
|
+
dist/cjs/notch.cjs 4.46 kB │ gzip: 1.81 kB
|
|
20
|
+
dist/cjs/decoys.cjs 4.54 kB │ gzip: 1.94 kB
|
|
21
|
+
dist/cjs/compose.cjs 10.60 kB │ gzip: 4.03 kB
|
|
22
|
+
|
|
23
|
+
[32m✓ built in 53ms[39m
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
$ tsc --build --verbose
|
|
2
|
+
1:37:11 PM - Projects in this build:
|
|
3
|
+
* ../../dev/config/tsconfig.json
|
|
4
|
+
* tsconfig.json
|
|
5
|
+
|
|
6
|
+
1:37:12 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
7
|
+
|
|
8
|
+
1:37:12 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
9
|
+
|
|
10
|
+
1:37:12 PM - Building project '/home/runner/work/captcha/captcha/packages/puzzle-assets/tsconfig.json'...
|
|
11
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$ pnpm run build:cross-env --mode ${NODE_ENV:-development}
|
|
2
|
+
$ vite build --config vite.esm.config.ts --mode production
|
|
3
|
+
ViteEsmConfig: .
|
|
4
|
+
{ tsConfigPaths: [] }
|
|
5
|
+
{ externals: [] }
|
|
6
|
+
[33mThe plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the [1mresolve.tsconfigPaths[22m option. You can remove the plugin and set [1mresolve.tsconfigPaths: true[22m in your Vite config instead.[39m
|
|
7
|
+
[36mvite v8.1.5 [32mbuilding ssr environment for production...[36m[39m
|
|
8
|
+
Bundle build started
|
|
9
|
+
[2K
|
|
10
|
+
✓ 11 modules transformed.
|
|
11
|
+
rendering chunks...
|
|
12
|
+
computing gzip size...
|
|
13
|
+
dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
|
|
14
|
+
dist/prng.js 1.40 kB │ gzip: 0.73 kB
|
|
15
|
+
dist/browser.js 1.66 kB │ gzip: 0.69 kB
|
|
16
|
+
dist/encode.js 1.67 kB │ gzip: 0.82 kB
|
|
17
|
+
dist/index.js 3.51 kB │ gzip: 1.41 kB
|
|
18
|
+
dist/palette.js 3.99 kB │ gzip: 1.79 kB
|
|
19
|
+
dist/contrast.js 4.03 kB │ gzip: 1.90 kB
|
|
20
|
+
dist/background.js 4.25 kB │ gzip: 1.83 kB
|
|
21
|
+
dist/notch.js 4.41 kB │ gzip: 1.81 kB
|
|
22
|
+
dist/decoys.js 4.50 kB │ gzip: 1.93 kB
|
|
23
|
+
dist/compose.js 10.51 kB │ gzip: 4.01 kB
|
|
24
|
+
|
|
25
|
+
[32m✓ built in 33ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# @prosopo/puzzle-assets
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- f961dab: Store everything needed to redraw a puzzle, so the portal can show the exact challenge a user was served.
|
|
7
|
+
|
|
8
|
+
Until now a puzzle record kept the target, the origin and the accuracy required (`tolerance`), and
|
|
9
|
+
nothing else. The things that decide what the puzzle actually looked like — how many decoy shapes were
|
|
10
|
+
scattered on it, how dark they were, how big the piece was, and which background was used — were
|
|
11
|
+
picked per challenge and then thrown away. So you could see that someone failed by 11px and had no way
|
|
12
|
+
to know whether they had been handed an easy puzzle or a nasty one.
|
|
13
|
+
|
|
14
|
+
Puzzle records now carry a `render` object with all of it: the difficulty ladder level, the piece size,
|
|
15
|
+
the frame geometry, the five render settings, and the two 128-bit seeds that regenerate the imagery.
|
|
16
|
+
Given those, the portal reproduces the background and the piece byte for byte.
|
|
17
|
+
|
|
18
|
+
To make that possible the renderer had to become reproducible. `renderPuzzle` and `createBackground`
|
|
19
|
+
now take an optional seed and report the one they used, instead of generating one internally and
|
|
20
|
+
discarding it; the background pool hands out each image together with its seed. Default behaviour is
|
|
21
|
+
unchanged — omit the seed and you still get a fresh random one.
|
|
22
|
+
|
|
23
|
+
The ladder level is new on the session too (`puzzleLevel`), because it was computed when the puzzle
|
|
24
|
+
was escalated and never written down anywhere. It is absent when the ladder did not choose the
|
|
25
|
+
puzzle — a site-configured puzzle or a traffic-filter override has no level.
|
|
26
|
+
|
|
27
|
+
The record is patched after the imagery is produced rather than written with the rest of the record,
|
|
28
|
+
because the piece size and the seeds only exist once the render has run, and the target has to be
|
|
29
|
+
durable before it is expressed in pixels. If that patch fails it is logged and ignored: losing a
|
|
30
|
+
diagnostic must not cost the user their solve. Records written before this change, and any challenge
|
|
31
|
+
whose render threw, simply have no `render`.
|
|
32
|
+
|
|
33
|
+
**The seeds are secret.** `puzzle-assets/src/prng.ts` explains why: they regenerate the clean
|
|
34
|
+
background, and a clean background can be diffed against the composite to read the target position
|
|
35
|
+
straight off. They were previously barred from leaving the provider entirely. They may now sit on the
|
|
36
|
+
captcha record, and nowhere else — not in a response, not in a log, not on the session. Anything that
|
|
37
|
+
exposes a puzzle record to a caller has to drop `render.backgroundSeed` and `render.renderSeed`, and
|
|
38
|
+
must not serve them at all while the challenge is still live. The comment in `prng.ts` now says so.
|
|
39
|
+
|
|
40
|
+
Test coverage. Three guards, each confirmed to fail when the thing it protects is broken:
|
|
41
|
+
`renderReplay.unit.test.ts` re-renders from the stored fields and asserts the bytes match, with one
|
|
42
|
+
case per field asserting that changing *only* that field changes the output — so a record missing a
|
|
43
|
+
field cannot pass; `puzzleRenderer.unit.test.ts` replays through the same entry points the portal would
|
|
44
|
+
use, which catches a background paired with the wrong seed; and `schemas.test.ts` asserts every
|
|
45
|
+
sub-field survives a strict-mode mongoose write, since a field absent from the schema is dropped
|
|
46
|
+
silently and would store nothing at all.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 6e2eb55: Make the puzzle piece look like a puzzle piece, sit apart from the background, and survive colour-blind vision.
|
|
51
|
+
|
|
52
|
+
Three complaints from the Twickets call, all about how the challenge looks.
|
|
53
|
+
|
|
54
|
+
**The silhouette is now always a jigsaw piece.** The generator had five unrelated shape families — jigsaw, circle, rounded rectangle, regular polygon, flower — so four pieces in five were not jigsaw-shaped at all, and a first-time user was as likely to be handed a heptagon as anything recognisable. There is now one grammar: a rounded body where each of the four edges independently gets a tab, a blank or nothing, with the knob's position along its edge, its radius and the corner rounding all varying.
|
|
55
|
+
|
|
56
|
+
Silhouette variety is deliberate anti-automation and it has not been given up — it has moved inside the grammar. 3^4 edge combinations before any of the continuous parameters, against five families with a handful of parameters each, so the space did not shrink; what changed is that every sample in it reads as a piece of a jigsaw. A sweep over 400 seeds finds more than 320 distinct coarse signatures, so there is nothing like a template to match against.
|
|
57
|
+
|
|
58
|
+
The piece is also scaled to fit its bounding box now, measured from the knobs actually drawn, rather than the body being inset by the worst case the parameters could produce. Insetting for the worst case shrinks every piece to make room for the largest tab that was never drawn. A tab reaching the border used to come out clipped flat, which looks like a manufacturing defect rather than a jigsaw tab.
|
|
59
|
+
|
|
60
|
+
**The piece is separated by a luminance step, not by hue.** It is cut from the same mesh the background is drawn from, so on an unlucky palette it was the same colour as whatever it was resting on and the only thing dividing them was a CSS drop-shadow. It now carries an outline whose brightness steps away from its own, in the direction chosen from the piece's own mean luminance — a dark piece gets a light rim, a light piece a dark one. A fixed direction vanishes at one end of the range, which is how a cue that is present in the code ends up absent on screen.
|
|
61
|
+
|
|
62
|
+
The size of the step is solved for per piece to reach a target contrast ratio, not set as a fixed byte offset: 40 units out of 255 is obvious on a mid grey and nearly invisible near white. Being a brightness step rather than a hue, it survives greyscale, a monochrome display, every dichromacy and a phone screen in sunlight — which the tests assert by measuring luminance only, and again after flattening the piece to grey.
|
|
63
|
+
|
|
64
|
+
**Palette generation now has a measured contrast floor.** The six palettes are analogous-hue families, which is what makes a mesh gradient look deliberate and is also the worst case for colour-blind vision: a band of neighbouring hues at one lightness is exactly what a dichromat collapses to a single colour. Nothing checked for that, so a palette could lose all its structure for 1 in 12 men and no test would notice.
|
|
65
|
+
|
|
66
|
+
A new `contrast` module does WCAG relative luminance on linearised sRGB and Viénot–Brettel–Mollon simulation of protanopia, deuteranopia and tritanopia. `drawPalette` now redraws while its widest pair falls below the floor under *any* of the four vision types, widening the lightness band on each retry rather than giving up after N tries — a floor that can be returned past is not a floor. The first attempts are unstretched, so an ordinary draw is exactly what it was before and only a failing palette gets touched.
|
|
67
|
+
|
|
68
|
+
The floor is on the palette's *range*, not on every pair: a gradient needs near-neighbours, and demanding contrast between them would produce stripes. What it rules out is a field that is uniformly one brightness.
|
|
69
|
+
|
|
70
|
+
**Tests sweep seeds rather than checking an example.** 45 tests, including 400-seed sweeps of the palette floor, the presence of tabs, silhouette variety, the bounding box, and the area covered; and 120-seed sweeps of the rim's luminance step, its direction, its survival in greyscale, and its survival under each dichromacy. A palette or parameter that is fine on the one seed somebody happened to look at and unreadable on a tenth of the rest is the failure that shipped, and a single-seed test cannot see it.
|
|
71
|
+
|
|
72
|
+
One thing the tests could not see, found by rendering the output and looking at it: a fixed 4px rim is a tidy outline on a large piece and most of the surface area of a 44px one — the default — so the whole piece read as a pale blob rather than as a piece with a lit edge. No measurement of rim-versus-core contrast catches that, because by that measure a piece which is entirely rim scores perfectly. The rim is now proportional to the piece size, and the tests read the band geometry from the renderer so the measurement cannot drift away from what was drawn.
|
|
73
|
+
|
|
74
|
+
## 0.1.4
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- 6fd727c: Publish `@prosopo/native-merkle` and `@prosopo/puzzle-assets` to npm instead of keeping them private.
|
|
78
|
+
|
|
79
|
+
These are the last two entries in `@prosopo/provider`'s `dependencies` that carried `"private": true` and so were never published. With `@prosopo/native-ja4` already fixed, dropping `private` here makes `npm i @prosopo/provider` resolve for the first time since 5.5.0 — until now it failed with an `E404` on whichever unpublished dependency npm reached first. The Docker image and the bundled CLI build the workspace from source and never resolve these against the registry, which is why the breakage stayed invisible.
|
|
80
|
+
|
|
81
|
+
`native-merkle` also gains `repository`, because the release workflow publishes with `NPM_CONFIG_PROVENANCE=true` and provenance attestation verifies `repository.url` against the OIDC claim for `prosopo/captcha`. `puzzle-assets` already declared `repository`, `author`, `bugs` and `homepage`, so it needed only the `private` line removed.
|
|
82
|
+
|
|
83
|
+
Neither tarball changes shape. `native-merkle` keeps its `files` allowlist of `index.js`, `index.d.ts` and the prebuilt `*.node`, and stays `x86_64-unknown-linux-gnu` only — it resolves on linux-x64-gnu and throws napi's "Unsupported architecture" elsewhere, exactly as it does inside the workspace today. `puzzle-assets` ships `dist/` with both the root and `./browser` subpath exports, and keeps its `sharp` runtime dependency.
|
|
84
|
+
|
|
3
85
|
## 0.1.3
|
|
4
86
|
### Patch Changes
|
|
5
87
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2022 PROSOPO LIMITED
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/dist/browser.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ export interface RenderedPuzzleRgba {
|
|
|
9
9
|
background: RgbaImage;
|
|
10
10
|
piece: RgbaImage;
|
|
11
11
|
pieceSize: number;
|
|
12
|
+
seed: Buffer;
|
|
12
13
|
}
|
|
13
|
-
export declare const renderPuzzleToRgba: (background: RgbaImage, placement: NotchPlacement, geometry?: PuzzleGeometry, settings?: PuzzleRenderSettings) => RenderedPuzzleRgba;
|
|
14
|
-
export declare const createBackground: (geometry?: PuzzleGeometry) => RgbaImage;
|
|
14
|
+
export declare const renderPuzzleToRgba: (background: RgbaImage, placement: NotchPlacement, geometry?: PuzzleGeometry, settings?: PuzzleRenderSettings, seed?: Buffer) => RenderedPuzzleRgba;
|
|
15
|
+
export declare const createBackground: (geometry?: PuzzleGeometry, seed?: Buffer) => RgbaImage;
|
|
15
16
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,SAAS,GACT,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,gBAAgB,EAAE,cAI9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,oBAMrC,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,SAAS,GACT,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,gBAAgB,EAAE,cAI9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,oBAMrC,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACb;AAOD,eAAO,MAAM,kBAAkB,eAClB,SAAS,aACV,cAAc,aACf,cAAc,aACd,oBAAoB,SACxB,MAAM,KACV,kBAuBF,CAAC;AAEF,eAAO,MAAM,gBAAgB,cAClB,cAAc,SAClB,MAAM,KACV,SACmE,CAAC"}
|
package/dist/browser.js
CHANGED
|
@@ -22,17 +22,18 @@ var DEFAULT_RENDER_SETTINGS = {
|
|
|
22
22
|
* than WebP-encoded buffers, so browser consumers can `putImageData` onto
|
|
23
23
|
* a canvas without needing sharp.
|
|
24
24
|
*/
|
|
25
|
-
var renderPuzzleToRgba = (background, placement, geometry = DEFAULT_GEOMETRY, settings = DEFAULT_RENDER_SETTINGS) => {
|
|
26
|
-
const prng = createPrng(
|
|
25
|
+
var renderPuzzleToRgba = (background, placement, geometry = DEFAULT_GEOMETRY, settings = DEFAULT_RENDER_SETTINGS, seed = createSeed()) => {
|
|
26
|
+
const prng = createPrng(seed);
|
|
27
27
|
const pieceSize = geometry.pieceSize ?? geometry.notchSize;
|
|
28
28
|
paintDecoys(background, prng, settings.decoyCount, pieceSize, placement, settings.decoyEdgeDarkness, settings.decoyBodyBrightness, settings.decoyHoleDarken);
|
|
29
29
|
const { background: cut, piece } = cutNotch(prng, background, createNotchShape(prng, pieceSize), pieceSize, placement, settings.holeDarken);
|
|
30
30
|
return {
|
|
31
31
|
background: cut,
|
|
32
32
|
piece,
|
|
33
|
-
pieceSize
|
|
33
|
+
pieceSize,
|
|
34
|
+
seed
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
|
-
var createBackground = (geometry = DEFAULT_GEOMETRY) => generateBackground(createPrng(
|
|
37
|
+
var createBackground = (geometry = DEFAULT_GEOMETRY, seed = createSeed()) => generateBackground(createPrng(seed), geometry.width, geometry.height);
|
|
37
38
|
//#endregion
|
|
38
39
|
export { DEFAULT_GEOMETRY, DEFAULT_RENDER_SETTINGS, SEED_BYTES, createBackground, createPrng, createSeed, generateBackground, paintDecoyPiece, paintDecoys, renderPuzzleToRgba };
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQnD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS3D,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC/C,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC5D,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,CAAC;IACtB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,GAAG;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQnD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS3D,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC/C,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC5D,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,CAAC;IACtB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,GAAG;CACpB,CAAC;AAeF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,UAAqB,EACrB,SAAyB,EACzB,WAA2B,gBAAgB,EAC3C,WAAiC,uBAAuB,EACxD,OAAe,UAAU,EAAE,EACN,EAAE;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC;IAC3D,WAAW,CACV,UAAU,EACV,IAAI,EACJ,QAAQ,CAAC,UAAU,EACnB,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,iBAAiB,EAC1B,QAAQ,CAAC,mBAAmB,EAC5B,QAAQ,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,QAAQ,CAC1C,IAAI,EACJ,UAAU,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,UAAU,CACnB,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,WAA2B,gBAAgB,EAC3C,OAAe,UAAU,EAAE,EACf,EAAE,CACd,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/cjs/compose.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_contrast = require("./contrast.cjs");
|
|
1
2
|
const require_notch = require("./notch.cjs");
|
|
2
3
|
//#region src/compose.ts
|
|
3
4
|
/**
|
|
@@ -39,6 +40,74 @@ var HOLE_INNER_SHADOW = .45;
|
|
|
39
40
|
* against the darkened cutout it drops into.
|
|
40
41
|
*/
|
|
41
42
|
var PIECE_EDGE_LIGHT = 8;
|
|
43
|
+
/**
|
|
44
|
+
* The piece's outline, measured inwards from the silhouette, as a fraction of
|
|
45
|
+
* the piece's size.
|
|
46
|
+
*
|
|
47
|
+
* Full strength out to `PLATEAU`, then tapering to nothing by `WIDTH`. A rim that
|
|
48
|
+
* tapers from the very first pixel has its strongest values exactly where
|
|
49
|
+
* anti-aliasing makes the pixels partly transparent, so the part of it that is
|
|
50
|
+
* fully opaque is only ever half strength — the cue measures far weaker than the
|
|
51
|
+
* number configuring it suggests. The plateau puts the full step on pixels that
|
|
52
|
+
* are actually opaque, and the taper then stops it ending in a hard line.
|
|
53
|
+
*
|
|
54
|
+
* PROPORTIONAL, NOT A FIXED PIXEL COUNT. A 4px band is a tidy outline on a
|
|
55
|
+
* 120px piece and most of the surface area of a 44px one — the default
|
|
56
|
+
* `notchSize` — which made the whole piece read as a pale blob rather than as a
|
|
57
|
+
* piece with a lit edge. Caught by rendering it and looking; no measurement of
|
|
58
|
+
* rim-versus-core contrast can see it, because by that measure a piece that is
|
|
59
|
+
* entirely rim scores perfectly.
|
|
60
|
+
*/
|
|
61
|
+
var PIECE_RIM_PLATEAU_RATIO = .03;
|
|
62
|
+
var PIECE_RIM_WIDTH_RATIO = .065;
|
|
63
|
+
/** Floors, so the rim does not vanish on a very small piece. */
|
|
64
|
+
var PIECE_RIM_MIN_PLATEAU = 1;
|
|
65
|
+
var PIECE_RIM_MIN_WIDTH = 2.2;
|
|
66
|
+
/** Rim geometry for a piece of `size` px. Exported so tests measure the band the renderer actually drew. */
|
|
67
|
+
var pieceRimGeometry = (size) => {
|
|
68
|
+
const plateau = Math.max(PIECE_RIM_MIN_PLATEAU, size * PIECE_RIM_PLATEAU_RATIO);
|
|
69
|
+
return {
|
|
70
|
+
plateau,
|
|
71
|
+
width: Math.max(PIECE_RIM_MIN_WIDTH, size * PIECE_RIM_WIDTH_RATIO, plateau + 1)
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The WCAG contrast ratio the rim aims to reach against the piece's own body.
|
|
76
|
+
*
|
|
77
|
+
* This is the separation cue, and it is deliberately not a hue. The piece is cut
|
|
78
|
+
* from the same mesh the background is drawn from, so on an unlucky palette it
|
|
79
|
+
* was the same colour as whatever it was resting on and the only thing dividing
|
|
80
|
+
* them was a CSS drop-shadow. A luminance step survives what hue does not:
|
|
81
|
+
* greyscale, a monochrome display, every dichromacy, and a phone screen in
|
|
82
|
+
* sunlight.
|
|
83
|
+
*
|
|
84
|
+
* Expressed as a ratio rather than as a fixed byte offset because a fixed offset
|
|
85
|
+
* is not a fixed cue: 40 units out of 255 is obvious on a mid grey and almost
|
|
86
|
+
* invisible near white, where the same step is a much smaller fraction of the
|
|
87
|
+
* luminance. The required offset is solved for per piece from the luminance it
|
|
88
|
+
* actually has.
|
|
89
|
+
*
|
|
90
|
+
* 1.5 is comfortably visible without reading as a drawn border. The tests assert
|
|
91
|
+
* a lower bound than this, because the taper and the per-pixel noise cost a
|
|
92
|
+
* little of it.
|
|
93
|
+
*/
|
|
94
|
+
var PIECE_RIM_TARGET_CONTRAST = 1.5;
|
|
95
|
+
/**
|
|
96
|
+
* Cap on the rim offset, in 0-255 units. Past roughly this the rim stops reading
|
|
97
|
+
* as a lit or shaded edge and starts reading as a stroke drawn around a sticker,
|
|
98
|
+
* which is the look this is trying to avoid.
|
|
99
|
+
*/
|
|
100
|
+
var PIECE_RIM_MAX_STEP = 72;
|
|
101
|
+
/**
|
|
102
|
+
* Above this relative luminance the piece is treated as light, and gets a dark
|
|
103
|
+
* rim rather than a light one.
|
|
104
|
+
*
|
|
105
|
+
* Picking the direction from the piece's own mean rather than fixing it is the
|
|
106
|
+
* whole point: a fixed light rim vanishes on a pale background and a fixed dark
|
|
107
|
+
* one vanishes on a dark background, which is how a cue that is present in the
|
|
108
|
+
* code ends up absent on screen.
|
|
109
|
+
*/
|
|
110
|
+
var PIECE_RIM_DIRECTION_THRESHOLD = .4;
|
|
42
111
|
var clamp255 = (v) => v < 0 ? 0 : v > 255 ? 255 : v;
|
|
43
112
|
/**
|
|
44
113
|
* Sample the background at `(x, y)`. Returns `null` when the coordinate
|
|
@@ -56,6 +125,49 @@ var sampleBackground = (image, x, y) => {
|
|
|
56
125
|
];
|
|
57
126
|
};
|
|
58
127
|
/**
|
|
128
|
+
* Rim strength in [0, 1] for a signed distance: 1 on the plateau, tapering to 0
|
|
129
|
+
* at the full width inside the silhouette, 0 beyond it.
|
|
130
|
+
*/
|
|
131
|
+
var rimProfile = (d, plateau, width) => {
|
|
132
|
+
if (d <= -width) return 0;
|
|
133
|
+
if (d >= -plateau) return 1;
|
|
134
|
+
return (d + width) / (width - plateau);
|
|
135
|
+
};
|
|
136
|
+
/** Approximate sRGB byte for a relative luminance. */
|
|
137
|
+
var byteForLuminance = (luminance) => 255 * Math.max(0, Math.min(1, luminance)) ** (1 / 2.2);
|
|
138
|
+
/**
|
|
139
|
+
* The signed offset, in 0-255 units, to apply at the piece's rim.
|
|
140
|
+
*
|
|
141
|
+
* Averages the luminance of the pixels the piece will be cut from, picks the
|
|
142
|
+
* direction that steps *away* from it, then solves for the offset that reaches
|
|
143
|
+
* [`PIECE_RIM_TARGET_CONTRAST`] against it. A dark piece gets a light rim and a
|
|
144
|
+
* light piece a dark one, so the cue is present whatever the palette produced —
|
|
145
|
+
* which neither a fixed direction nor a fixed magnitude can promise.
|
|
146
|
+
*/
|
|
147
|
+
var pieceRimDelta = (background, shape, size, left, top) => {
|
|
148
|
+
let total = 0;
|
|
149
|
+
let counted = 0;
|
|
150
|
+
for (let ly = 0; ly < size; ly += 4) for (let lx = 0; lx < size; lx += 4) {
|
|
151
|
+
if (require_notch.coverageFromDistance(shape.distance(lx, ly)) <= 0) continue;
|
|
152
|
+
const sample = sampleBackground(background, left + lx, top + ly);
|
|
153
|
+
if (sample === null) continue;
|
|
154
|
+
total += require_contrast.relativeLuminance({
|
|
155
|
+
r: sample[0],
|
|
156
|
+
g: sample[1],
|
|
157
|
+
b: sample[2]
|
|
158
|
+
});
|
|
159
|
+
counted++;
|
|
160
|
+
}
|
|
161
|
+
if (counted === 0) return PIECE_RIM_MAX_STEP;
|
|
162
|
+
const pieceLuminance = total / counted;
|
|
163
|
+
const lighten = pieceLuminance <= PIECE_RIM_DIRECTION_THRESHOLD;
|
|
164
|
+
const delta = byteForLuminance(lighten ? PIECE_RIM_TARGET_CONTRAST * (pieceLuminance + .05) - .05 : (pieceLuminance + .05) / PIECE_RIM_TARGET_CONTRAST - .05) - byteForLuminance(pieceLuminance);
|
|
165
|
+
const capped = Math.max(-PIECE_RIM_MAX_STEP, Math.min(PIECE_RIM_MAX_STEP, delta));
|
|
166
|
+
const floor = 24;
|
|
167
|
+
if (Math.abs(capped) < floor) return lighten ? floor : -24;
|
|
168
|
+
return capped;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
59
171
|
* Cut the notch out of `background` and produce the matching piece.
|
|
60
172
|
*
|
|
61
173
|
* `background` is mutated in place — it is a freshly generated, single-use
|
|
@@ -67,6 +179,8 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
|
|
|
67
179
|
const top = Math.round(placement.targetY - half);
|
|
68
180
|
const pieceData = Buffer.alloc(size * size * 4);
|
|
69
181
|
const brightness = prng.range(PIECE_BRIGHTNESS_RANGE[0], PIECE_BRIGHTNESS_RANGE[1]);
|
|
182
|
+
const rimStep = pieceRimDelta(background, shape, size, left, top);
|
|
183
|
+
const rim = pieceRimGeometry(size);
|
|
70
184
|
for (let ly = 0; ly < size; ly++) for (let lx = 0; lx < size; lx++) {
|
|
71
185
|
const d = shape.distance(lx, ly);
|
|
72
186
|
const coverage = require_notch.coverageFromDistance(d);
|
|
@@ -82,8 +196,9 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
|
|
|
82
196
|
}
|
|
83
197
|
const [r, g, b] = sample;
|
|
84
198
|
const edgeLight = d > -2 ? PIECE_EDGE_LIGHT * (1 + d / 2) : 0;
|
|
199
|
+
const rimDelta = rimStep * rimProfile(d, rim.plateau, rim.width);
|
|
85
200
|
const noise = (prng.next() - .5) * 2 * PIECE_NOISE;
|
|
86
|
-
const delta = brightness + edgeLight + noise;
|
|
201
|
+
const delta = brightness + edgeLight + rimDelta + noise;
|
|
87
202
|
pieceData[pi] = clamp255(Math.round(r + delta));
|
|
88
203
|
pieceData[pi + 1] = clamp255(Math.round(g + delta));
|
|
89
204
|
pieceData[pi + 2] = clamp255(Math.round(b + delta));
|
|
@@ -124,3 +239,4 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
|
|
|
124
239
|
};
|
|
125
240
|
//#endregion
|
|
126
241
|
exports.cutNotch = cutNotch;
|
|
242
|
+
exports.pieceRimGeometry = pieceRimGeometry;
|