@cazala/party 0.4.0 → 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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ A high-performance TypeScript particle physics engine with dual runtime support
12
12
  - **Advanced Rendering**: Trails, particle instancing, line rendering with multiple color modes
13
13
  - **Export/Import Presets**: Export/import module settings (inputs + enabled state)
14
14
  - **Cross-platform**: Works in all modern browsers with automatic feature detection
15
- - **Spawner Utility**: Generate particle shapes, including text
15
+ - **Spawner Utility**: Generate particle shapes, including text and images
16
16
 
17
17
  ## Installation
18
18
 
@@ -192,7 +192,7 @@ engine.unpinAll();
192
192
 
193
193
  ### Spawner
194
194
 
195
- Generate particle arrays from common shapes (including text) using `Spawner`:
195
+ Generate particle arrays from common shapes (including text and images) using `Spawner`:
196
196
 
197
197
  ```typescript
198
198
  import { Spawner } from "@cazala/party";
@@ -220,6 +220,28 @@ Notes:
220
220
  - `size` controls particle radius; `textSize` is the font size used to rasterize text.
221
221
  - Playground font options: `sans-serif`, `serif`, `monospace`.
222
222
 
223
+ Image example:
224
+
225
+ ```typescript
226
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
227
+ const imageParticles = spawner.initParticles({
228
+ count: 12000,
229
+ shape: "image",
230
+ center: { x: 0, y: 0 },
231
+ position: { x: 0, y: 0 },
232
+ align: { horizontal: "center", vertical: "center" },
233
+ imageData,
234
+ imageSize: 400, // scales to this max dimension
235
+ size: 3,
236
+ mass: 1,
237
+ });
238
+ ```
239
+
240
+ Notes:
241
+
242
+ - `imageData` must be provided synchronously (no URL fetching inside the spawner).
243
+ - Fully transparent pixels are skipped; particle colors come from image pixels.
244
+
223
245
  ### Modules
224
246
 
225
247
  Modules are pluggable components that contribute to simulation or rendering:
package/dist/index.js CHANGED
@@ -5473,7 +5473,7 @@ function calculateVelocity(position, center, cfg) {
5473
5473
  }
5474
5474
  class Spawner {
5475
5475
  initParticles(options) {
5476
- const { count, shape, center, spacing = 25, radius = 100, innerRadius = 50, squareSize = 200, cornerRadius = 0, size = 5, mass = 1, bounds, velocity, colors, text = "Party", font = "sans-serif", textSize = 64, position, align, } = options;
5476
+ const { count, shape, center, spacing = 25, radius = 100, innerRadius = 50, squareSize = 200, cornerRadius = 0, size = 5, mass = 1, bounds, velocity, colors, text = "Party", font = "sans-serif", textSize = 64, position, align, imageData, imageSize, } = options;
5477
5477
  const particles = [];
5478
5478
  if (count <= 0)
5479
5479
  return particles;
@@ -5587,6 +5587,99 @@ class Spawner {
5587
5587
  }
5588
5588
  return particles;
5589
5589
  }
5590
+ if (shape === "image") {
5591
+ if (!imageData)
5592
+ return particles;
5593
+ const width = Math.floor(imageData.width);
5594
+ const height = Math.floor(imageData.height);
5595
+ if (!Number.isFinite(width) ||
5596
+ !Number.isFinite(height) ||
5597
+ width <= 0 ||
5598
+ height <= 0)
5599
+ return particles;
5600
+ const data = imageData.data;
5601
+ const sampleStepTarget = Math.max(1, Math.round(size));
5602
+ const targetSize = typeof imageSize === "number" && Number.isFinite(imageSize) && imageSize > 0
5603
+ ? imageSize
5604
+ : Math.max(width, height);
5605
+ const scale = Math.max(0.0001, targetSize / Math.max(width, height));
5606
+ const sampleStepSource = Math.max(1, Math.round(sampleStepTarget / scale));
5607
+ const points = [];
5608
+ for (let y = 0; y < height; y += sampleStepSource) {
5609
+ for (let x = 0; x < width; x += sampleStepSource) {
5610
+ const idx = (y * width + x) * 4;
5611
+ const alpha = data[idx + 3];
5612
+ if (alpha === 0)
5613
+ continue;
5614
+ points.push({
5615
+ x: x * scale,
5616
+ y: y * scale,
5617
+ color: {
5618
+ r: data[idx] / 255,
5619
+ g: data[idx + 1] / 255,
5620
+ b: data[idx + 2] / 255,
5621
+ a: alpha / 255,
5622
+ },
5623
+ });
5624
+ }
5625
+ }
5626
+ const maxCount = Math.max(0, Math.floor(count));
5627
+ if (maxCount <= 0 || points.length === 0)
5628
+ return particles;
5629
+ const imagePosition = position ?? center;
5630
+ const horizontal = align?.horizontal ?? "center";
5631
+ const vertical = align?.vertical ?? "center";
5632
+ const scaledWidth = width * scale;
5633
+ const scaledHeight = height * scale;
5634
+ const originX = horizontal === "left"
5635
+ ? imagePosition.x
5636
+ : horizontal === "right"
5637
+ ? imagePosition.x - scaledWidth
5638
+ : imagePosition.x - scaledWidth / 2;
5639
+ const originY = vertical === "top"
5640
+ ? imagePosition.y
5641
+ : vertical === "bottom"
5642
+ ? imagePosition.y - scaledHeight
5643
+ : imagePosition.y - scaledHeight / 2;
5644
+ const baseCount = Math.min(maxCount, points.length);
5645
+ const stride = points.length / baseCount;
5646
+ for (let i = 0; i < baseCount; i++) {
5647
+ const idx = Math.floor(i * stride);
5648
+ const point = points[idx];
5649
+ if (!point)
5650
+ continue;
5651
+ const x = originX + point.x;
5652
+ const y = originY + point.y;
5653
+ const { vx, vy } = calculateVelocity({ x, y }, imagePosition, velocity);
5654
+ particles.push({
5655
+ position: { x, y },
5656
+ velocity: { x: vx, y: vy },
5657
+ size,
5658
+ mass,
5659
+ color: point.color,
5660
+ });
5661
+ }
5662
+ const extraCount = maxCount - baseCount;
5663
+ if (extraCount > 0) {
5664
+ const jitter = sampleStepTarget * 0.4;
5665
+ for (let i = 0; i < extraCount; i++) {
5666
+ const point = points[Math.floor(Math.random() * points.length)];
5667
+ if (!point)
5668
+ continue;
5669
+ const x = originX + point.x + (Math.random() - 0.5) * jitter;
5670
+ const y = originY + point.y + (Math.random() - 0.5) * jitter;
5671
+ const { vx, vy } = calculateVelocity({ x, y }, imagePosition, velocity);
5672
+ particles.push({
5673
+ position: { x, y },
5674
+ velocity: { x: vx, y: vy },
5675
+ size,
5676
+ mass,
5677
+ color: point.color,
5678
+ });
5679
+ }
5680
+ }
5681
+ return particles;
5682
+ }
5590
5683
  if (shape === "grid") {
5591
5684
  const cols = Math.ceil(Math.sqrt(count));
5592
5685
  const rows = Math.ceil(count / cols);