@pictogrammers/components 0.4.8 → 0.5.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.
Files changed (191) hide show
  1. package/package.json +1 -1
  2. package/pg/annoy/annoy.css +1 -1
  3. package/pg/button/button.css +5 -4
  4. package/pg/buttonLink/buttonLink.css +1 -1
  5. package/pg/buttonMenu/__examples__/basic/basic.ts +2 -2
  6. package/pg/buttonMenu/buttonMenu.ts +4 -1
  7. package/pg/cardUser/cardUser.css +1 -1
  8. package/pg/database/__examples__/basic/basic.ts +0 -1
  9. package/pg/grid/__examples__/basic/basic.ts +1 -4
  10. package/pg/grid/grid.css +1 -1
  11. package/pg/header/header.css +1 -1
  12. package/pg/icon/__examples__/basic/basic.ts +1 -1
  13. package/pg/inputCheck/__examples__/basic/basic.ts +1 -1
  14. package/pg/inputCheck/inputCheck.css +5 -0
  15. package/pg/inputCheck/inputCheck.ts +4 -0
  16. package/pg/inputCheckList/__examples__/basic/basic.ts +1 -1
  17. package/pg/inputFileLocal/inputFileLocal.css +1 -1
  18. package/pg/inputNumber/README.md +27 -0
  19. package/pg/inputNumber/__examples__/basic/basic.html +9 -0
  20. package/pg/inputNumber/__examples__/basic/basic.ts +30 -0
  21. package/pg/inputNumber/inputNumber.css +34 -0
  22. package/pg/inputNumber/inputNumber.html +1 -0
  23. package/pg/inputNumber/inputNumber.spec.ts +59 -0
  24. package/pg/inputNumber/inputNumber.ts +63 -0
  25. package/pg/inputPixelEditor/README.md +211 -29
  26. package/pg/inputPixelEditor/__examples__/basic/basic.css +8 -0
  27. package/pg/inputPixelEditor/__examples__/basic/basic.html +29 -7
  28. package/pg/inputPixelEditor/__examples__/basic/basic.ts +274 -13
  29. package/pg/inputPixelEditor/__examples__/basic/constants.ts +62 -0
  30. package/pg/inputPixelEditor/inputPixelEditor.css +37 -2
  31. package/pg/inputPixelEditor/inputPixelEditor.html +22 -0
  32. package/pg/inputPixelEditor/inputPixelEditor.ts +822 -82
  33. package/pg/inputPixelEditor/utils/bitmapToMask.ts +22 -8
  34. package/pg/inputPixelEditor/utils/blobToImage.ts +11 -0
  35. package/pg/inputPixelEditor/utils/canvasToPngBuffer.ts +12 -0
  36. package/pg/inputPixelEditor/utils/constants.ts +55 -1
  37. package/pg/inputPixelEditor/utils/crc32.ts +116 -0
  38. package/pg/inputPixelEditor/utils/diffMap.ts +32 -0
  39. package/pg/inputPixelEditor/utils/generateGradient.ts +112 -0
  40. package/pg/inputPixelEditor/utils/getEllipsePixels.ts +131 -19
  41. package/pg/inputPixelEditor/utils/getFloodFill.ts +83 -0
  42. package/pg/inputPixelEditor/utils/getGridColorIndexes.ts +13 -0
  43. package/pg/inputPixelEditor/utils/getOutline.ts +92 -0
  44. package/pg/inputPixelEditor/utils/inputMode.ts +7 -1
  45. package/pg/inputPixelEditor/utils/pixelSizes.ts +47 -0
  46. package/pg/inputPixelEditor/utils/pngMetadata.ts +487 -0
  47. package/pg/inputSelect/inputSelect.css +4 -4
  48. package/pg/inputText/inputText.css +14 -7
  49. package/pg/inputText/inputText.ts +5 -1
  50. package/pg/json/README.md +59 -0
  51. package/pg/json/__examples__/basic/basic.html +4 -0
  52. package/pg/json/__examples__/basic/basic.ts +31 -0
  53. package/pg/json/json.css +9 -0
  54. package/pg/json/json.html +1 -0
  55. package/pg/json/json.ts +124 -0
  56. package/pg/jsonArray/README.md +3 -0
  57. package/pg/jsonArray/jsonArray.css +15 -0
  58. package/pg/jsonArray/jsonArray.html +7 -0
  59. package/pg/jsonArray/jsonArray.ts +55 -0
  60. package/pg/jsonBoolean/README.md +3 -0
  61. package/pg/jsonBoolean/jsonBoolean.css +27 -0
  62. package/pg/jsonBoolean/jsonBoolean.html +5 -0
  63. package/pg/jsonBoolean/jsonBoolean.ts +69 -0
  64. package/pg/jsonNumber/README.md +3 -0
  65. package/pg/jsonNumber/jsonNumber.css +21 -0
  66. package/pg/jsonNumber/jsonNumber.html +5 -0
  67. package/pg/jsonNumber/jsonNumber.ts +42 -0
  68. package/pg/jsonObject/README.md +3 -0
  69. package/pg/jsonObject/jsonObject.css +11 -0
  70. package/pg/jsonObject/jsonObject.html +5 -0
  71. package/pg/jsonObject/jsonObject.ts +55 -0
  72. package/pg/jsonString/README.md +3 -0
  73. package/pg/jsonString/jsonString.css +21 -0
  74. package/pg/jsonString/jsonString.html +5 -0
  75. package/pg/jsonString/jsonString.ts +42 -0
  76. package/pg/menu/menu.ts +6 -5
  77. package/pg/menuItem/README.md +13 -2
  78. package/pg/menuItem/menuItem.css +17 -22
  79. package/pg/menuItem/menuItem.ts +8 -3
  80. package/pg/menuItemIcon/__examples__/basic/basic.html +1 -1
  81. package/pg/menuItemIcon/__examples__/basic/basic.ts +7 -0
  82. package/pg/menuItemIcon/menuItemIcon.css +18 -15
  83. package/pg/menuItemIcon/menuItemIcon.ts +8 -4
  84. package/pg/modal/README.md +29 -0
  85. package/pg/modal/__examples__/basic/basic.html +4 -0
  86. package/pg/modal/__examples__/basic/basic.ts +42 -0
  87. package/pg/modal/index.ts +3 -0
  88. package/pg/modal/modal.css +40 -0
  89. package/pg/modal/modal.html +9 -0
  90. package/pg/modal/modal.ts +14 -0
  91. package/pg/modification/__examples__/basic/basic.ts +1 -1
  92. package/pg/overlayMenu/overlayMenu.ts +6 -2
  93. package/pg/overlaySelectMenu/overlaySelectMenu.ts +6 -0
  94. package/pg/overlaySubMenu/overlaySubMenu.ts +6 -2
  95. package/pg/scroll/__examples__/basic/basic.ts +1 -1
  96. package/pg/search/search.css +1 -1
  97. package/pg/table/README.md +108 -0
  98. package/pg/table/__examples__/basic/basic.css +0 -0
  99. package/pg/table/__examples__/basic/basic.html +10 -0
  100. package/pg/table/__examples__/basic/basic.ts +111 -0
  101. package/pg/table/table.css +20 -0
  102. package/pg/table/table.html +6 -0
  103. package/pg/table/table.ts +86 -0
  104. package/pg/tableCellButtonIcon/README.md +3 -0
  105. package/pg/tableCellButtonIcon/tableCellButtonIcon.css +16 -0
  106. package/pg/tableCellButtonIcon/tableCellButtonIcon.html +5 -0
  107. package/pg/tableCellButtonIcon/tableCellButtonIcon.ts +34 -0
  108. package/pg/tableCellCheck/README.md +3 -0
  109. package/pg/tableCellCheck/tableCellCheck.css +15 -0
  110. package/pg/tableCellCheck/tableCellCheck.html +3 -0
  111. package/pg/tableCellCheck/tableCellCheck.ts +43 -0
  112. package/pg/tableCellNumber/README.md +3 -0
  113. package/pg/tableCellNumber/tableCellNumber.css +11 -0
  114. package/pg/tableCellNumber/tableCellNumber.html +3 -0
  115. package/pg/tableCellNumber/tableCellNumber.ts +40 -0
  116. package/pg/tableCellText/README.md +3 -0
  117. package/pg/tableCellText/tableCellText.css +11 -0
  118. package/pg/tableCellText/tableCellText.html +3 -0
  119. package/pg/tableCellText/tableCellText.ts +62 -0
  120. package/pg/tableColumn/README.md +3 -0
  121. package/pg/tableColumn/tableColumn.css +12 -0
  122. package/pg/tableColumn/tableColumn.html +1 -0
  123. package/pg/tableColumn/tableColumn.ts +29 -0
  124. package/pg/tableRow/README.md +3 -0
  125. package/pg/tableRow/tableRow.css +11 -0
  126. package/pg/tableRow/tableRow.html +1 -0
  127. package/pg/tableRow/tableRow.ts +77 -0
  128. package/pg/tabs/tabs.css +1 -1
  129. package/pg/tree/README.md +10 -4
  130. package/pg/tree/__examples__/basic/basic.html +1 -0
  131. package/pg/tree/__examples__/basic/basic.ts +6 -1
  132. package/pg/tree/tree.css +1 -0
  133. package/pg/treeItem/treeItem.css +3 -3
  134. package/favicon.svg +0 -20
  135. package/index.html +0 -321
  136. package/main.js +0 -2
  137. package/main.js.LICENSE.txt +0 -10
  138. package/pgAnnoy.js +0 -1
  139. package/pgApp.js +0 -1
  140. package/pgAvatar.js +0 -1
  141. package/pgButton.js +0 -1
  142. package/pgButtonGroup.js +0 -1
  143. package/pgButtonLink.js +0 -1
  144. package/pgButtonMenu.js +0 -1
  145. package/pgButtonToggle.js +0 -1
  146. package/pgCard.js +0 -1
  147. package/pgCardUser.js +0 -1
  148. package/pgColor.js +0 -1
  149. package/pgDatabase.js +0 -1
  150. package/pgDropdown.js +0 -1
  151. package/pgGrid.js +0 -1
  152. package/pgHeader.js +0 -1
  153. package/pgIcon.js +0 -1
  154. package/pgInputCheck.js +0 -1
  155. package/pgInputCheckList.js +0 -1
  156. package/pgInputFileLocal.js +0 -1
  157. package/pgInputHexRgb.js +0 -1
  158. package/pgInputPixelEditor.js +0 -1
  159. package/pgInputRange.js +0 -1
  160. package/pgInputSelect.js +0 -1
  161. package/pgInputText.js +0 -1
  162. package/pgInputTextIcon.js +0 -1
  163. package/pgInputUserSelect.js +0 -1
  164. package/pgListTag.js +0 -1
  165. package/pgMarkdown.js +0 -2
  166. package/pgMarkdown.js.LICENSE.txt +0 -10
  167. package/pgMenu.js +0 -1
  168. package/pgMenuDivider.js +0 -1
  169. package/pgMenuIcon.js +0 -1
  170. package/pgMenuItem.js +0 -1
  171. package/pgMenuItemIcon.js +0 -1
  172. package/pgModalAlert.js +0 -1
  173. package/pgModification.js +0 -1
  174. package/pgNav.js +0 -1
  175. package/pgOverlay.js +0 -1
  176. package/pgOverlayContextMenu.js +0 -1
  177. package/pgOverlayMenu.js +0 -1
  178. package/pgOverlaySelectMenu.js +0 -1
  179. package/pgOverlaySubMenu.js +0 -1
  180. package/pgPicker.js +0 -1
  181. package/pgPreview.js +0 -1
  182. package/pgScroll.js +0 -1
  183. package/pgSearch.js +0 -1
  184. package/pgTab.js +0 -1
  185. package/pgTabs.js +0 -1
  186. package/pgToast.js +0 -1
  187. package/pgToasts.js +0 -1
  188. package/pgTooltip.js +0 -1
  189. package/pgTree.js +0 -1
  190. package/pgTreeButtonIcon.js +0 -1
  191. package/pgTreeItem.js +0 -1
