├── .gitignore ├── README.md ├── app ├── Makefile ├── README.md └── src │ ├── Color.cpp │ ├── Color.h │ └── Main.cpp ├── favicon.ico ├── fonts ├── munro-narrow.ttf ├── munro-small.ttf └── munro.ttf ├── index.html ├── media ├── artifact_ss.png ├── cover.png ├── edg32.gpl ├── header-icon.png ├── icon-large.png ├── icon.png └── palette.png ├── scripts ├── color.js ├── file-helper.js ├── image.js ├── palette.js ├── pixel-swap.js └── pixel-swap.wasm └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | app/.vs 2 | app/build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pixel Swap 2 | 3 | Select an image. Select a color palette. Convert colors like magic. Try it here: [pixelswap.art](https://pixelswap.art). 4 | 5 |

6 | 7 |

8 | 9 | ## Instructions 10 | 11 | Pixel Swap is a small web application that swaps the colors of an image with the closest color from the selected palette. Here's how to use it: 12 | 13 | 1. Select the color-matching method. 14 | 15 | - Euclidean distance is the fastest. CIELAB is closest to how the human eye perceives differences in color in real life. 16 | - None of the methods take into account aesthetics or style, so often the "right" method for your conversion is a matter of personal choice. 17 | 18 | 2. Select the image. 19 | 20 | - Must be a .PNG file. 21 | - Alpha values won't be affected. 22 | - Large images will take longer to convert; performance is machine-dependent because the application runs client-side. 23 | - I would recommend images under 2000x2000px, but you can exceed this limit. 24 | 25 | 3. Select the color palette. 26 | 27 | - Must be a .GPL file ([GIMP Color Palette](https://docs.gimp.org/2.10/en/gimp-concepts-palettes.html)). 28 | - You can find a collection of color palettes here: https://lospec.com/palette-list. Download as GIMP .GPL. 29 | - There is minimal validation for color palettes. Please ensure the file you give the application is well-formatted. 30 | 31 | 4. Download the image by clicking the converted picture or the "Download Result" button. 32 | 33 | ## How 34 | 35 | Pixel Swap converts an image's colors by comparing each pixel to every color in the selected palette. The color-by-color difference is calculated using the same method for every pixel in the image, but you can change the method to automatically recalculate the result. There are three possible methods: 36 | 37 | 1. Euclidean Distance 38 | 39 | - The difference between two colors is calculated by representing them as two points in 3D space, then calculating the distance between them [[1](#ref1)]. 40 | - For both colors, let X = R, Y = G, and Z = B. Then, calculate the euclidean distance between the two resulting points. Shorter distances indicate smaller differences in color. 41 | 42 | 2. Weighted RGB 43 | 44 | - Identical to Euclidean Distance, except the individual difference in color is weighted differently for red, green, and blue. 45 | - I used 0.3 for red, 0.59 for green, and 0.11 for blue [[1](#ref1)]. 46 | 47 | 3. CIELAB 48 | 49 | - The difference between two colors is equal to their difference in the CIE-L\*ab color space. 50 | - If the colors are represented in RGB, they must first be converted into the XYZ color format, and then into CIE-L\*ab [[2](#ref2)]. 51 | - CIE-L\*ab color conversion depends on several constant reference values; my conversion is based on the D50 illuminant with a 2° (CIE 1931) observer because I read that they are commonly used. 52 | 53 | Pixel Swap was originally written in JavaScript, then translated into C++ and compiled into WebAssembly to improve performance. To view the source code, visit the [app](./app/) folder. 54 | 55 | ### References and Acknowledgements 56 | 57 | 1. Baeldung. "How to Compute the Similarity of Colours," https://www.baeldung.com/cs/compute-similarity-of-colours. 58 | 2. EasyRGB. "Color math and programming code examples," http://www.easyrgb.com/en/math.php. 59 | 60 | This application is hosted at [pixelswap.art](https://pixelswap.art) for free via [Netlify](https://www.netlify.com/). 61 | -------------------------------------------------------------------------------- /app/Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR := src 2 | BUILD_DIR := build 3 | 4 | OUT := $(BUILD_DIR)/pixel-swap.js 5 | SRC := $(SRC_DIR)/Main.cpp $(SRC_DIR)/Color.cpp 6 | 7 | OPTFLAGS := -O3 -sALLOW_MEMORY_GROWTH=1 8 | EXPFLAGS := -sEXPORTED_RUNTIME_METHODS=ccall -sEXPORTED_FUNCTIONS=_main,_SwapColors,_free 9 | 10 | .PHONY: all clean 11 | 12 | all: $(OUT) 13 | 14 | $(OUT): $(BUILD_DIR) 15 | emcc $(SRC) -o $(OUT) $(OPTFLAGS) $(EXPFLAGS) 16 | 17 | $(BUILD_DIR): 18 | mkdir -p $@ 19 | 20 | clean: 21 | @$(RM) -rv $(BUILD_DIR) 22 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | ## Why WebAssembly? 2 | 3 | Performance. 4 | 5 | ## How? 6 | 7 | By reading a lot of documentation, then replacing the core loop of my program with an optimized loop written in C++. 8 | 9 | ``` 10 | /* BEGIN WEBASSEMBLY CODE */ 11 | 12 | let pixelBuffer = Module._malloc( 13 | pixels.length * Uint8Array.BYTES_PER_ELEMENT 14 | ); 15 | Module.HEAPU8.set(pixels, pixelBuffer); 16 | Module.ccall( 17 | "SwapColors", 18 | null, 19 | ["string", "array", "number", "number", "number"], 20 | [ColorMatchingMethod, palette, palette.length, pixelBuffer, pixels.length] 21 | ); 22 | 23 | let convertedImage = new Uint8ClampedArray( 24 | Module.HEAPU8.buffer, 25 | pixelBuffer, 26 | pixels.length 27 | ); 28 | 29 | Module._free(pixelBuffer); 30 | 31 | /* END WEBASSEMBLY CODE */ 32 | ``` 33 | 34 | The preceding code snippet is a great example of how to call an exported C++ method with a string, static array, integer, and dynamic array as parameters. 35 | 36 | ## Gotcha's 37 | 38 | When trying to hack this together, I encountered a couple of areas of confusion: scope, synchronization, and dynamic memory. 39 | 40 | - Scope 41 | - When I say scope, I'm referring to where my exported functions live and where I can call them. 42 | - At a minimum, you need a .wasm file to run your compiled code in the browser. 43 | - The easiest way to access and use this .wasm file is by importing the JavaScript glue file that's generated when compiling with [emscripten](https://emscripten.org/). 44 | - The glue file will give you access to the "Module" object, which you can use to call functions exported from your C/C++ code such as malloc, free, and, in my case, the SwapColors method. 45 | - Synchronization 46 | 47 | - This is a small detail that I spent a lot longer on than I should have. 48 | - Basically, I was trying to initialize the page by making a call to an exported method, but was getting undefined reference errors: `Uncaught TypeError: Cannot read properties of undefined (reading 'malloc') webassembly`. 49 | - This error was occurring because the WebAssembly module hadn't fully loaded in, which confused me because it's the first script I import in "index.html." 50 | - To fix the error, I simply wait until the Module has fully loaded before calling any exported methods (including malloc): `Module.onRuntimeInitialized = () => initializePreviewImages();`. 51 | 52 | - Dynamic Memory 53 | 54 | - Code examples helped me out tremendously with this one. 55 | - To call methods that require dynamically allocated memory, you must use the HEAP found on the WebAssembly Module. 56 | - You can write to the HEAP using the "set" method and read from it by accessing the buffer (i.e. Module.HEAP.buffer) [[1](#ref1)]. 57 | 58 | ## A Note On Compilation 59 | 60 | emscripten offers [many compiler settings](https://github.com/emscripten-core/emscripten/blob/main/src/settings.js). I would recommend sticking to what's necessary while making sure to include the optimization flag (use -O3 for the best performance). My final compiler command came out to: 61 | 62 | ``` 63 | emcc src/Main.cpp src/Color.cpp -o build/pixel-swap.js -O3 -sALLOW_MEMORY_GROWTH=1 -sEXPORTED_RUNTIME_METHODS=ccall -sEXPORTED_FUNCTIONS=_main,_SwapColors,_free 64 | ``` 65 | 66 | - At first, I was using `-sLINKABLE=1` and `-sEXPORT_ALL=1` to resolve undefined symbol errors, but this resulted in a bloated output file and Module object. 67 | - `-sALLOW_MEMORY_GROWTH=1` was necessary to store larger images (exceeding a few megabytes) on the heap. 68 | - I didn't include "cwrap" in `EXPORTED_RUNTIME_METHODS` because I didn't use it, but in other examples, you will see them include both. 69 | - The .js on the end of `-o build/pixel-swap.js` tells emscripten to build only the .wasm and .js glue file. You can also build a .html file, but I didn't because I wasn't going to use it. 70 | - `-O3` is the highest level of optimization you can specify for the compiler and I would recommend including this in your production code otherwise you aren't fully taking advantage of the power of WebAssembly. 71 | 72 | ### References 73 | 74 | 1. Marco Selvatici. "Webassembly Tutorial - 10. Memory," https://marcoselvatici.github.io/WASM_tutorial/. 75 | 2. emscripten. "Interacting with code," https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html. 76 | 3. WebAssembly. "Developer's Guide," https://webassembly.org/getting-started/developers-guide/. 77 | 4. MDN Web Docs. "Compiling an Existing C Module to WebAssembly," https://developer.mozilla.org/en-US/docs/WebAssembly/existing_C_to_wasm. 78 | -------------------------------------------------------------------------------- /app/src/Color.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Color.h" 3 | 4 | MatchingMethod Color::matchingMethod = MatchingMethod::Euclidean; 5 | 6 | float Color::EuclideanDistance(Color& color) 7 | { 8 | return sqrtf( 9 | powf(channels[0] - color.channels[0], 2) + 10 | powf(channels[1] - color.channels[1], 2) + 11 | powf(channels[2] - color.channels[2], 2) 12 | ); 13 | } 14 | 15 | float Color::WeightedRGB(Color& color) 16 | { 17 | return sqrtf( 18 | 0.3f * powf(channels[0] - color.channels[0], 2) + 19 | 0.59f * powf(channels[1] - color.channels[1], 2) + 20 | 0.11f * powf(channels[2] - color.channels[2], 2) 21 | ); 22 | } 23 | 24 | float Color::Cielab(Color& color) 25 | { 26 | CIE myCIE = ToCIE(); 27 | CIE otherCIE = color.ToCIE(); 28 | return sqrtf( 29 | powf(myCIE.L - otherCIE.L, 2) + 30 | powf(myCIE.a - otherCIE.a, 2) + 31 | powf(myCIE.b - otherCIE.b, 2) 32 | ); 33 | } 34 | 35 | uint8_t Color::operator [](int i) const 36 | { 37 | return channels[i]; 38 | } 39 | 40 | Color::Color(uint8_t red, uint8_t green, uint8_t blue) 41 | { 42 | channels[0] = red; 43 | channels[1] = green; 44 | channels[2] = blue; 45 | 46 | // Uninitialized values of CIE and XYZ are expressed as all -1's 47 | xyz = { -1, -1, -1 }; 48 | cie = { -1, -1, -1 }; 49 | } 50 | 51 | XYZ& Color::ToXYZ() 52 | { 53 | // XYZ values should be all non-negative, otherwise the struct is uninitialized 54 | if (xyz.x < 0 || xyz.y < 0 || xyz.z < 0) 55 | { 56 | float r = channels[0] / 255.0f; 57 | float g = channels[1] / 255.0f; 58 | float b = channels[2] / 255.0f; 59 | 60 | if (r > 0.04045f) r = powf(((r + 0.055f) / 1.055f), 2.4f); 61 | else r = r / 12.92f; 62 | if (g > 0.04045f) g = powf(((g + 0.055f) / 1.055f), 2.4f); 63 | else g = g / 12.92f; 64 | if (b > 0.04045f) b = powf(((b + 0.055f) / 1.055f), 2.4f); 65 | else b = b / 12.92f; 66 | 67 | r = r * 100; 68 | g = g * 100; 69 | b = b * 100; 70 | 71 | xyz.x = r * 0.4124f + g * 0.3576f + b * 0.1805f; 72 | xyz.y = r * 0.2126f + g * 0.7152f + b * 0.0722f; 73 | xyz.z = r * 0.0193f + g * 0.1192f + b * 0.9505f; 74 | 75 | return xyz; 76 | } 77 | return xyz; 78 | } 79 | 80 | CIE& Color::ToCIE() 81 | { 82 | // L value should be non-negative, otherwise the struct is uninitialized 83 | if (cie.L < 0) 84 | { 85 | cie = XYZToCIE(ToXYZ()); 86 | } 87 | return cie; 88 | } 89 | 90 | float Color::Difference(Color& color) 91 | { 92 | if (matchingMethod == MatchingMethod::Euclidean) 93 | { 94 | return EuclideanDistance(color); 95 | } 96 | else if (matchingMethod == MatchingMethod::Weighted) 97 | { 98 | return WeightedRGB(color); 99 | } 100 | else if (matchingMethod == MatchingMethod::CIELAB) 101 | { 102 | return Cielab(color); 103 | } 104 | return -1; 105 | } 106 | 107 | std::string Color::ToString() 108 | { 109 | return "rgb(" + std::to_string(channels[0]) + ", " 110 | + std::to_string(channels[1]) + ", " 111 | + std::to_string(channels[2]) + ")"; 112 | } 113 | 114 | CIE Color::XYZToCIE(XYZ& xyz) 115 | { 116 | // Based on Observer: 2� (CIE 1931), Illuminant: D50 117 | float x = xyz.x / 96.422f; 118 | float y = xyz.y / 100.0f; 119 | float z = xyz.z / 82.521f; 120 | 121 | if (x > 0.008856f) x = powf(x, (1 / 3.0f)); 122 | else x = 7.787f * x + 16 / 116.0f; 123 | if (y > 0.008856f) y = powf(y, (1 / 3.0f)); 124 | else y = 7.787f * y + 16 / 116.0f; 125 | if (z > 0.008856f) z = powf(z, (1 / 3.0f)); 126 | else z = 7.787f * z + 16 / 116.0f; 127 | 128 | float L = 116 * y - 16; 129 | float a = 500 * (x - y); 130 | float b = 200 * (y - z); 131 | 132 | return { L, a, b }; 133 | } 134 | 135 | void Color::SetMatchingMethod(const char* method) 136 | { 137 | std::string euclidean("Euclidean"); 138 | std::string weighted("Weighted"); 139 | std::string cielab("CIELAB"); 140 | 141 | if (euclidean.compare(method) == 0) 142 | { 143 | matchingMethod = MatchingMethod::Euclidean; 144 | } 145 | else if (weighted.compare(method) == 0) 146 | { 147 | matchingMethod = MatchingMethod::Weighted; 148 | } 149 | else if (cielab.compare(method) == 0) 150 | { 151 | matchingMethod = MatchingMethod::CIELAB; 152 | } 153 | } -------------------------------------------------------------------------------- /app/src/Color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct XYZ 5 | { 6 | float x; 7 | float y; 8 | float z; 9 | }; 10 | 11 | struct CIE 12 | { 13 | float L; 14 | float a; 15 | float b; 16 | }; 17 | 18 | enum class MatchingMethod 19 | { 20 | Euclidean, 21 | Weighted, 22 | CIELAB 23 | }; 24 | 25 | class Color 26 | { 27 | private: 28 | uint8_t channels[3]; 29 | 30 | XYZ xyz; 31 | CIE cie; 32 | 33 | float EuclideanDistance(Color& color); 34 | float WeightedRGB(Color& color); 35 | float Cielab(Color& color); 36 | 37 | static MatchingMethod matchingMethod; 38 | public: 39 | uint8_t operator [](int i) const; 40 | 41 | Color(uint8_t red, uint8_t green, uint8_t blue); 42 | 43 | XYZ& ToXYZ(); 44 | CIE& ToCIE(); 45 | 46 | float Difference(Color& color); 47 | 48 | std::string ToString(); 49 | 50 | static CIE XYZToCIE(XYZ& xyz); 51 | static void SetMatchingMethod(const char* method); 52 | }; 53 | -------------------------------------------------------------------------------- /app/src/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Color.h" 5 | 6 | #define EXTERN extern "C" 7 | 8 | int main(void) 9 | { 10 | std::cout << "WASM has loaded." << std::endl; 11 | return 0; 12 | } 13 | 14 | void InitializePalette(std::vector& colors, uint8_t* palette, int paletteLength) 15 | { 16 | for (int i = 0; i < paletteLength; i += 3) 17 | { 18 | colors.push_back(Color(palette[i], palette[i + 1], palette[i + 2])); 19 | } 20 | } 21 | 22 | Color& GetMostSimilarColor(std::vector& colors, Color& color) 23 | { 24 | float minDifference = std::numeric_limits::max(); 25 | int indexOfMostSimilar = 0; 26 | 27 | for (int i = 0; i < colors.size(); i++) 28 | { 29 | float difference = color.Difference(colors[i]); 30 | 31 | if (difference < minDifference) 32 | { 33 | indexOfMostSimilar = i; 34 | minDifference = difference; 35 | } 36 | } 37 | 38 | return colors[indexOfMostSimilar]; 39 | } 40 | 41 | EXTERN EMSCRIPTEN_KEEPALIVE 42 | void SwapColors(const char* matchingMethod, uint8_t* palette, int paletteLength, uint8_t* image, int imageLength) 43 | { 44 | Color::SetMatchingMethod(matchingMethod); 45 | std::vector colors; 46 | 47 | InitializePalette(colors, palette, paletteLength); 48 | 49 | // Loop through all of the pixels and modify the components 50 | for (int i = 0; i < imageLength; i += 4) 51 | { 52 | Color pixelColor(image[i], image[i + 1], image[i + 2]); 53 | Color newColor = GetMostSimilarColor(colors, pixelColor); 54 | 55 | image[i] = newColor[0]; 56 | image[i + 1] = newColor[1]; 57 | image[i + 2] = newColor[2]; 58 | // image[i+3] is the transparency. 59 | } 60 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/favicon.ico -------------------------------------------------------------------------------- /fonts/munro-narrow.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/fonts/munro-narrow.ttf -------------------------------------------------------------------------------- /fonts/munro-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/fonts/munro-small.ttf -------------------------------------------------------------------------------- /fonts/munro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/fonts/munro.ttf -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pixel Swap 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 |
23 | 24 |

Pixel Swap

25 |

Quickly swap pixel art color palettes

26 |
27 |
28 |
29 | 30 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |

42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 52 |
53 |
54 | 61 | 66 | 69 |
70 |
71 |
72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /media/artifact_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/artifact_ss.png -------------------------------------------------------------------------------- /media/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/cover.png -------------------------------------------------------------------------------- /media/edg32.gpl: -------------------------------------------------------------------------------- 1 | GIMP Palette 2 | # 3 | # By ENDESGA Studios 4 | # https://twitter.com/ENDESGA 5 | # 6 | 190 74 47 Tetanus 7 | 216 118 68 Rust 8 | 234 212 170 Birch 9 | 228 166 114 Sap 10 | 184 111 80 Oak 11 | 116 63 57 Pine 12 | 63 40 50 Darkbark 13 | 158 40 53 Blood 14 | 228 59 68 Fabric 15 | 247 118 34 Amber 16 | 254 174 52 Glow 17 | 254 231 97 Light 18 | 99 199 77 Glade 19 | 62 137 72 Flora 20 | 38 92 66 Moss 21 | 25 60 62 Mold 22 | 18 78 137 Deep 23 | 0 149 233 Archaeon 24 | 44 232 245 Ion 25 | 255 255 255 White 26 | 192 203 220 Aluminium 27 | 139 155 180 Zinc 28 | 90 105 136 Iron 29 | 58 68 102 Steel 30 | 38 43 68 Shade 31 | 255 0 68 Iiem 32 | 24 20 37 Ink 33 | 104 56 108 Lilac 34 | 181 80 136 Petal 35 | 246 117 122 Peach 36 | 232 183 150 Skin 37 | 194 133 105 Shadeskin 38 | -------------------------------------------------------------------------------- /media/header-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/header-icon.png -------------------------------------------------------------------------------- /media/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/icon-large.png -------------------------------------------------------------------------------- /media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/icon.png -------------------------------------------------------------------------------- /media/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/media/palette.png -------------------------------------------------------------------------------- /scripts/color.js: -------------------------------------------------------------------------------- 1 | const matchingMethodSelector = document.getElementById("matching-method"); 2 | matchingMethodSelector.onchange = (e) => { 3 | ColorMatchingMethod = e.target.value; 4 | }; 5 | 6 | const MatchingMethods = { 7 | CIELAB: "CIELAB", 8 | Weighted: "Weighted", 9 | Euclidean: "Euclidean", 10 | }; 11 | 12 | let ColorMatchingMethod = matchingMethodSelector.value; 13 | -------------------------------------------------------------------------------- /scripts/file-helper.js: -------------------------------------------------------------------------------- 1 | function getFileType(filename) { 2 | return filename.split(".").pop(); 3 | } 4 | 5 | function isValidPng(file) { 6 | return getFileType(file.name).toLocaleLowerCase() === "png"; 7 | } 8 | 9 | function isValidGpl(file) { 10 | return getFileType(file.name).toLocaleLowerCase() === "gpl"; 11 | } 12 | 13 | function parseGplFile(file) { 14 | let lines = file.split(/\r?\n/); 15 | let palette = []; 16 | 17 | if (lines[0] !== "GIMP Palette") { 18 | alert("Poorly formatted GPL file!"); 19 | return; 20 | } 21 | 22 | for (let i = 1; i < lines.length; i++) { 23 | let line = lines[i]; 24 | 25 | // Ignore comments and empty lines 26 | if (line.length === 0 || line[0] === "#") continue; 27 | let rgbValues = line.trim().split(/\s+/); 28 | 29 | palette.push(rgbValues[0]); 30 | palette.push(rgbValues[1]); 31 | palette.push(rgbValues[2]); 32 | } 33 | 34 | return new Uint8ClampedArray(palette); 35 | } 36 | -------------------------------------------------------------------------------- /scripts/image.js: -------------------------------------------------------------------------------- 1 | const beforeImage = document.getElementById("before-image"); 2 | const beforeImagePreview = document.getElementById("before-image-preview"); 3 | const beforeDiv = document.getElementById("before"); 4 | const afterImage = document.getElementById("after-image"); 5 | const afterImagePreview = document.getElementById("after-image-preview"); 6 | const afterDiv = document.getElementById("after"); 7 | const imageInput = document.getElementById("image-input"); 8 | const downloadButtons = document.getElementsByClassName("download-button"); 9 | const imageDownload = document.getElementById("image-download"); 10 | 11 | imageInput.addEventListener("change", readSingleFile); 12 | palettePreview.addEventListener("change", updatePreviewImages); 13 | matchingMethodSelector.addEventListener("change", updatePreviewImages); 14 | 15 | // Update the image preview when the palette changes 16 | observer = new MutationObserver(updatePreviewImages); 17 | observer.observe(palettePreview, { childList: true }); 18 | 19 | let image = ""; 20 | 21 | // Do not initialize until the WebAssembly module has been loaded or _malloc 22 | // will not be defined 23 | Module.onRuntimeInitialized = () => initializePreviewImages(); 24 | 25 | function readSingleFile(e) { 26 | //Retrieve the first (and only!) File from the FileList object 27 | let file = e.target.files[0]; 28 | 29 | if (file) { 30 | let reader = new FileReader(); 31 | reader.onload = (event) => { 32 | if (isValidPng(file)) { 33 | image = event.target.result; 34 | updatePreviewImages(); 35 | } else { 36 | e.target.value = null; 37 | alert("Not a valid PNG file!"); 38 | } 39 | }; 40 | reader.readAsDataURL(file); 41 | } 42 | } 43 | 44 | function initializePreviewImages() { 45 | beforeImage.crossOrigin = "Anonymous"; 46 | image = beforeImage.src; 47 | updatePreviewImages(); 48 | } 49 | 50 | function updatePreviewImages() { 51 | // Must wait until the image loads or you won't be able to load the image data 52 | beforeImage.onload = () => { 53 | updateBeforeImagePreview(beforeImage); 54 | updateAfterImagePreview(beforeImage); 55 | }; 56 | beforeImage.src = image; 57 | 58 | beforeDiv.hidden = !image; 59 | afterDiv.hidden = !image || palette.length === 0; 60 | Array.from(downloadButtons).forEach((button) => { 61 | button.hidden = afterDiv.hidden; 62 | }); 63 | } 64 | 65 | function updateAfterImagePreview(image) { 66 | const width = image.width || image.naturalWidth; 67 | const height = image.height || image.naturalHeight; 68 | afterImagePreview.width = width; 69 | afterImagePreview.height = height; 70 | let context = afterImagePreview.getContext("2d", { 71 | willReadFrequently: true, 72 | }); 73 | 74 | context.drawImage(image, 0, 0); 75 | 76 | let imageData = context.getImageData(0, 0, width, height), 77 | pixels = imageData.data; 78 | 79 | /* BEGIN WEBASSEMBLY CODE */ 80 | 81 | let pixelBuffer = Module._malloc( 82 | pixels.length * Uint8Array.BYTES_PER_ELEMENT 83 | ); 84 | Module.HEAPU8.set(pixels, pixelBuffer); 85 | Module.ccall( 86 | "SwapColors", 87 | null, 88 | ["string", "array", "number", "number", "number"], 89 | [ColorMatchingMethod, palette, palette.length, pixelBuffer, pixels.length] 90 | ); 91 | 92 | let convertedImage = new Uint8ClampedArray( 93 | Module.HEAPU8.buffer, 94 | pixelBuffer, 95 | pixels.length 96 | ); 97 | 98 | Module._free(pixelBuffer); 99 | 100 | /* END WEBASSEMBLY CODE */ 101 | 102 | context.putImageData(new ImageData(convertedImage, width, height), 0, 0); 103 | 104 | afterImage.src = afterImagePreview.toDataURL("image/png"); 105 | afterImagePreview.href = afterImage.src; 106 | Array.from(downloadButtons).forEach((button) => { 107 | button.href = afterImage.src; 108 | }); 109 | imageDownload.href = afterImage.src; 110 | } 111 | 112 | function updateBeforeImagePreview(image) { 113 | const width = image.width || image.naturalWidth; 114 | const height = image.height || image.naturalHeight; 115 | beforeImagePreview.width = width; 116 | beforeImagePreview.height = height; 117 | let context = beforeImagePreview.getContext("2d", { 118 | willReadFrequently: true, 119 | }); 120 | 121 | context.drawImage(image, 0, 0); 122 | 123 | let imageData = context.getImageData(0, 0, width, height); 124 | 125 | context.putImageData(imageData, 0, 0); 126 | } 127 | -------------------------------------------------------------------------------- /scripts/palette.js: -------------------------------------------------------------------------------- 1 | const palettePreview = document.getElementById("area"); 2 | const paletteInput = document.getElementById("palette-input"); 3 | paletteInput.addEventListener("change", readSingleFile); 4 | 5 | let paletteFileContents = `GIMP Palette 6 | # 7 | # By ENDESGA Studios 8 | # https://twitter.com/ENDESGA 9 | # 10 | 190 74 47 Tetanus 11 | 216 118 68 Rust 12 | 234 212 170 Birch 13 | 228 166 114 Sap 14 | 184 111 80 Oak 15 | 116 63 57 Pine 16 | 63 40 50 Darkbark 17 | 158 40 53 Blood 18 | 228 59 68 Fabric 19 | 247 118 34 Amber 20 | 254 174 52 Glow 21 | 254 231 97 Light 22 | 99 199 77 Glade 23 | 62 137 72 Flora 24 | 38 92 66 Moss 25 | 25 60 62 Mold 26 | 18 78 137 Deep 27 | 0 149 233 Archaeon 28 | 44 232 245 Ion 29 | 255 255 255 White 30 | 192 203 220 Aluminium 31 | 139 155 180 Zinc 32 | 90 105 136 Iron 33 | 58 68 102 Steel 34 | 38 43 68 Shade 35 | 255 0 68 Iiem 36 | 24 20 37 Ink 37 | 104 56 108 Lilac 38 | 181 80 136 Petal 39 | 246 117 122 Peach 40 | 232 183 150 Skin 41 | 194 133 105 Shadeskin 42 | `; 43 | let palette = []; 44 | 45 | initializePalette(); 46 | 47 | function readSingleFile(e) { 48 | //Retrieve the first (and only!) File from the FileList object 49 | let file = e.target.files[0]; 50 | 51 | if (file) { 52 | let reader = new FileReader(); 53 | reader.onload = (event) => { 54 | if (isValidGpl(file)) { 55 | paletteFileContents = event.target.result; 56 | palette = parseGplFile(paletteFileContents); 57 | updatePreviewText(); 58 | } else { 59 | e.target.value = null; 60 | alert("Not a valid GPL file!"); 61 | } 62 | }; 63 | reader.readAsText(file); 64 | } 65 | } 66 | 67 | function initializePalette() { 68 | palette = parseGplFile(paletteFileContents); 69 | updatePreviewText(); 70 | } 71 | 72 | function updatePreviewText() { 73 | let lines = paletteFileContents.split(/\r?\n/); 74 | let i; 75 | if (lines.length === 0) return; 76 | palettePreview.innerText = ""; 77 | // Do not add background color to title or comments 78 | for (i = 0; lines[i].charAt(0) === "#" || i === 0; i++) { 79 | palettePreview.innerText += lines[i] + "\n"; 80 | } 81 | // Parse palette colors separately since they need background color 82 | for (let j = 0; j < palette.length; i++, j += 3) { 83 | let r = palette[j]; 84 | let g = palette[j + 1]; 85 | let b = palette[j + 2]; 86 | let fontClass = isLight(r, g, b) ? "dark-font" : "light-font"; 87 | let backgroundColor = cssColor(r, g, b); 88 | palettePreview.innerHTML += `${lines[i]}`; 89 | } 90 | } 91 | 92 | function cssColor(red, green, blue) { 93 | return `rgb(${red}, ${green}, ${blue})`; 94 | } 95 | 96 | function isLight(red, green, blue) { 97 | return red * 0.299 + green * 0.587 + blue * 0.114 > 154; 98 | } 99 | -------------------------------------------------------------------------------- /scripts/pixel-swap.js: -------------------------------------------------------------------------------- 1 | var Module=typeof Module!="undefined"?Module:{};var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}var fs,nodePath;if(typeof require==="function"){fs=require("fs");nodePath=require("path")}read_=(filename,binary)=>{filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="pixel-swap.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["m"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["q"];addOnInit(Module["asm"]["n"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function ___cxa_allocate_exception(size){return _malloc(size+24)+24}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _abort(){abort("")}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function getHeapMax(){return 2147483648}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return()=>crypto_module["randomBytes"](1)[0]}catch(e){}}return()=>abort("randomDevice")}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(FS.cwd(),path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(p=>!!p),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;tempI64=[Math.floor(stat.atime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.atime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=0;tempI64=[Math.floor(stat.mtime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.mtime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=0;tempI64=[Math.floor(stat.ctime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.ctime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm,loc){return _strftime(s,maxsize,format,tm)}function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;_proc_exit(status)}function handleException(e){if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)}function getCFunc(ident){var func=Module["_"+ident];return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;run(); 2 | -------------------------------------------------------------------------------- /scripts/pixel-swap.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunkums/PixelSwap/6237a1e3b924745c5e9ee6ac163d76df181f32ed/scripts/pixel-swap.wasm -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Munro"; 3 | src: url(./fonts/munro.ttf); 4 | } 5 | 6 | @font-face { 7 | font-family: "Munro Small"; 8 | src: url(./fonts/munro-small.ttf); 9 | } 10 | 11 | * { 12 | font-family: "Roboto Mono", monospace; 13 | color: #535657; 14 | } 15 | 16 | .light-font { 17 | color: #f4faff; 18 | } 19 | 20 | .dark-font { 21 | color: #4f646f; 22 | } 23 | 24 | body, 25 | h1, 26 | h2, 27 | h3, 28 | html, 29 | p { 30 | margin: 0; 31 | } 32 | 33 | body, 34 | html { 35 | height: 100%; 36 | } 37 | 38 | html { 39 | background-color: #4f646f; 40 | display: flex; 41 | justify-content: center; 42 | } 43 | 44 | body { 45 | display: flex; 46 | flex-direction: column; 47 | width: 66%; 48 | align-items: center; 49 | } 50 | 51 | @media only screen and (max-width: 992px) { 52 | body { 53 | width: 94%; 54 | } 55 | } 56 | 57 | #content { 58 | margin-top: 0.1rem; 59 | background-color: #f4faff; 60 | display: flex; 61 | flex-direction: row; 62 | border: solid #b7adcf 0.2rem; 63 | border-radius: 0.33rem; 64 | width: 100%; 65 | flex: auto; 66 | } 67 | 68 | #body-left, 69 | #body-right { 70 | display: flex; 71 | flex-direction: column; 72 | overflow-x: scroll; 73 | padding: 1rem; 74 | border: solid #dee7e7 0.175rem; 75 | } 76 | 77 | #body-left { 78 | width: 25%; 79 | } 80 | 81 | #body-right { 82 | width: 75%; 83 | } 84 | 85 | #body-right-top { 86 | display: flex; 87 | flex-wrap: wrap; 88 | } 89 | 90 | .download-button { 91 | margin-left: auto; 92 | align-self: center; 93 | margin-bottom: 0.5rem; 94 | } 95 | 96 | label { 97 | font-weight: 500; 98 | } 99 | 100 | input, 101 | select { 102 | font-weight: 400; 103 | } 104 | 105 | button { 106 | font-size: medium; 107 | font-weight: 900; 108 | } 109 | 110 | button, 111 | select { 112 | color: #535657; 113 | background-color: white; 114 | border: 0.125rem solid #dee7e7; 115 | border-radius: 0.25rem; 116 | } 117 | 118 | header { 119 | display: flex; 120 | align-items: center; 121 | width: 100%; 122 | } 123 | 124 | header img { 125 | width: 3.7rem; 126 | margin-right: 1rem; 127 | } 128 | 129 | hr { 130 | border: 0; 131 | clear: both; 132 | display: block; 133 | width: 100%; 134 | background-color: #dee7e7; 135 | height: 0.125rem; 136 | } 137 | 138 | h1, 139 | h2 { 140 | color: #f4faff; 141 | padding: 1rem 0; 142 | width: 50%; 143 | overflow: hidden; 144 | justify-content: center; 145 | } 146 | 147 | h1 { 148 | font-family: "Munro", "Roboto Mono", monospace; 149 | text-align: start; 150 | } 151 | 152 | h2 { 153 | font-family: "Munro Small", monospace; 154 | font-size: x-large; 155 | text-align: end; 156 | display: inline-block; 157 | vertical-align: center; 158 | line-height: normal; 159 | } 160 | 161 | p { 162 | flex: auto; 163 | font-size: small; 164 | } 165 | 166 | span { 167 | display: block; 168 | } 169 | 170 | canvas { 171 | box-sizing: border-box; 172 | background-color: white; 173 | border: 0.125rem solid #dee7e7; 174 | margin-bottom: 0.5rem; 175 | width: 100%; 176 | image-rendering: -moz-crisp-edges; 177 | image-rendering: -webkit-crisp-edges; 178 | image-rendering: pixelated; 179 | image-rendering: crisp-edges; 180 | } 181 | 182 | input, 183 | select { 184 | margin-top: 1rem; 185 | margin-bottom: 1rem; 186 | } 187 | 188 | footer { 189 | text-align: center; 190 | color: #dee7e7; 191 | padding: 0.5rem; 192 | } 193 | 194 | a { 195 | color: #dee7e7; 196 | } 197 | 198 | @media only screen and (min-width: 600px) { 199 | } 200 | --------------------------------------------------------------------------------