@pixagram/renderart 0.2.0 → 0.2.1

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.mjs CHANGED
@@ -618,38 +618,85 @@ var HEX_PRESETS = {
618
618
  };
619
619
 
620
620
  // src/wasm-loader.ts
621
- var wasmModule = null;
621
+ var wasm = null;
622
622
  var wasmMemory = null;
623
623
  var initPromise = null;
624
- async function initWasm(wasmInit, wasm) {
625
- if (wasmModule) return;
624
+ var wasmUrl = "";
625
+ var cachedUint8Memory = null;
626
+ var WASM_VECTOR_LEN = 0;
627
+ function getUint8Memory() {
628
+ if (cachedUint8Memory === null || cachedUint8Memory.byteLength === 0) {
629
+ cachedUint8Memory = new Uint8Array(wasmMemory.buffer);
630
+ }
631
+ return cachedUint8Memory;
632
+ }
633
+ function passArray8ToWasm(arg) {
634
+ const ptr = wasm.__wbindgen_malloc(arg.length);
635
+ getUint8Memory().set(arg, ptr);
636
+ WASM_VECTOR_LEN = arg.length;
637
+ return ptr;
638
+ }
639
+ function setWasmUrl(url) {
640
+ wasmUrl = url;
641
+ }
642
+ function getDefaultWasmUrl() {
643
+ if (wasmUrl) return wasmUrl;
644
+ const paths = [
645
+ "/renderart_wasm_bg.wasm",
646
+ "/wasm/renderart_wasm_bg.wasm",
647
+ "/static/wasm/renderart_wasm_bg.wasm",
648
+ "/assets/wasm/renderart_wasm_bg.wasm"
649
+ ];
650
+ return paths[0];
651
+ }
652
+ async function initWasm(customUrl) {
653
+ if (wasm) return;
626
654
  if (initPromise) return initPromise;
655
+ const url = customUrl || getDefaultWasmUrl();
627
656
  initPromise = (async () => {
628
657
  try {
629
- await wasmInit();
630
- wasmModule = wasm;
631
- if (wasmModule.get_memory) {
632
- wasmMemory = wasmModule.get_memory();
658
+ const response = await fetch(url);
659
+ if (!response.ok) {
660
+ throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
661
+ }
662
+ const bytes = await response.arrayBuffer();
663
+ const imports = {
664
+ __wbindgen_placeholder__: {
665
+ __wbindgen_throw: (ptr, len) => {
666
+ const msg = new TextDecoder().decode(getUint8Memory().subarray(ptr, ptr + len));
667
+ throw new Error(msg);
668
+ }
669
+ },
670
+ wbg: {
671
+ __wbindgen_throw: (ptr, len) => {
672
+ const msg = new TextDecoder().decode(getUint8Memory().subarray(ptr, ptr + len));
673
+ throw new Error(msg);
674
+ }
675
+ }
676
+ };
677
+ const { instance } = await WebAssembly.instantiate(bytes, imports);
678
+ wasm = instance.exports;
679
+ wasmMemory = wasm.memory;
680
+ if (wasm.__wbindgen_start) {
681
+ wasm.__wbindgen_start();
633
682
  }
634
683
  } catch (e) {
635
684
  initPromise = null;
636
- throw new Error(`Failed to initialize WASM module: ${e}`);
685
+ throw new Error(`Failed to initialize WASM: ${e}. Make sure the .wasm file is served at: ${url}`);
637
686
  }
638
687
  })();
639
688
  return initPromise;
640
689
  }
641
690
  function isWasmLoaded() {
642
- return wasmModule !== null;
691
+ return wasm !== null;
643
692
  }
644
- function getWasmModule() {
645
- if (!wasmModule) {
646
- throw new Error("WASM not initialized. Call initWasm() first.");
693
+ async function ensureWasm() {
694
+ if (!wasm) {
695
+ await initWasm();
647
696
  }
648
- return wasmModule;
649
697
  }
650
- function getOutputData(result) {
651
- if (!wasmMemory) throw new Error("WASM memory not available");
652
- const buffer = new Uint8Array(wasmMemory.buffer, result.ptr, result.len);
698
+ function getOutputData(ptr, len) {
699
+ const buffer = getUint8Memory().subarray(ptr, ptr + len);
653
700
  return new Uint8ClampedArray(buffer.slice());
654
701
  }
655
702
  function toUint8Array(input) {
@@ -675,45 +722,49 @@ var CrtCpuRenderer = class _CrtCpuRenderer {
675
722
  constructor() {
676
723
  this.ready = false;
677
724
  }
678
- /** Create renderer (WASM must be initialized first via initWasm) */
679
- static create() {
680
- if (!isWasmLoaded()) {
681
- throw new Error("WASM not initialized. Call initWasm() first.");
682
- }
725
+ /** Create and initialize renderer (auto-loads WASM) */
726
+ static async create() {
727
+ await ensureWasm();
683
728
  const renderer = new _CrtCpuRenderer();
684
729
  renderer.ready = true;
685
730
  return renderer;
686
731
  }
687
- /** Check if renderer is ready */
688
732
  isReady() {
689
733
  return this.ready && isWasmLoaded();
690
734
  }
691
- /** Render CRT effect */
692
735
  render(input, options = {}) {
693
- if (!this.ready || !wasmModule) {
736
+ if (!this.ready || !wasm) {
694
737
  throw new Error("Renderer not initialized");
695
738
  }
696
739
  const data = toUint8Array(input);
697
740
  const width = input.width;
698
741
  const height = input.height;
699
742
  const scale = Math.min(32, Math.max(2, options.scale ?? 3));
700
- const config = new wasmModule.CrtConfig();
701
- config.warp_x = options.warpX ?? 0.015;
702
- config.warp_y = options.warpY ?? 0.02;
703
- config.scan_hardness = options.scanHardness ?? -4;
704
- config.scan_opacity = options.scanOpacity ?? 0.5;
705
- config.mask_opacity = options.maskOpacity ?? 0.3;
706
- config.enable_warp = options.enableWarp !== false;
707
- config.enable_scanlines = options.enableScanlines !== false;
708
- config.enable_mask = options.enableMask !== false;
709
- const result = wasmModule.crt_upscale_config(data, width, height, scale, config);
743
+ const ptr = passArray8ToWasm(data);
744
+ const resultPtr = wasm.crt_upscale_raw(
745
+ ptr,
746
+ WASM_VECTOR_LEN,
747
+ width,
748
+ height,
749
+ scale,
750
+ options.warpX ?? 0.015,
751
+ options.warpY ?? 0.02,
752
+ options.scanHardness ?? -4,
753
+ options.scanOpacity ?? 0.5,
754
+ options.maskOpacity ?? 0.3,
755
+ options.enableWarp !== false ? 1 : 0,
756
+ options.enableScanlines !== false ? 1 : 0,
757
+ options.enableMask !== false ? 1 : 0
758
+ );
759
+ const outWidth = width * scale;
760
+ const outHeight = height * scale;
761
+ const outLen = outWidth * outHeight * 4;
710
762
  return {
711
- data: getOutputData(result),
712
- width: result.width,
713
- height: result.height
763
+ data: getOutputData(resultPtr, outLen),
764
+ width: outWidth,
765
+ height: outHeight
714
766
  };
715
767
  }
716
- /** Dispose resources */
717
768
  dispose() {
718
769
  this.ready = false;
719
770
  }
@@ -722,50 +773,68 @@ var HexCpuRenderer = class _HexCpuRenderer {
722
773
  constructor() {
723
774
  this.ready = false;
724
775
  }
725
- /** Create renderer (WASM must be initialized first via initWasm) */
726
- static create() {
727
- if (!isWasmLoaded()) {
728
- throw new Error("WASM not initialized. Call initWasm() first.");
729
- }
776
+ static async create() {
777
+ await ensureWasm();
730
778
  const renderer = new _HexCpuRenderer();
731
779
  renderer.ready = true;
732
780
  return renderer;
733
781
  }
734
- /** Check if renderer is ready */
735
782
  isReady() {
736
783
  return this.ready && isWasmLoaded();
737
784
  }
738
- /** Render hexagonal effect */
739
785
  render(input, options = {}) {
740
- if (!this.ready || !wasmModule) {
786
+ if (!this.ready || !wasm) {
741
787
  throw new Error("Renderer not initialized");
742
788
  }
743
789
  const data = toUint8Array(input);
744
790
  const width = input.width;
745
791
  const height = input.height;
746
792
  const scale = Math.min(32, Math.max(2, options.scale ?? 16));
747
- const config = new wasmModule.HexConfig();
748
- config.orientation = options.orientation === "pointy-top" ? 1 : 0;
749
- config.draw_borders = options.drawBorders ?? false;
750
- config.border_color = parseColorToU32(options.borderColor, 673720575);
751
- config.border_thickness = options.borderThickness ?? 1;
752
- config.background_color = parseColorToU32(options.backgroundColor, 0);
753
- const result = wasmModule.hex_upscale_config(data, width, height, scale, config);
793
+ const orientation = options.orientation === "pointy-top" ? 1 : 0;
794
+ const ptr = passArray8ToWasm(data);
795
+ const resultPtr = wasm.hex_upscale_raw(
796
+ ptr,
797
+ WASM_VECTOR_LEN,
798
+ width,
799
+ height,
800
+ scale,
801
+ orientation,
802
+ options.drawBorders ? 1 : 0,
803
+ parseColorToU32(options.borderColor, 673720575),
804
+ options.borderThickness ?? 1,
805
+ parseColorToU32(options.backgroundColor, 0)
806
+ );
807
+ const dims = this.getDimensions(width, height, scale, options.orientation);
808
+ const outLen = dims.width * dims.height * 4;
754
809
  return {
755
- data: getOutputData(result),
756
- width: result.width,
757
- height: result.height
810
+ data: getOutputData(resultPtr, outLen),
811
+ width: dims.width,
812
+ height: dims.height
758
813
  };
759
814
  }
760
- /** Get output dimensions */
761
815
  getDimensions(srcWidth, srcHeight, scale, orientation = "flat-top") {
762
- if (!this.ready || !wasmModule) {
763
- throw new Error("Renderer not initialized");
816
+ const SQRT3 = 1.732050808;
817
+ scale = Math.min(32, Math.max(2, scale));
818
+ if (orientation === "flat-top") {
819
+ const hSpacing = scale * 1.5;
820
+ const vSpacing = scale * SQRT3;
821
+ const cellWidth = scale * 2;
822
+ const cellHeight = scale * SQRT3;
823
+ return {
824
+ width: Math.ceil(srcWidth * hSpacing + cellWidth),
825
+ height: Math.ceil(srcHeight * vSpacing + cellHeight)
826
+ };
827
+ } else {
828
+ const hSpacing = scale * SQRT3;
829
+ const vSpacing = scale * 1.5;
830
+ const cellWidth = scale * SQRT3;
831
+ const cellHeight = scale * 2;
832
+ return {
833
+ width: Math.ceil(srcWidth * hSpacing + cellWidth),
834
+ height: Math.ceil(srcHeight * vSpacing + cellHeight)
835
+ };
764
836
  }
765
- const dims = wasmModule.hex_get_dimensions(srcWidth, srcHeight, scale, orientation === "pointy-top" ? 1 : 0);
766
- return { width: dims[0], height: dims[1] };
767
837
  }
768
- /** Dispose resources */
769
838
  dispose() {
770
839
  this.ready = false;
771
840
  }
@@ -774,41 +843,44 @@ var XbrzCpuRenderer = class _XbrzCpuRenderer {
774
843
  constructor() {
775
844
  this.ready = false;
776
845
  }
777
- /** Create renderer (WASM must be initialized first via initWasm) */
778
- static create() {
779
- if (!isWasmLoaded()) {
780
- throw new Error("WASM not initialized. Call initWasm() first.");
781
- }
846
+ static async create() {
847
+ await ensureWasm();
782
848
  const renderer = new _XbrzCpuRenderer();
783
849
  renderer.ready = true;
784
850
  return renderer;
785
851
  }
786
- /** Check if renderer is ready */
787
852
  isReady() {
788
853
  return this.ready && isWasmLoaded();
789
854
  }
790
- /** Render xBRZ scaling */
791
855
  render(input, options = {}) {
792
- if (!this.ready || !wasmModule) {
856
+ if (!this.ready || !wasm) {
793
857
  throw new Error("Renderer not initialized");
794
858
  }
795
859
  const data = toUint8Array(input);
796
860
  const width = input.width;
797
861
  const height = input.height;
798
862
  const scale = Math.min(6, Math.max(2, options.scale ?? 2));
799
- const config = new wasmModule.XbrzConfig();
800
- config.luminance_weight = options.luminanceWeight ?? 1;
801
- config.equal_color_tolerance = options.equalColorTolerance ?? 30;
802
- config.dominant_direction_threshold = options.dominantDirectionThreshold ?? 4.4;
803
- config.steep_direction_threshold = options.steepDirectionThreshold ?? 2.2;
804
- const result = wasmModule.xbrz_upscale_config(data, width, height, scale, config);
863
+ const ptr = passArray8ToWasm(data);
864
+ const resultPtr = wasm.xbrz_upscale_raw(
865
+ ptr,
866
+ WASM_VECTOR_LEN,
867
+ width,
868
+ height,
869
+ scale,
870
+ options.luminanceWeight ?? 1,
871
+ options.equalColorTolerance ?? 30,
872
+ options.dominantDirectionThreshold ?? 4.4,
873
+ options.steepDirectionThreshold ?? 2.2
874
+ );
875
+ const outWidth = width * scale;
876
+ const outHeight = height * scale;
877
+ const outLen = outWidth * outHeight * 4;
805
878
  return {
806
- data: getOutputData(result),
807
- width: result.width,
808
- height: result.height
879
+ data: getOutputData(resultPtr, outLen),
880
+ width: outWidth,
881
+ height: outHeight
809
882
  };
810
883
  }
811
- /** Dispose resources */
812
884
  dispose() {
813
885
  this.ready = false;
814
886
  }
@@ -850,13 +922,11 @@ function getMaxTextureSize() {
850
922
  }
851
923
  }
852
924
  var CrtEngine = class {
853
- constructor(gpuAvailable, cpuAvailable) {
925
+ constructor(gpuAvailable) {
854
926
  this.gpuRenderer = null;
855
927
  this.cpuRenderer = null;
856
928
  this.gpuAvailable = gpuAvailable;
857
- this.cpuAvailable = cpuAvailable;
858
929
  }
859
- /** Initialize GPU renderer */
860
930
  ensureGpu() {
861
931
  if (!this.gpuRenderer && this.gpuAvailable) {
862
932
  this.gpuRenderer = CrtGpuRenderer.create();
@@ -864,15 +934,13 @@ var CrtEngine = class {
864
934
  if (!this.gpuRenderer) throw new Error("GPU renderer not available");
865
935
  return this.gpuRenderer;
866
936
  }
867
- /** Initialize CPU renderer */
868
- ensureCpu() {
869
- if (!this.cpuRenderer && this.cpuAvailable && isWasmLoaded()) {
870
- this.cpuRenderer = CrtCpuRenderer.create();
937
+ async ensureCpu() {
938
+ if (!this.cpuRenderer) {
939
+ this.cpuRenderer = await CrtCpuRenderer.create();
871
940
  }
872
- if (!this.cpuRenderer) throw new Error("CPU renderer not available. Initialize WASM first.");
873
941
  return this.cpuRenderer;
874
942
  }
875
- render(input, optionsOrPreset, overrides) {
943
+ async render(input, optionsOrPreset, overrides) {
876
944
  let options = {};
877
945
  if (typeof optionsOrPreset === "string") {
878
946
  options = { ...CRT_PRESETS[optionsOrPreset], ...overrides };
@@ -887,13 +955,13 @@ var CrtEngine = class {
887
955
  if (backend === "gpu") throw e;
888
956
  }
889
957
  }
890
- return this.ensureCpu().render(input, options);
958
+ const cpu = await this.ensureCpu();
959
+ return cpu.render(input, options);
891
960
  }
892
961
  /** Render synchronously (GPU only) */
893
962
  renderSync(input, options) {
894
963
  return this.ensureGpu().render(input, options ?? {});
895
964
  }
896
- /** Dispose resources */
897
965
  dispose() {
898
966
  this.gpuRenderer?.dispose();
899
967
  this.cpuRenderer?.dispose();
@@ -902,11 +970,10 @@ var CrtEngine = class {
902
970
  }
903
971
  };
904
972
  var HexEngine = class {
905
- constructor(gpuAvailable, cpuAvailable) {
973
+ constructor(gpuAvailable) {
906
974
  this.gpuRenderer = null;
907
975
  this.cpuRenderer = null;
908
976
  this.gpuAvailable = gpuAvailable;
909
- this.cpuAvailable = cpuAvailable;
910
977
  }
911
978
  ensureGpu() {
912
979
  if (!this.gpuRenderer && this.gpuAvailable) {
@@ -915,14 +982,13 @@ var HexEngine = class {
915
982
  if (!this.gpuRenderer) throw new Error("GPU renderer not available");
916
983
  return this.gpuRenderer;
917
984
  }
918
- ensureCpu() {
919
- if (!this.cpuRenderer && this.cpuAvailable && isWasmLoaded()) {
920
- this.cpuRenderer = HexCpuRenderer.create();
985
+ async ensureCpu() {
986
+ if (!this.cpuRenderer) {
987
+ this.cpuRenderer = await HexCpuRenderer.create();
921
988
  }
922
- if (!this.cpuRenderer) throw new Error("CPU renderer not available. Initialize WASM first.");
923
989
  return this.cpuRenderer;
924
990
  }
925
- render(input, optionsOrPreset, overrides) {
991
+ async render(input, optionsOrPreset, overrides) {
926
992
  let options = {};
927
993
  if (typeof optionsOrPreset === "string") {
928
994
  options = { ...HEX_PRESETS[optionsOrPreset], ...overrides };
@@ -937,13 +1003,12 @@ var HexEngine = class {
937
1003
  if (backend === "gpu") throw e;
938
1004
  }
939
1005
  }
940
- return this.ensureCpu().render(input, options);
1006
+ const cpu = await this.ensureCpu();
1007
+ return cpu.render(input, options);
941
1008
  }
942
- /** Render synchronously (GPU only) */
943
1009
  renderSync(input, options) {
944
1010
  return this.ensureGpu().render(input, options ?? {});
945
1011
  }
946
- /** Get output dimensions */
947
1012
  getDimensions(srcWidth, srcHeight, options) {
948
1013
  const scale = options?.scale ?? 16;
949
1014
  const orientation = options?.orientation ?? "flat-top";
@@ -957,27 +1022,25 @@ var HexEngine = class {
957
1022
  }
958
1023
  };
959
1024
  var XbrzEngine = class {
960
- constructor(cpuAvailable) {
1025
+ constructor() {
961
1026
  this.cpuRenderer = null;
962
- this.cpuAvailable = cpuAvailable;
963
1027
  }
964
- ensureCpu() {
965
- if (!this.cpuRenderer && this.cpuAvailable && isWasmLoaded()) {
966
- this.cpuRenderer = XbrzCpuRenderer.create();
1028
+ async ensureCpu() {
1029
+ if (!this.cpuRenderer) {
1030
+ this.cpuRenderer = await XbrzCpuRenderer.create();
967
1031
  }
968
- if (!this.cpuRenderer) throw new Error("CPU renderer not available. Initialize WASM first.");
969
1032
  return this.cpuRenderer;
970
1033
  }
971
- render(input, optionsOrPreset, overrides) {
1034
+ async render(input, optionsOrPreset, overrides) {
972
1035
  let options = {};
973
1036
  if (typeof optionsOrPreset === "string") {
974
1037
  options = { ...XBRZ_PRESETS[optionsOrPreset], ...overrides };
975
1038
  } else if (optionsOrPreset) {
976
1039
  options = optionsOrPreset;
977
1040
  }
978
- return this.ensureCpu().render(input, options);
1041
+ const cpu = await this.ensureCpu();
1042
+ return cpu.render(input, options);
979
1043
  }
980
- /** Get output dimensions */
981
1044
  getDimensions(srcWidth, srcHeight, scale = 2) {
982
1045
  const s = Math.min(6, Math.max(2, scale));
983
1046
  return { width: srcWidth * s, height: srcHeight * s };
@@ -988,47 +1051,38 @@ var XbrzEngine = class {
988
1051
  }
989
1052
  };
990
1053
  var RenderArt = class _RenderArt {
991
- constructor(gpuAvailable, cpuAvailable) {
992
- this.crt = new CrtEngine(gpuAvailable, cpuAvailable);
993
- this.hex = new HexEngine(gpuAvailable, cpuAvailable);
994
- this.xbrz = new XbrzEngine(cpuAvailable);
1054
+ constructor(gpuAvailable) {
1055
+ this.crt = new CrtEngine(gpuAvailable);
1056
+ this.hex = new HexEngine(gpuAvailable);
1057
+ this.xbrz = new XbrzEngine();
995
1058
  const maxTextureSize = gpuAvailable ? getMaxTextureSize() : 0;
996
1059
  this._capabilities = {
997
1060
  gpu: gpuAvailable,
998
- cpu: cpuAvailable,
1061
+ cpu: true,
1062
+ // Always potentially available via async init
999
1063
  maxTextureSize,
1000
- recommendedBackend: gpuAvailable ? "gpu" : cpuAvailable ? "cpu" : "auto"
1064
+ recommendedBackend: gpuAvailable ? "gpu" : "cpu"
1001
1065
  };
1002
1066
  }
1003
- /**
1004
- * Create RenderArt instance
1005
- * GPU renderers always available if WebGL2 supported.
1006
- * CPU renderers require WASM to be initialized via initWasm() first.
1007
- */
1067
+ /** Create RenderArt instance */
1008
1068
  static create() {
1009
1069
  const gpuAvailable = checkWebGL2();
1010
- const cpuAvailable = isWasmLoaded();
1011
- if (!gpuAvailable && !cpuAvailable) {
1012
- throw new Error("No rendering backend available. WebGL2 not supported and WASM not initialized.");
1013
- }
1014
- return new _RenderArt(gpuAvailable, cpuAvailable);
1070
+ return new _RenderArt(gpuAvailable);
1015
1071
  }
1016
- /**
1017
- * Create GPU-only RenderArt instance (no WASM required)
1018
- * CPU fallback will not be available.
1019
- */
1072
+ /** Create GPU-only instance */
1020
1073
  static createGpuOnly() {
1021
- const gpuAvailable = checkWebGL2();
1022
- if (!gpuAvailable) {
1074
+ if (!checkWebGL2()) {
1023
1075
  throw new Error("WebGL2 not supported");
1024
1076
  }
1025
- return new _RenderArt(true, false);
1077
+ return new _RenderArt(true);
1078
+ }
1079
+ /** Pre-initialize WASM for faster first CPU render */
1080
+ async preloadWasm(wasmUrl2) {
1081
+ await initWasm(wasmUrl2);
1026
1082
  }
1027
- /** Get renderer capabilities */
1028
1083
  get capabilities() {
1029
1084
  return { ...this._capabilities };
1030
1085
  }
1031
- /** Dispose all resources */
1032
1086
  dispose() {
1033
1087
  this.crt.dispose();
1034
1088
  this.hex.dispose();
@@ -1036,6 +1090,6 @@ var RenderArt = class _RenderArt {
1036
1090
  }
1037
1091
  };
1038
1092
 
1039
- export { CRT_PRESETS, CrtCpuRenderer, CrtEngine, CrtGpuRenderer, HEX_PRESETS, HexCpuRenderer, HexEngine, HexGpuRenderer, RenderArt, XBRZ_PRESETS, XbrzCpuRenderer, XbrzEngine, getWasmModule, hexGetDimensions, initWasm, isWasmLoaded };
1093
+ export { CRT_PRESETS, CrtCpuRenderer, CrtEngine, CrtGpuRenderer, HEX_PRESETS, HexCpuRenderer, HexEngine, HexGpuRenderer, RenderArt, XBRZ_PRESETS, XbrzCpuRenderer, XbrzEngine, hexGetDimensions, initWasm, isWasmLoaded, setWasmUrl };
1040
1094
  //# sourceMappingURL=index.mjs.map
1041
1095
  //# sourceMappingURL=index.mjs.map