@@ -6,28 +6,38 @@ interface Edge {
6
6
  }
7
7
 
8
8
  interface Options {
9
+ x?: number;
10
+ y?: number;
9
11
  width?: number;
10
12
  height?: number;
11
13
  scale?: number;
12
14
  offsetX?: number;
13
15
  offsetY?: number;
16
+ include?: number[];
14
17
  }
15
18
 
16
19
  export function toIndex(x: number, y: number, width: number) {
17
20
  return y * width + x;
18
21
  }
19
22
 
23
+ /**
24
+ * Convert a 2d array to a SVG path.
25
+ * @param data
26
+ * @param options
27
+ * @returns path string
28
+ */
20
29
  export default function bitmaskToPath(data: number[] | number[][], options: Options = {}) {
21
30
 
22
- let bitmask: number[] | number[][],
31
+ let bitmask: number[],
23
32
  width: number,
24
33
  height: number,
25
34
  scale = 1,
26
35
  offsetX = 0,
27
- offsetY = 0;
36
+ offsetY = 0,
37
+ include = [1];
28
38
 
29
39
  if (options.width) {
30
- bitmask = data;
40
+ bitmask = data as number[]; // already flat
31
41
  width = options.width;
32
42
  height = bitmask.length / width;
33
43
  if (height % 1 !== 0) {
@@ -53,6 +63,10 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
53
63
  offsetY = options.offsetY;
54
64
  }
55
65
 
66
+ if (options.include) {
67
+ include = options.include;
68
+ }
69
+
56
70
  // Naively copy into a new bitmask with a border of 1 to make sampling easier (no out of bounds checks)
57
71
  const newWidth = width + 2;
58
72
  const newHeight = height + 2;
@@ -65,7 +79,7 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
65
79
 
66
80
  for (let y = 0; y < height; ++y) {
67
81
  for (let x = 0; x < width; ++x) {
68
- bm[BMXYToIndex(x, y)] = bitmask[toIndex(x, y, width)];
82
+ bm[BMXYToIndex(x, y)] = include.includes(bitmask[toIndex(x, y, width)]) ? 1 : 0;
69
83
  }
70
84
  }
71
85
 
@@ -101,7 +115,7 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
101
115
 
102
116
  for (let y = 0; y < height; ++y) {
103
117
  for (let x = 0; x < width; ++x) {
104
- if (bm[BMXYToIndex(x, y)] == 1) {
118
+ if (bm[BMXYToIndex(x, y)] === 1) {
105
119
  const left = bm[BMXYToIndex(x - 1, y)];
106
120
  if (left == 0) {
107
121
  const edge = edges[EdgeYIndex(x, y)];
@@ -116,7 +130,7 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
116
130
  UnionGroup(edge);
117
131
  }
118
132
  const right = bm[BMXYToIndex(x + 1, y)];
119
- if (right == 0) {
133
+ if (right === 0) {
120
134
  const edge = edges[EdgeYIndex(x + 1, y)];
121
135
  SetEdge(edge, x + 1, y);
122
136
  if (bm[BMXYToIndex(x + 1, y + 1)]) {
@@ -129,7 +143,7 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
129
143
  UnionGroup(edge);
130
144
  }
131
145
  const top = bm[BMXYToIndex(x, y - 1)];
132
- if (top == 0) {
146
+ if (top === 0) {
133
147
  const edge: Edge = edges[EdgeXIndex(x, y)];
134
148
  SetEdge(edge, x, y);
135
149
  if (bm[BMXYToIndex(x + 1, y - 1)]) {
@@ -142,7 +156,7 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
142
156
  UnionGroup(edge);
143
157
  }
144
158
  const bottom = bm[BMXYToIndex(x, y + 1)];
145
- if (bottom == 0) {
159
+ if (bottom === 0) {
146
160
  const edge = edges[EdgeXIndex(x, y + 1)];
147
161
  SetEdge(edge, x + 1, y + 1);
148
162
  if (bm[BMXYToIndex(x - 1, y + 1)]) {
@@ -0,0 +1,11 @@
1
+ export function blobToImage(blob) {
2
+ return new Promise(resolve => {
3
+ const url = URL.createObjectURL(blob)
4
+ let img = new Image()
5
+ img.onload = () => {
6
+ URL.revokeObjectURL(url)
7
+ resolve(img)
8
+ }
9
+ img.src = url
10
+ });
11
+ }
@@ -0,0 +1,12 @@
1
+ export async function canvasToPngBuffer(canvas: HTMLCanvasElement): Promise<ArrayBuffer> {
2
+ const blob = await new Promise((resolve: BlobCallback) =>
3
+ canvas.toBlob(resolve, 'image/png')
4
+ );
5
+
6
+ // `canvas.toBlob` can return null, so guard it
7
+ if (!blob) {
8
+ throw new Error("Failed to convert canvas to Blob");
9
+ }
10
+
11
+ return await blob.arrayBuffer();
12
+ }
@@ -1 +1,55 @@
1
- export const WHITE = '#FFFFFF';
1
+ export const WHITE = '#FFFFFF';
2
+
3
+ export type Pixel = { x: number, y: number };
4
+
5
+ export type Point = [number, number];
6
+
7
+ export enum LayerType {
8
+ Pixel = 'pixel',
9
+ Reference = 'reference',
10
+ Pattern = 'pattern',
11
+ Linear = 'linear',
12
+ Radial = 'radial',
13
+ }
14
+
15
+ /**
16
+ * stop 0 to 1
17
+ * colorIndex
18
+ */
19
+ export type GradientStop = [number, number];
20
+
21
+ interface LayerPixel {
22
+ type: LayerType.Pixel;
23
+ path: string;
24
+ color: number;
25
+ }
26
+
27
+ interface LayerReference {
28
+ type: LayerType.Reference;
29
+ id: string;
30
+ position: Point;
31
+ }
32
+
33
+ interface LayerPattern {
34
+ type: LayerType.Pattern;
35
+ id: string;
36
+ path: string;
37
+ offset: Point;
38
+ }
39
+
40
+ interface LayerLinear {
41
+ type: LayerType.Linear;
42
+ start: Point;
43
+ end: Point;
44
+ stops: GradientStop[];
45
+ dither: 'bayer4' | 'bayer8' | 'bayer16';
46
+ }
47
+
48
+ interface LayerRadial {
49
+ type: LayerType.Radial;
50
+ start: Point;
51
+ end: Point;
52
+ stops: GradientStop[];
53
+ transform: [number, number, number, number, number, number];
54
+ dither: 'bayer4' | 'bayer8' | 'bayer16';
55
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Compute CRC32 lookup tables as described at:
3
+ * https://github.com/komrad36/CRC#option-6-1-byte-tabular
4
+ *
5
+ * An iteration of CRC computation can be performed on 8 bits of input at once. By pre-computing a
6
+ * table of the values of CRC(?) for all 2^8 = 256 possible byte values, during the final
7
+ * computation we can replace a loop over 8 bits with a single lookup in the table.
8
+ *
9
+ * For further speedup, we can also pre-compute the values of CRC(?0) for all possible bytes when a
10
+ * zero byte is appended. Then we can process two bytes of input at once by computing CRC(AB) =
11
+ * CRC(A0) ^ CRC(B), using one lookup in the CRC(?0) table and one lookup in the CRC(?) table.
12
+ *
13
+ * The same technique applies for any number of bytes to be processed at once, although the speed
14
+ * improvements diminish.
15
+ *
16
+ * @param polynomial The binary representation of the polynomial to use (reversed, i.e. most
17
+ * significant bit represents x^0).
18
+ * @param numTables The number of bytes of input that will be processed at once.
19
+ */
20
+ function crc32GenerateTables({
21
+ polynomial,
22
+ numTables,
23
+ }: {
24
+ polynomial: number;
25
+ numTables: number;
26
+ }): Uint32Array {
27
+ const table = new Uint32Array(256 * numTables);
28
+ for (let i = 0; i < 256; i++) {
29
+ let r = i;
30
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
31
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
32
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
33
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
34
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
35
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
36
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
37
+ r = ((r & 1) * polynomial) ^ (r >>> 1);
38
+ table[i] = r;
39
+ }
40
+ for (let i = 256; i < table.length; i++) {
41
+ const value = table[i - 256]!;
42
+ table[i] = table[value & 0xff]! ^ (value >>> 8);
43
+ }
44
+ return table;
45
+ }
46
+
47
+ const CRC32_TABLE = crc32GenerateTables({ polynomial: 0xedb88320, numTables: 8 });
48
+
49
+ /**
50
+ * Initialize a CRC32 to all 1 bits.
51
+ */
52
+ function crc32Init(): number {
53
+ return ~0;
54
+ }
55
+
56
+ /**
57
+ * Update a streaming CRC32 calculation.
58
+ *
59
+ * For performance, this implementation processes the data 8 bytes at a time, using the algorithm
60
+ * presented at: https://github.com/komrad36/CRC#option-9-8-byte-tabular
61
+ */
62
+ function crc32Update(prev: number, data: ArrayBufferView): number {
63
+ const byteLength = data.byteLength;
64
+ const view = new DataView(data.buffer, data.byteOffset, byteLength);
65
+ let r = prev;
66
+ let offset = 0;
67
+
68
+ // Process bytes one by one until we reach 4-byte alignment, which will speed up uint32 access.
69
+ const toAlign = -view.byteOffset & 3;
70
+ for (; offset < toAlign && offset < byteLength; offset++) {
71
+ r = CRC32_TABLE[(r ^ view.getUint8(offset)) & 0xff]! ^ (r >>> 8);
72
+ }
73
+ if (offset === byteLength) {
74
+ return r;
75
+ }
76
+
77
+ offset = toAlign;
78
+
79
+ // Process 8 bytes (2 uint32s) at a time.
80
+ let remainingBytes = byteLength - offset;
81
+ for (; remainingBytes >= 8; offset += 8, remainingBytes -= 8) {
82
+ r ^= view.getUint32(offset, true);
83
+ const r2 = view.getUint32(offset + 4, true);
84
+ r =
85
+ CRC32_TABLE[0 * 256 + ((r2 >>> 24) & 0xff)]! ^
86
+ CRC32_TABLE[1 * 256 + ((r2 >>> 16) & 0xff)]! ^
87
+ CRC32_TABLE[2 * 256 + ((r2 >>> 8) & 0xff)]! ^
88
+ CRC32_TABLE[3 * 256 + ((r2 >>> 0) & 0xff)]! ^
89
+ CRC32_TABLE[4 * 256 + ((r >>> 24) & 0xff)]! ^
90
+ CRC32_TABLE[5 * 256 + ((r >>> 16) & 0xff)]! ^
91
+ CRC32_TABLE[6 * 256 + ((r >>> 8) & 0xff)]! ^
92
+ CRC32_TABLE[7 * 256 + ((r >>> 0) & 0xff)]!;
93
+ }
94
+
95
+ // Process any remaining bytes one by one. (Perf note: inexplicably, using a temporary variable
96
+ // `i` rather than reusing `offset` here is faster in V8.)
97
+ for (let i = offset; i < byteLength; i++) {
98
+ r = CRC32_TABLE[(r ^ view.getUint8(i)) & 0xff]! ^ (r >>> 8);
99
+ }
100
+ return r;
101
+ }
102
+
103
+ /**
104
+ * Finalize a CRC32 by inverting the output value. An unsigned right-shift of 0 is used to ensure the result is a positive number.
105
+ */
106
+ function crc32Final(prev: number): number {
107
+ return (prev ^ ~0) >>> 0;
108
+ }
109
+
110
+ /**
111
+ * Calculate a one-shot CRC32. If the data is being accumulated incrementally, use the functions
112
+ * `crc32Init`, `crc32Update`, and `crc32Final` instead.
113
+ */
114
+ export function crc32(data: ArrayBufferView): number {
115
+ return crc32Final(crc32Update(crc32Init(), data));
116
+ }
@@ -0,0 +1,32 @@
1
+ export function diffMap(map1, map2) {
2
+ // 1. Get all keys from both maps as Sets
3
+ const keys1 = new Set(map1.keys());
4
+ const keys2 = new Set(map2.keys());
5
+
6
+ // 2. Combine all unique keys into a single Set
7
+ const allKeys = new Set([...new Set(map1.keys()), ...new Set(map2.keys())]);
8
+
9
+ // 3. Filter the combined keys to find those not present in either original Set
10
+ const uniqueKeys = new Set();
11
+ for (const key of allKeys) {
12
+ if (!keys1.has(key) || !keys2.has(key)) {
13
+ uniqueKeys.add(key);
14
+ }
15
+ }
16
+
17
+ return uniqueKeys;
18
+ }
19
+
20
+ export function diffLeftMapPixels(map1: Map<string, number[]>, map2: Map<string, number[]>) {
21
+ const leftPixels: number[][] = [];
22
+
23
+ // Iterate over all keys in the first map
24
+ for (const key of map1.keys()) {
25
+ // If the second map does not have the current key, add it to the results
26
+ if (!map2.has(key)) {
27
+ leftPixels.push(map1.get(key) as number[]);
28
+ }
29
+ }
30
+
31
+ return leftPixels;
32
+ }
@@ -0,0 +1,112 @@
1
+ // Not used but included
2
+ const byerMatrix2x2 = [
3
+ [0, 2],
4
+ [3, 1]
5
+ ];
6
+
7
+ const bayerMatrix4x4 = [
8
+ [0, 8, 2, 10],
9
+ [12, 4, 14, 6],
10
+ [3, 11, 1, 9],
11
+ [15, 7, 13, 5]
12
+ ];
13
+
14
+ const bayerMatrix8x8 = [
15
+ [0, 32, 8, 40, 2, 34, 10, 42],
16
+ [48, 16, 56, 24, 50, 18, 58, 26],
17
+ [12, 44, 4, 36, 14, 46, 6, 38],
18
+ [60, 28, 52, 20, 62, 30, 54, 22],
19
+ [3, 35, 11, 43, 1, 33, 9, 41],
20
+ [51, 19, 59, 27, 49, 17, 57, 25],
21
+ [15, 47, 7, 39, 13, 45, 5, 37],
22
+ [63, 31, 55, 23, 61, 29, 53, 21]
23
+ ];
24
+
25
+ const bayerMatrix16x16 = [
26
+ [0, 128, 32, 160, 8, 136, 40, 168, 2, 130, 34, 162, 10, 138, 42, 170],
27
+ [192, 64, 224, 96, 200, 72, 232, 104, 194, 66, 226, 98, 202, 74, 234, 106],
28
+ [48, 176, 16, 144, 56, 184, 24, 152, 50, 178, 18, 146, 58, 186, 26, 154],
29
+ [240, 112, 208, 80, 248, 120, 216, 88, 242, 114, 210, 82, 250, 122, 218, 90],
30
+ [12, 140, 44, 172, 4, 132, 36, 164, 14, 142, 46, 174, 6, 134, 38, 166],
31
+ [204, 76, 236, 108, 196, 68, 228, 100, 206, 78, 238, 110, 198, 70, 230, 102],
32
+ [60, 188, 28, 156, 52, 180, 20, 148, 62, 190, 30, 158, 54, 182, 22, 150],
33
+ [252, 124, 220, 92, 244, 116, 212, 84, 254, 126, 222, 94, 246, 118, 214, 86],
34
+ [3, 131, 35, 163, 11, 139, 43, 171, 1, 129, 33, 161, 9, 137, 41, 169],
35
+ [195, 67, 227, 99, 203, 75, 235, 107, 193, 65, 225, 97, 201, 73, 233, 105],
36
+ [51, 179, 19, 147, 59, 187, 27, 155, 49, 177, 17, 145, 57, 185, 25, 153],
37
+ [243, 115, 211, 83, 251, 123, 219, 91, 241, 113, 209, 81, 249, 121, 217, 89],
38
+ [15, 143, 47, 175, 7, 135, 39, 167, 13, 141, 45, 173, 5, 133, 37, 165],
39
+ [207, 79, 239, 111, 199, 71, 231, 103, 205, 77, 237, 109, 197, 69, 229, 101],
40
+ [63, 191, 31, 159, 55, 183, 23, 151, 61, 189, 29, 157, 53, 181, 21, 149],
41
+ [255, 127, 223, 95, 247, 119, 215, 87, 253, 125, 221, 93, 245, 117, 213, 85]
42
+ ];
43
+
44
+ // Normalize the Bayer matrix values to a 0-1 range
45
+ const normalizeBayerMatrix = (matrix) => {
46
+ const maxVal = matrix.flat().reduce((max, val) => Math.max(max, val), 0);
47
+ return matrix.map(row => row.map(val => val / (maxVal + 1))); // +1 to ensure values are strictly less than 1
48
+ };
49
+
50
+ const normalizedBayerMatrix = normalizeBayerMatrix(bayerMatrix4x4);
51
+
52
+ /**
53
+ * Applies Bayer dithering to an image data array between two specified colors.
54
+ * @param imageData The ImageData object containing pixel data.
55
+ * @param color1 The first color (e.g., [r, g, b]).
56
+ * @param color2 The second color (e.g., [r, g, b]).
57
+ * @returns The dithered ImageData object.
58
+ */
59
+ function applyBayerDithering(
60
+ imageData,
61
+ color1,
62
+ color2
63
+ ) {
64
+ const { data, width, height } = imageData;
65
+ const matrixSize = normalizedBayerMatrix.length;
66
+
67
+ for (let y = 0; y < height; y++) {
68
+ for (let x = 0; x < width; x++) {
69
+ const i = (y * width + x) * 4; // Index for R component
70
+
71
+ // Calculate grayscale intensity (luminance) of the original pixel
72
+ const r = data[i];
73
+ const g = data[i + 1];
74
+ const b = data[i + 2];
75
+ const intensity = (0.299 * r + 0.587 * g + 0.114 * b) / 255; // Normalize to 0-1
76
+
77
+ // Get the threshold from the normalized Bayer matrix
78
+ const threshold = normalizedBayerMatrix[y % matrixSize][x % matrixSize];
79
+
80
+ // Assign color based on intensity and threshold
81
+ if (intensity > threshold) {
82
+ data[i] = color2[0];
83
+ data[i + 1] = color2[1];
84
+ data[i + 2] = color2[2];
85
+ } else {
86
+ data[i] = color1[0];
87
+ data[i + 1] = color1[1];
88
+ data[i + 2] = color1[2];
89
+ }
90
+ }
91
+ }
92
+ return imageData;
93
+ }
94
+ /*
95
+ // Example usage (assuming you have an ImageData object from a canvas)
96
+ const canvas = document.querySelector('canvas');
97
+ const ctx = canvas.getContext('2d');
98
+
99
+ const linearGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); // x0, y0, x1, y1
100
+
101
+ // Add color stops
102
+ linearGradient.addColorStop(0, 'white'); // Start color at 0%
103
+ linearGradient.addColorStop(1, 'black'); // End color at 100%
104
+
105
+ // Apply the gradient to fillStyle and draw a rectangle
106
+ ctx.fillStyle = linearGradient;
107
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
108
+
109
+ const originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
110
+ const ditheredImageData = applyBayerDithering(originalImageData, [0, 0, 0], [255, 255, 255]);
111
+ ctx.putImageData(ditheredImageData, 0, 0);
112
+ */
@@ -1,28 +1,140 @@
1
- export default function getEllipsePixels(x0: number, y0: number, x1: number, y1: number) {
2
- let a = Math.abs(x1 - x0), b = Math.abs(y1 - y0), b1 = b & 1; /* values of diameter */
3
- let dx = 4 * (1 - a) * b * b, dy = 4 * (b1 + 1) * a * a; /* error increment */
4
- let err = dx + dy + b1 * a * a, e2; /* error of 1.step */
5
-
6
- if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
7
- if (y0 > y1) y0 = y1; /* .. exchange them */
8
- y0 += (b + 1) / 2; y1 = y0 - b1; /* starting pixel */
9
- a *= 8 * a; b1 = 8 * b * b;
1
+ export function distance(x: number, y: number, ratio: number): number {
2
+ return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2));
3
+ }
4
+
5
+ function filled(x: number, y: number, radius: number, ratio: number): boolean {
6
+ return distance(x, y, ratio) <= radius;
7
+ }
8
+
9
+ function thinfilled(x: number, y: number, radius: number, ratio: number): boolean {
10
+ return filled(x, y, radius, ratio) && !(
11
+ filled(x + 1, y, radius, ratio) &&
12
+ filled(x - 1, y, radius, ratio) &&
13
+ filled(x, y + 1, radius, ratio) &&
14
+ filled(x, y - 1, radius, ratio)
15
+ );
16
+ }
17
+
18
+ function isFilled(x: number, y: number, width: number, height: number): boolean {
19
+ const bounds = {
20
+ minX: 0,
21
+ maxX: width,
22
+ minY: 0,
23
+ maxY: height,
24
+ };
25
+
26
+ x = -.5 * (bounds.maxX - 2 * (x + .5));
27
+ y = -.5 * (bounds.maxY - 2 * (y + .5));
28
+
29
+ return thinfilled(x, y, (bounds.maxX / 2), bounds.maxX / bounds.maxY);
30
+ }
31
+
32
+
33
+ function betterCircle(x0: number, y0: number, x1: number, y1: number) {
34
+ const width = Math.abs(x0 - x1);
35
+ const height = Math.abs(y0 - y1);
36
+ const minX = Math.min(x0, x1);
37
+ const minY = Math.min(y0, y1);
38
+
10
39
  const pixels: { x: number, y: number }[] = [];
40
+
41
+ // Loop through bounding box
42
+ for (let y = 0; y < height; y++) {
43
+ for (let x = 0; x < width; x++) {
44
+ // Center coordinates relative to ellipse
45
+ const cx = -.5 * (width - 2 * (x + 0.5));
46
+ const cy = -.5 * (height - 2 * (y + 0.5));
47
+
48
+ // Use filled() instead of thinfilled() to fill the ellipse
49
+ if (filled(cx, cy, width / 2, width / height)) {
50
+ pixels.push({ x: x + minX, y: y + minY });
51
+ }
52
+ }
53
+ }
54
+
55
+ return pixels;
56
+ }
57
+
58
+
59
+ function ellipse(x0: number, y0: number, x1: number, y1: number) {
60
+ const pixels: { x: number, y: number }[] = [];
61
+
62
+ let a = Math.abs(x1 - x0),
63
+ b = Math.abs(y1 - y0),
64
+ b1 = b & 1;
65
+
66
+ let dx = 4 * (1.0 - a) * b * b,
67
+ dy = 4 * (b1 + 1) * a * a;
68
+
69
+ let err = dx + dy + b1 * a * a,
70
+ e2;
71
+
72
+ if (x0 > x1) {
73
+ x0 = x1;
74
+ x1 += a;
75
+ }
76
+ if (y0 > y1) y0 = y1;
77
+ y0 += (b + 1) >> 1;
78
+ y1 = y0 - b1;
79
+
80
+ a = 8 * a * a;
81
+ b1 = 8 * b * b;
82
+
83
+ // Outline drawing
11
84
  do {
12
- pixels.push({ x: x1, y: y0 }); /* I. Quadrant */
13
- pixels.push({ x: x0, y: y0 }); /* II. Quadrant */
14
- pixels.push({ x: x0, y: y1 }); /* III. Quadrant */
15
- pixels.push({ x: x1, y: y1 }); /* IV. Quadrant */
85
+ pixels.push({ x: x1, y: y0 }); // I Quadrant
86
+ pixels.push({ x: x0, y: y0 }); // II Quadrant
87
+ pixels.push({ x: x0, y: y1 }); // III Quadrant
88
+ pixels.push({ x: x1, y: y1 }); // IV Quadrant
89
+
16
90
  e2 = 2 * err;
17
- if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
18
- if (e2 >= dx || 2 * err > dy) { x0++; x1--; err += dx += b1; } /* x step */
91
+ if (e2 <= dy) {
92
+ y0++;
93
+ y1--;
94
+ err += dy += a;
95
+ }
96
+ if (e2 >= dx || 2 * err > dy) {
97
+ x0++;
98
+ x1--;
99
+ err += dx += b1;
100
+ }
19
101
  } while (x0 <= x1);
20
102
 
21
- while (y0 - y1 < b) { /* too early stop of flat ellipses a=1 */
22
- pixels.push({ x: x0 - 1, y: y0 }); /* -> finish tip of ellipse */
103
+ while (y0 - y1 <= b) {
104
+ pixels.push({ x: x0 - 1, y: y0 });
23
105
  pixels.push({ x: x1 + 1, y: y0++ });
24
106
  pixels.push({ x: x0 - 1, y: y1 });
25
107
  pixels.push({ x: x1 + 1, y: y1-- });
26
108
  }
27
- return pixels;
28
- }
109
+
110
+ // --- Fill interior ---
111
+ // Group pixels by row (y), then fill between minX and maxX
112
+ const rows: Record<number, { minX: number; maxX: number }> = {};
113
+ for (const p of pixels) {
114
+ if (!(p.y in rows)) {
115
+ rows[p.y] = { minX: p.x, maxX: p.x };
116
+ } else {
117
+ rows[p.y].minX = Math.min(rows[p.y].minX, p.x);
118
+ rows[p.y].maxX = Math.max(rows[p.y].maxX, p.x);
119
+ }
120
+ }
121
+
122
+ const filledPixels: { x: number; y: number }[] = [...pixels];
123
+ for (const y in rows) {
124
+ const { minX, maxX } = rows[y];
125
+ for (let x = minX; x <= maxX; x++) {
126
+ filledPixels.push({ x, y: parseInt(y) });
127
+ }
128
+ }
129
+
130
+ return filledPixels;
131
+ }
132
+
133
+
134
+ export default function getEllipsePixels(x0: number, y0: number, x1: number, y1: number) {
135
+ if (Math.abs(x0 - x1) === Math.abs(y0 - y1) && Math.abs(x0 - x1)) {
136
+ console.log('circle', Math.abs(x0 - x1), Math.abs(y0 - y1))
137
+ return betterCircle(x0, y0, x1 + 1, y1 + 1);
138
+ }
139
+ return ellipse(x0, y0, x1, y1);
140
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Represents a coordinate in the 2D array.
3
+ */
4
+ type Coordinate = [number, number];
5
+
6
+ /**
7
+ * Flood fills a 2D array starting from a specific coordinate to find all connected numbers
8
+ * that are within a specified set of target values.
9
+ *
10
+ * @param grid The 2D array (grid) of numbers.
11
+ * @param startX The starting x-coordinate.
12
+ * @param startY The starting y-coordinate.
13
+ * @param targetValues The set of numbers to search for (e.g., [1, 2, 5]).
14
+ * @returns A list of [x, y] coordinates that were found during the flood fill.
15
+ */
16
+ export function getFloodFill(
17
+ grid: number[][],
18
+ startX: number,
19
+ startY: number,
20
+ targetValues: number[]
21
+ ): Coordinate[] {
22
+ // Use a Set for quick lookups of target values
23
+ const targets = new Set(targetValues);
24
+
25
+ // Check if starting coordinates are valid and if the value at the start is a target
26
+ if (
27
+ startY < 0 ||
28
+ startY >= grid.length ||
29
+ startX < 0 ||
30
+ startX >= grid[0].length ||
31
+ !targets.has(grid[startY][startX])
32
+ ) {
33
+ return [];
34
+ }
35
+
36
+ // Queue for Breadth-First Search (BFS)
37
+ const queue: Coordinate[] = [[startX, startY]];
38
+ // Set to keep track of visited coordinates to avoid cycles and redundant processing
39
+ const visited = new Set<string>();
40
+ const foundPixels: Coordinate[] = [];
41
+
42
+ // Helper to convert [x, y] to a string key for the 'visited' set
43
+ const toKey = (x: number, y: number) => `${x},${y}`;
44
+
45
+ // Mark the starting pixel as visited and add to found list
46
+ visited.add(toKey(startX, startY));
47
+ foundPixels.push([startX, startY]);
48
+
49
+ while (queue.length > 0) {
50
+ const [currentX, currentY] = queue.shift()!;
51
+ const currentValue = grid[currentY][currentX];
52
+
53
+ // Define potential neighbors (up, down, left, right)
54
+ const neighbors: Coordinate[] = [
55
+ [currentX + 1, currentY],
56
+ [currentX - 1, currentY],
57
+ [currentX, currentY + 1],
58
+ [currentX, currentY - 1],
59
+ ];
60
+
61
+ for (const [nextX, nextY] of neighbors) {
62
+ // Check bounds
63
+ if (
64
+ nextY >= 0 &&
65
+ nextY < grid.length &&
66
+ nextX >= 0 &&
67
+ nextX < grid[0].length
68
+ ) {
69
+ const neighborValue = grid[nextY][nextX];
70
+ const key = toKey(nextX, nextY);
71
+
72
+ // If the neighbor has a target value and has not been visited yet
73
+ if (targets.has(neighborValue) && !visited.has(key)) {
74
+ visited.add(key);
75
+ foundPixels.push([nextX, nextY]);
76
+ queue.push([nextX, nextY]);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ return foundPixels;
83
+ }