poppygl 0.0.22 → 0.0.24

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/dist/index.d.ts CHANGED
@@ -195,8 +195,17 @@ declare function drawInfiniteGrid(software_renderer: SoftwareRenderer, params: {
195
195
 
196
196
  declare const pureImageFactory: ImageFactory;
197
197
 
198
+ declare function encodePNG(image: BitmapLike): Promise<Uint8Array>;
199
+
198
200
  declare function encodePNGToBuffer(image: BitmapLike): Promise<Buffer>;
199
201
 
202
+ interface RenderGLTFToPNGFromURLOptions extends RenderOptionsInput, LoadGLTFWithResourcesFromURLOptions {
203
+ }
204
+ declare function renderGLTFToPNGFromURL(url: string, options?: RenderGLTFToPNGFromURLOptions): Promise<Uint8Array>;
205
+
206
+ type RenderGLTFToPNGFromGLBOptions = RenderOptionsInput;
207
+ declare function renderGLTFToPNGFromGLB(glb: ArrayBuffer | Uint8Array, options?: RenderGLTFToPNGFromGLBOptions): Promise<Uint8Array>;
208
+
200
209
  interface RenderGLTFToPNGBufferFromURLOptions extends RenderOptionsInput, LoadGLTFWithResourcesFromURLOptions {
201
210
  }
202
211
  declare function renderGLTFToPNGBufferFromURL(url: string, options?: RenderGLTFToPNGBufferFromURLOptions): Promise<Buffer>;
@@ -210,4 +219,4 @@ declare function renderGLTFToPNGBuffer(gltfOrJson: string | any, options?: Rende
210
219
 
211
220
  declare function renderSceneFromGLTF(scene: GLTFScene, options?: RenderOptionsInput, imageFactory?: ImageFactory): RenderResult;
212
221
 
213
- export { type BitmapLike, type Camera, type CameraRotation, type CameraUp, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DebugPoint, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type RGBA, type RenderGLTFToPNGBufferFromGLBBufferOptions, type RenderGLTFToPNGBufferFromURLOptions, type RenderGLTFToPNGBufferOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createGrid, createInfiniteGrid, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNGToBuffer, getDefaultRenderOptions, hexToRgb, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderSceneFromGLTF, resolveRenderOptions };
222
+ export { type BitmapLike, type Camera, type CameraRotation, type CameraUp, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DebugPoint, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type RGBA, type RenderGLTFToPNGBufferFromGLBBufferOptions, type RenderGLTFToPNGBufferFromURLOptions, type RenderGLTFToPNGBufferOptions, type RenderGLTFToPNGFromGLBOptions, type RenderGLTFToPNGFromURLOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createGrid, createInfiniteGrid, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNG, encodePNGToBuffer, getDefaultRenderOptions, hexToRgb, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderGLTFToPNGFromGLB, renderGLTFToPNGFromURL, renderSceneFromGLTF, resolveRenderOptions };
package/dist/index.js CHANGED
@@ -1610,12 +1610,51 @@ function createSceneFromGLTF(gltf, resources) {
1610
1610
  }
1611
1611
 
1612
1612
  // lib/gltf/resourceUtils.ts
1613
- import { PassThrough } from "readable-stream";
1614
- import * as PImage from "pureimage";
1613
+ import { decode } from "fast-png";
1614
+
1615
+ // lib/utils/bytes.ts
1616
+ function getNodeBuffer() {
1617
+ const maybeBuffer = globalThis.Buffer;
1618
+ if (!maybeBuffer) {
1619
+ throw new Error("Node Buffer API is not available in this runtime.");
1620
+ }
1621
+ return maybeBuffer;
1622
+ }
1623
+ function toUint8Array(chunk) {
1624
+ if (chunk instanceof Uint8Array) return chunk;
1625
+ if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
1626
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
1627
+ }
1628
+ function concatUint8Arrays(chunks) {
1629
+ const totalLength = chunks.reduce((length, chunk) => length + chunk.length, 0);
1630
+ const result = new Uint8Array(totalLength);
1631
+ let offset = 0;
1632
+ for (const chunk of chunks) {
1633
+ result.set(chunk, offset);
1634
+ offset += chunk.length;
1635
+ }
1636
+ return result;
1637
+ }
1638
+ function base64ToUint8Array(base64) {
1639
+ if (typeof globalThis.atob === "function") {
1640
+ const binary = globalThis.atob(base64);
1641
+ const bytes = new Uint8Array(binary.length);
1642
+ for (let i = 0; i < binary.length; i++) {
1643
+ bytes[i] = binary.charCodeAt(i);
1644
+ }
1645
+ return bytes;
1646
+ }
1647
+ return new Uint8Array(getNodeBuffer().from(base64, "base64"));
1648
+ }
1649
+ function uint8ArrayToNodeBuffer(bytes) {
1650
+ return getNodeBuffer().from(bytes);
1651
+ }
1652
+
1653
+ // lib/gltf/resourceUtils.ts
1615
1654
  function bufferFromDataURI(uri) {
1616
1655
  const match = uri.match(/^data:.*?;base64,(.*)$/);
1617
1656
  if (!match) throw new Error(`Unsupported data URI: ${uri.slice(0, 64)}...`);
1618
- return Buffer.from(match[1], "base64");
1657
+ return base64ToUint8Array(match[1]);
1619
1658
  }
1620
1659
  function isPNG(filenameOrUri) {
1621
1660
  return /\.png(\?|$)/i.test(filenameOrUri) || /image\/png/i.test(filenameOrUri);
@@ -1630,20 +1669,35 @@ function detectMimeTypeFromBuffer(buf, hint) {
1630
1669
  if (buf.length >= 2 && buf[0] === 255 && buf[1] === 216) return "image/jpeg";
1631
1670
  return null;
1632
1671
  }
1633
- function bufferToStream(buf) {
1634
- const stream = new PassThrough();
1635
- stream.end(Buffer.from(buf));
1636
- return stream;
1672
+ async function decodeImageViaFastPng(buf) {
1673
+ const decoded = decode(buf);
1674
+ return {
1675
+ width: decoded.width,
1676
+ height: decoded.height,
1677
+ data: new Uint8ClampedArray(decoded.data)
1678
+ };
1679
+ }
1680
+ async function decodeImageViaPureImage(buf, mimeType) {
1681
+ const { PassThrough: PassThrough2 } = await import("readable-stream");
1682
+ const PImage3 = await import("pureimage");
1683
+ const stream = new PassThrough2();
1684
+ stream.end(buf);
1685
+ if (mimeType === "image/png") return PImage3.decodePNGFromStream(stream);
1686
+ if (mimeType === "image/jpeg" || mimeType === "image/jpg")
1687
+ return PImage3.decodeJPEGFromStream(stream);
1688
+ throw new Error(`Unsupported embedded image mimeType: ${mimeType}`);
1637
1689
  }
1638
1690
  async function decodeImageFromBuffer(buf, mimeType) {
1639
1691
  const type = detectMimeTypeFromBuffer(buf, mimeType);
1640
- if (type === "image/png")
1641
- return PImage.decodePNGFromStream(bufferToStream(buf));
1642
- if (type === "image/jpeg" || type === "image/jpg")
1643
- return PImage.decodeJPEGFromStream(bufferToStream(buf));
1644
- throw new Error(
1645
- `Unsupported embedded image mimeType: ${mimeType ?? "unknown"}`
1646
- );
1692
+ if (!type) {
1693
+ throw new Error(
1694
+ `Unsupported embedded image mimeType: ${mimeType ?? "unknown"}`
1695
+ );
1696
+ }
1697
+ if (type === "image/png") {
1698
+ return decodeImageViaFastPng(buf);
1699
+ }
1700
+ return decodeImageViaPureImage(buf, type);
1647
1701
  }
1648
1702
 
1649
1703
  // lib/gltf/parseGLB.ts
@@ -1799,26 +1853,136 @@ async function loadGLTFWithResourcesFromURL(url, options = {}) {
1799
1853
  }
1800
1854
 
1801
1855
  // lib/image/pureImageFactory.ts
1802
- import * as PImage2 from "pureimage";
1803
- var pureImageFactory = (width, height) => PImage2.make(width, height);
1856
+ import * as PImage from "pureimage";
1857
+ var pureImageFactory = (width, height) => PImage.make(width, height);
1858
+
1859
+ // lib/image/encodePNG.ts
1860
+ import { encode } from "fast-png";
1861
+ async function encodePNG(image) {
1862
+ return encode({
1863
+ width: image.width,
1864
+ height: image.height,
1865
+ data: image.data,
1866
+ channels: 4,
1867
+ depth: 8
1868
+ });
1869
+ }
1804
1870
 
1805
1871
  // lib/image/encodePNGToBuffer.ts
1806
- import { PassThrough as PassThrough2 } from "readable-stream";
1807
- import * as PImage3 from "pureimage";
1872
+ import { PassThrough } from "readable-stream";
1873
+ import * as PImage2 from "pureimage";
1808
1874
  async function encodePNGToBuffer(image) {
1809
- const passThrough = new PassThrough2();
1875
+ const passThrough = new PassThrough();
1810
1876
  const chunks = [];
1811
- passThrough.on("data", (chunk) => {
1812
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1813
- });
1877
+ passThrough.on(
1878
+ "data",
1879
+ (chunk) => {
1880
+ chunks.push(toUint8Array(chunk));
1881
+ }
1882
+ );
1814
1883
  const resultPromise = new Promise((resolve, reject) => {
1815
- passThrough.on("end", () => resolve(Buffer.concat(chunks)));
1884
+ passThrough.on("end", () => {
1885
+ resolve(uint8ArrayToNodeBuffer(concatUint8Arrays(chunks)));
1886
+ });
1816
1887
  passThrough.on("error", reject);
1817
1888
  });
1818
- await PImage3.encodePNGToStream(image, passThrough);
1889
+ await PImage2.encodePNGToStream(image, passThrough);
1819
1890
  return await resultPromise;
1820
1891
  }
1821
1892
 
1893
+ // lib/render/renderGLTFToPNGFromURL.ts
1894
+ async function renderGLTFToPNGFromURL(url, options = {}) {
1895
+ const { fetchImpl, ...renderOptions } = options;
1896
+ const { gltf, resources } = await loadGLTFWithResourcesFromURL(url, {
1897
+ fetchImpl
1898
+ });
1899
+ const scene = createSceneFromGLTF(gltf, resources);
1900
+ const { bitmap } = renderDrawCalls(
1901
+ scene.drawCalls,
1902
+ renderOptions,
1903
+ createUint8Bitmap
1904
+ );
1905
+ return encodePNG(bitmap);
1906
+ }
1907
+
1908
+ // lib/render/renderGLTFToPNGFromGLB.ts
1909
+ function normalizeToArrayBuffer(source) {
1910
+ if (source instanceof ArrayBuffer) return source;
1911
+ const { buffer, byteOffset, byteLength } = source;
1912
+ const viewBuffer = buffer;
1913
+ return viewBuffer.slice(byteOffset, byteOffset + byteLength);
1914
+ }
1915
+ async function renderGLTFToPNGFromGLB(glb, options = {}) {
1916
+ const arrayBuffer = normalizeToArrayBuffer(glb);
1917
+ const { gltf, binaryChunk } = parseGLB(arrayBuffer);
1918
+ const totalBuffers = Array.isArray(gltf.buffers) ? gltf.buffers.length : 0;
1919
+ const buffers = new Array(totalBuffers);
1920
+ for (let index = 0; index < totalBuffers; index++) {
1921
+ const entry = gltf.buffers[index];
1922
+ if (entry?.uri) {
1923
+ if (!entry.uri.startsWith("data:")) {
1924
+ throw new Error(
1925
+ `Buffer ${index} references external resource ${entry.uri}. Use renderGLTFToPNGFromURL for GLBs with external buffer URIs.`
1926
+ );
1927
+ }
1928
+ buffers[index] = bufferFromDataURI(entry.uri);
1929
+ continue;
1930
+ }
1931
+ if (!binaryChunk) {
1932
+ throw new Error(
1933
+ `GLB is missing the binary chunk required for buffer ${index}.`
1934
+ );
1935
+ }
1936
+ buffers[index] = binaryChunk;
1937
+ }
1938
+ const resolveBufferViewSlice = (bufferViewIndex) => {
1939
+ const bufferView = gltf.bufferViews?.[bufferViewIndex];
1940
+ if (!bufferView) {
1941
+ throw new Error(`Invalid bufferView index ${bufferViewIndex}.`);
1942
+ }
1943
+ const bufferIndex = bufferView.buffer;
1944
+ const buffer = buffers[bufferIndex];
1945
+ if (!buffer) {
1946
+ throw new Error(
1947
+ `Missing buffer data for bufferView ${bufferViewIndex} (buffer ${bufferIndex}).`
1948
+ );
1949
+ }
1950
+ const byteOffset = bufferView.byteOffset ?? 0;
1951
+ const byteLength = bufferView.byteLength;
1952
+ if (typeof byteLength !== "number") {
1953
+ throw new Error(`bufferView ${bufferViewIndex} is missing byteLength.`);
1954
+ }
1955
+ return buffer.subarray(byteOffset, byteOffset + byteLength);
1956
+ };
1957
+ const images = await Promise.all(
1958
+ (gltf.images ?? []).map(async (image, imageIndex) => {
1959
+ if (image.uri) {
1960
+ if (!image.uri.startsWith("data:")) {
1961
+ throw new Error(
1962
+ `Image ${imageIndex} references external resource ${image.uri}. Embed textures or load via renderGLTFToPNGFromURL.`
1963
+ );
1964
+ }
1965
+ const imageBytes = bufferFromDataURI(image.uri);
1966
+ return decodeImageFromBuffer(imageBytes, image.mimeType);
1967
+ }
1968
+ if (typeof image.bufferView === "number") {
1969
+ const slice = resolveBufferViewSlice(image.bufferView);
1970
+ return decodeImageFromBuffer(slice, image.mimeType);
1971
+ }
1972
+ throw new Error(
1973
+ `Image ${imageIndex} must supply a data URI or bufferView for GLB rendering.`
1974
+ );
1975
+ })
1976
+ );
1977
+ const scene = createSceneFromGLTF(gltf, { buffers, images });
1978
+ const { bitmap } = renderDrawCalls(
1979
+ scene.drawCalls,
1980
+ options,
1981
+ createUint8Bitmap
1982
+ );
1983
+ return encodePNG(bitmap);
1984
+ }
1985
+
1822
1986
  // lib/render/renderGLTFToPNGBufferFromURL.ts
1823
1987
  async function renderGLTFToPNGBufferFromURL(url, options = {}) {
1824
1988
  const { fetchImpl, ...renderOptions } = options;
@@ -1835,14 +1999,14 @@ async function renderGLTFToPNGBufferFromURL(url, options = {}) {
1835
1999
  }
1836
2000
 
1837
2001
  // lib/render/renderGLTFToPNGBufferFromGLBBuffer.ts
1838
- function normalizeToArrayBuffer(source) {
2002
+ function normalizeToArrayBuffer2(source) {
1839
2003
  if (source instanceof ArrayBuffer) return source;
1840
2004
  const { buffer, byteOffset, byteLength } = source;
1841
2005
  const viewBuffer = buffer;
1842
2006
  return viewBuffer.slice(byteOffset, byteOffset + byteLength);
1843
2007
  }
1844
2008
  async function renderGLTFToPNGBufferFromGLBBuffer(glb, options = {}) {
1845
- const arrayBuffer = normalizeToArrayBuffer(glb);
2009
+ const arrayBuffer = normalizeToArrayBuffer2(glb);
1846
2010
  const { gltf, binaryChunk } = parseGLB(arrayBuffer);
1847
2011
  const totalBuffers = Array.isArray(gltf.buffers) ? gltf.buffers.length : 0;
1848
2012
  const buffers = new Array(totalBuffers);
@@ -2044,6 +2208,7 @@ export {
2044
2208
  createUint8Bitmap,
2045
2209
  decodeImageFromBuffer,
2046
2210
  drawInfiniteGrid,
2211
+ encodePNG,
2047
2212
  encodePNGToBuffer,
2048
2213
  getDefaultRenderOptions,
2049
2214
  hexToRgb,
@@ -2055,6 +2220,8 @@ export {
2055
2220
  renderGLTFToPNGBuffer,
2056
2221
  renderGLTFToPNGBufferFromGLBBuffer,
2057
2222
  renderGLTFToPNGBufferFromURL,
2223
+ renderGLTFToPNGFromGLB,
2224
+ renderGLTFToPNGFromURL,
2058
2225
  renderSceneFromGLTF,
2059
2226
  resolveRenderOptions
2060
2227
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poppygl",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.22",
4
+ "version": "0.0.24",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tscircuit/poppygl.git"
@@ -37,6 +37,7 @@
37
37
  "typescript": "^5"
38
38
  },
39
39
  "dependencies": {
40
+ "fast-png": "^8.0.0",
40
41
  "gl-matrix": "^3.4.4",
41
42
  "pureimage": "^0.4.18",
42
43
  "readable-stream": "^4.7.0"