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