diff --git a/dist/BlueMarble-For-GreasyFork.user.css b/dist/BlueMarble-For-GreasyFork.user.css
index eb9d159..51f4238 100644
--- a/dist/BlueMarble-For-GreasyFork.user.css
+++ b/dist/BlueMarble-For-GreasyFork.user.css
@@ -1,131 +1,3 @@
-/* src/WindowFilter.css */
-#bm-window-filter p svg {
- display: inline;
- height: 1em;
- fill: white;
-}
-#bm-filter-flex {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- gap: 1em 3ch;
-}
-.bm-filter-color {
- width: fit-content;
- max-width: 35ch;
- background-color: rgba(21, 48, 99, 0.9);
- border-radius: 1em;
- padding: 0.5em;
- gap: 1ch;
- transition: background-color 0.3s ease;
-}
-.bm-filter-color:hover,
-.bm-filter-color:focus-within {
- background-color: rgba(17, 40, 85, 0.9);
-}
-.bm-filter-container-rgb {
- display: block;
- border: thick double darkslategray;
- width: fit-content;
- height: fit-content;
- padding: 1ch;
-}
-.bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
- background:
- conic-gradient(
- #aa0000 0%,
- #aaaa00 16.6%,
- #00aa00 33.3%,
- #00aaaa 50%,
- #0000aa 66.6%,
- #aa00aa 83.3%,
- #aa0000 100%);
-}
-.bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
- background: url('data:image/svg+xml;utf8,') repeat;
- background-color: transparent !important;
-}
-.bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
- fill: white !important;
-}
-.bm-filter-color[data-id="0"] .bm-filter-container-rgb {
- background-color: transparent !important;
-}
-#bm-window-filter .bm-filter-container-rgb button {
- padding: 0.75em 0.5ch;
-}
-.bm-filter-container-rgb svg {
- width: 4ch;
-}
-.bm-filter-color > .bm-flex-between {
- flex-direction: column;
- align-items: flex-start;
- gap: 0;
-}
-.bm-filter-color small {
- font-size: 0.75em;
-}
-#bm-window-filter .bm-filter-color.bm-color-hide {
- display: none;
-}
-.bm-windowed #bm-filter-flex {
- flex-direction: column;
- gap: 0.25em;
-}
-.bm-windowed .bm-filter-color {
- width: auto;
- margin: 0;
- padding: 0;
-}
-.bm-windowed .bm-filter-container-rgb {
- display: flex;
- width: 100%;
- gap: 0.5ch;
- align-items: center;
- padding: 0.1em 0.5ch;
- border: none;
- border-radius: 1em;
-}
-#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
- padding: 0.5em 0.25ch;
-}
-.bm-windowed .bm-filter-container-rgb svg {
- width: 3ch;
-}
-.bm-windowed .bm-filter-color h2 {
- font-size: 0.75em;
-}
-
-/* src/WindowWizard.css */
-#bm-wizard-tlist {
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- align-items: flex-start;
-}
-#bm-wizard-tlist > .bm-container {
- width: 100%;
- justify-content: flex-start;
- background-color: rgba(21, 48, 99, 0.9);
- border-radius: 1em;
- padding: 0.5em;
- transition: background-color 0.3s ease;
-}
-#bm-wizard-tlist > .bm-container:hover,
-#bm-wizard-tlist > .bm-container:focus-within {
- background-color: rgba(17, 40, 85, 0.9);
-}
-#bm-wizard-tlist .bm-wizard-template-container-image {
- height: 100%;
- font-size: xxx-large;
-}
-#bm-wizard-tlist .bm-wizard-template-container-flavor {
- flex-direction: column;
- align-items: flex-start;
- gap: 0;
-}
-
/* src/confettiManager.css */
div:has(> confetti-piece) {
position: absolute;
@@ -222,7 +94,8 @@ confetti-piece {
font-weight: bold;
vertical-align: middle;
}
-.bm-dragbar h1 {
+.bm-dragbar h1,
+.bm-dragbar-text {
font-size: 1.2em;
user-select: none;
overflow: hidden;
@@ -406,11 +279,201 @@ input[type=file] {
font-size: 1.6em;
font-family: monospace;
}
-.bm-container:not(#bm-window-main .bm-container) {
- margin: 0.25em 0;
+.bm-windowed .bm-container:not(#bm-window-main .bm-container) {
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
}
.bm-windowed h1:not(#bm-window-main h1) {
font-size: 1em;
}
+/* src/WindowFilter.css */
+#bm-window-filter p svg {
+ display: inline;
+ height: 1em;
+ fill: white;
+}
+#bm-filter-flex {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 1em 3ch;
+}
+#bm-window-filter .bm-filter-color {
+ width: fit-content;
+ max-width: 35ch;
+ background-color: rgba(21, 48, 99, 0.9);
+ border-radius: 1em;
+ padding: 0.5em;
+ gap: 1ch;
+ transition: background-color 0.3s ease;
+}
+#bm-window-filter .bm-filter-color:hover,
+#bm-window-filter.bm-filter-color:focus-within {
+ background-color: rgba(17, 40, 85, 0.9);
+}
+#bm-window-filter .bm-filter-container-rgb {
+ display: block;
+ border: thick double darkslategray;
+ width: fit-content;
+ height: fit-content;
+ padding: 1ch;
+}
+#bm-window-filter .bm-filter-color[data-id="-2"] .bm-filter-container-rgb {
+ background:
+ conic-gradient(
+ #aa0000 0%,
+ #aaaa00 16.6%,
+ #00aa00 33.3%,
+ #00aaaa 50%,
+ #0000aa 66.6%,
+ #aa00aa 83.3%,
+ #aa0000 100%);
+}
+#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb {
+ background: url('data:image/svg+xml;utf8,') repeat;
+ background-color: transparent !important;
+}
+#bm-window-filter .bm-filter-color[data-id="-1"] .bm-filter-container-rgb svg {
+ fill: white !important;
+}
+#bm-window-filter .bm-filter-color[data-id="0"] .bm-filter-container-rgb {
+ background-color: transparent !important;
+}
+#bm-window-filter .bm-filter-container-rgb button {
+ padding: 0.75em 0.5ch;
+}
+#bm-window-filter .bm-filter-container-rgb svg {
+ width: 4ch;
+}
+#bm-window-filter .bm-filter-color > .bm-flex-between {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0;
+}
+#bm-window-filter .bm-filter-color small {
+ font-size: 0.75em;
+}
+#bm-window-filter .bm-filter-color.bm-color-hide {
+ display: none;
+}
+#bm-window-filter.bm-windowed #bm-filter-flex {
+ flex-direction: column;
+ gap: 0.25em;
+}
+#bm-window-filter.bm-windowed .bm-filter-color {
+ width: auto;
+ margin: 0;
+ padding: 0;
+}
+#bm-window-filter.bm-windowed .bm-filter-container-rgb {
+ display: flex;
+ width: 100%;
+ gap: 0.5ch;
+ align-items: center;
+ padding: 0.1em 0.5ch;
+ border: none;
+ border-radius: 1em;
+}
+#bm-window-filter.bm-windowed .bm-filter-container-rgb button {
+ padding: 0.5em 0.25ch;
+}
+#bm-window-filter.bm-windowed .bm-filter-container-rgb svg {
+ width: 3ch;
+}
+#bm-window-filter.bm-windowed .bm-filter-color h2 {
+ font-size: 0.75em;
+}
+#bm-window-filter #bm-filter-windowed-color-totals {
+ font-size: 1em;
+}
+
+/* src/WindowSettings.css */
+#bm-window-settings div:has(> .bm-highlight-preset-container) {
+ width: fit-content;
+ justify-content: flex-start;
+}
+#bm-window-settings .bm-highlight-preset-container {
+ display: flex;
+ flex-direction: column;
+ width: 13%;
+}
+#bm-window-settings .bm-highlight-preset-container span {
+ width: fit-content;
+ margin: auto;
+ font-size: 0.7em;
+}
+#bm-window-settings .bm-highlight-preset-container button {
+ width: fit-content;
+ padding: 0;
+ border-radius: 0;
+}
+#bm-window-settings .bm-highlight-preset-container svg {
+ stroke: #333;
+ stroke-width: 0.02px;
+ width: 100%;
+ min-width: 1.5ch;
+ max-width: 14.5ch;
+}
+#bm-window-settings .bm-highlight-preset-container button:hover svg,
+#bm-window-settings .bm-highlight-preset-container button:focus svg {
+ opacity: 0.9;
+}
+#bm-window-settings .bm-highlight-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ width: 25%;
+ min-width: 3ch;
+ max-width: 15ch;
+}
+#bm-window-settings .bm-highlight-grid > button {
+ width: 100%;
+ padding: 0;
+ aspect-ratio: 1 / 1;
+ background-color: white;
+ border: #333 1px solid;
+ border-radius: 0;
+ box-sizing: border-box;
+}
+#bm-window-settings .bm-highlight-grid > button[data-status=Incorrect] {
+ background-color: brown;
+}
+#bm-window-settings .bm-highlight-grid > button[data-status=Template] {
+ background-color: darkslategray;
+}
+#bm-window-settings .bm-highlight-grid > button:hover,
+#bm-window-settings .bm-highlight-grid > button:focus {
+ opacity: 0.8;
+}
+
+/* src/WindowWizard.css */
+#bm-wizard-tlist {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+#bm-wizard-tlist > .bm-container {
+ width: 100%;
+ justify-content: flex-start;
+ background-color: rgba(21, 48, 99, 0.9);
+ border-radius: 1em;
+ padding: 0.5em;
+ transition: background-color 0.3s ease;
+}
+#bm-wizard-tlist > .bm-container:hover,
+#bm-wizard-tlist > .bm-container:focus-within {
+ background-color: rgba(17, 40, 85, 0.9);
+}
+#bm-wizard-tlist .bm-wizard-template-container-image {
+ height: 100%;
+ font-size: xxx-large;
+}
+#bm-wizard-tlist .bm-wizard-template-container-flavor {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0;
+}
+
/* src/main.css */
diff --git a/dist/BlueMarble-For-GreasyFork.user.js b/dist/BlueMarble-For-GreasyFork.user.js
index 10597ba..f86342c 100644
--- a/dist/BlueMarble-For-GreasyFork.user.js
+++ b/dist/BlueMarble-For-GreasyFork.user.js
@@ -2,14 +2,14 @@
// @name Blue Marble
// @name:en Blue Marble
// @namespace https://github.com/SwingTheVine/
-// @version 0.91.0
+// @version 0.91.102
// @description A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
// @description:en A userscript to enhance the user experience on Wplace.live. This includes, but is not limited to: uploading images to display locally on a canvas, adding a button to move the Wplace color palette menu, and other QoL features.
// @author SwingTheVine
// @license MPL-2.0
// @supportURL https://discord.gg/tpeBPy46hf
// @homepageURL https://bluemarble.lol/
-// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/ffa17bc9a7c2db10efc201437dbf1637e11a6f61/dist/assets/Favicon.png
+// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/assets/Favicon.png
// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js
// @match https://wplace.live/*
@@ -21,7 +21,7 @@
// @grant GM_xmlhttpRequest
// @grant GM.download
// @connect telemetry.thebluecorner.net
-// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/ffa17bc9a7c2db10efc201437dbf1637e11a6f61/dist/BlueMarble-For-GreasyFork.user.css
+// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/78477321232b29c09e3794c360068d7d23a0172c/dist/BlueMarble-For-GreasyFork.user.css
// @antifeature tracking Anonymous opt-in telemetry data
// @noframes
// ==/UserScript==
@@ -317,196 +317,6 @@
{ "id": 63, "premium": true, "name": "Light Stone", "rgb": [205, 197, 158] }
];
- // src/Template.js
- var _Template_instances, calculateTotalPixelsFromImageData_fn;
- var Template = class {
- /** The constructor for the {@link Template} class with enhanced pixel tracking.
- * @param {Object} [params={}] - Object containing all optional parameters
- * @param {string} [params.displayName='My template'] - The display name of the template
- * @param {number} [params.sortID=0] - The sort number of the template for rendering priority
- * @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions)
- * @param {string} [params.url=''] - The URL to the source image
- * @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap)
- * @param {Array} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY)
- * @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk as a bitmap
- * @param {Object} [params.chunked32={}] - The affected chunks of the template, and their template for each chunk as a Uint32Array
- * @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles)
- * @param {Object} [params.pixelCount={total:0, colors:Map}] - Total number of pixels in the template (calculated automatically during processing)
- * @since 0.65.2
- */
- constructor({
- displayName = "My template",
- sortID = 0,
- authorID = "",
- url = "",
- file = null,
- coords: coords2 = null,
- chunked = null,
- chunked32 = {},
- tileSize = 1e3
- } = {}) {
- __privateAdd(this, _Template_instances);
- this.displayName = displayName;
- this.sortID = sortID;
- this.authorID = authorID;
- this.url = url;
- this.file = file;
- this.coords = coords2;
- this.chunked = chunked;
- this.chunked32 = chunked32;
- this.tileSize = tileSize;
- this.pixelCount = { total: 0, colors: /* @__PURE__ */ new Map() };
- }
- /** Creates chunks of the template for each tile.
- * @param {Number} tileSize - Size of the tile as determined by templateManager
- * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses
- * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates
- * @since 0.65.4
- */
- async createTemplateTiles(tileSize, paletteBM) {
- console.log("Template coordinates:", this.coords);
- const shreadSize = 3;
- const bitmap = await createImageBitmap(this.file);
- const imageWidth = bitmap.width;
- const imageHeight = bitmap.height;
- this.tileSize = tileSize;
- const templateTiles = {};
- const templateTilesBuffers = {};
- const canvas = new OffscreenCanvas(this.tileSize, this.tileSize);
- const context = canvas.getContext("2d", { willReadFrequently: true });
- canvas.width = imageWidth;
- canvas.height = imageHeight;
- context.imageSmoothingEnabled = false;
- context.drawImage(bitmap, 0, 0);
- let timer = Date.now();
- const totalPixelMap = __privateMethod(this, _Template_instances, calculateTotalPixelsFromImageData_fn).call(this, context.getImageData(0, 0, imageWidth, imageHeight), paletteBM);
- console.log(`Calculating total pixels took ${(Date.now() - timer) / 1e3} seconds`);
- let totalPixels = 0;
- const transparentColorID = 0;
- for (const [color, total] of totalPixelMap) {
- if (color == transparentColorID) {
- continue;
- }
- totalPixels += total;
- }
- this.pixelCount = { total: totalPixels, colors: totalPixelMap };
- timer = Date.now();
- const canvasMask = new OffscreenCanvas(3, 3);
- const contextMask = canvasMask.getContext("2d");
- contextMask.clearRect(0, 0, 3, 3);
- contextMask.fillStyle = "white";
- contextMask.fillRect(1, 1, 1, 1);
- for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) {
- const drawSizeY = Math.min(this.tileSize - pixelY % this.tileSize, imageHeight - (pixelY - this.coords[3]));
- console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`);
- for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2]; ) {
- console.log(`Pixel X: ${pixelX}
-Pixel Y: ${pixelY}`);
- const drawSizeX = Math.min(this.tileSize - pixelX % this.tileSize, imageWidth - (pixelX - this.coords[2]));
- console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`);
- console.log(`Draw Size X: ${drawSizeX}
-Draw Size Y: ${drawSizeY}`);
- const canvasWidth = drawSizeX * shreadSize;
- const canvasHeight = drawSizeY * shreadSize;
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
- console.log(`Draw X: ${drawSizeX}
-Draw Y: ${drawSizeY}
-Canvas Width: ${canvasWidth}
-Canvas Height: ${canvasHeight}`);
- context.imageSmoothingEnabled = false;
- console.log(`Getting X ${pixelX}-${pixelX + drawSizeX}
-Getting Y ${pixelY}-${pixelY + drawSizeY}`);
- context.clearRect(0, 0, canvasWidth, canvasHeight);
- context.drawImage(
- bitmap,
- // Bitmap image to draw
- pixelX - this.coords[2],
- // Coordinate X to draw *from*
- pixelY - this.coords[3],
- // Coordinate Y to draw *from*
- drawSizeX,
- // X width to draw *from*
- drawSizeY,
- // Y height to draw *from*
- 0,
- // Coordinate X to draw *at*
- 0,
- // Coordinate Y to draw *at*
- drawSizeX * shreadSize,
- // X width to draw *at*
- drawSizeY * shreadSize
- // Y height to draw *at*
- );
- context.save();
- context.globalCompositeOperation = "destination-in";
- context.fillStyle = context.createPattern(canvasMask, "repeat");
- context.fillRect(0, 0, canvasWidth, canvasHeight);
- context.restore();
- const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
- console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData);
- const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1e3)).toString().padStart(4, "0")},${(this.coords[1] + Math.floor(pixelY / 1e3)).toString().padStart(4, "0")},${(pixelX % 1e3).toString().padStart(3, "0")},${(pixelY % 1e3).toString().padStart(3, "0")}`;
- this.chunked32[templateTileName] = new Uint32Array(imageData.data.buffer);
- templateTiles[templateTileName] = await createImageBitmap(canvas);
- const canvasBlob = await canvas.convertToBlob();
- const canvasBuffer = await canvasBlob.arrayBuffer();
- const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer));
- templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes);
- console.log(templateTiles);
- pixelX += drawSizeX;
- }
- pixelY += drawSizeY;
- }
- console.log(`Parsing template took ${(Date.now() - timer) / 1e3} seconds`);
- console.log("Template Tiles: ", templateTiles);
- console.log("Template Tiles Buffers: ", templateTilesBuffers);
- console.log("Template Tiles Uint32Array: ", this.chunked32);
- return { templateTiles, templateTilesBuffers };
- }
- /** Calculates top left coordinate of template.
- * It uses `Template.chunked` to update `Template.coords`
- * @since 0.88.504
- */
- calculateCoordsFromChunked() {
- let topLeftCoord = [Infinity, Infinity, Infinity, Infinity];
- const tileKeys = Object.keys(this.chunked).sort();
- tileKeys.forEach((key, index) => {
- const [tileX, tileY, pixelX, pixelY] = key.split(",").map(Number);
- if (tileY < topLeftCoord[1] || tileY == topLeftCoord[1] && tileX < topLeftCoord[0]) {
- topLeftCoord = [tileX, tileY, pixelX, pixelY];
- }
- });
- this.coords = topLeftCoord;
- }
- };
- _Template_instances = new WeakSet();
- /** Calculates the total pixels for each color for the image.
- *
- * @param {ImageData} imageData - The pre-shreaded image "casted" onto a canvas
- * @param {Object} paletteBM - The palette Blue Marble uses for colors
- * @param {Number} paletteTolerance - How close an RGB color has to be in order to be considered a palette color. A tolerance of "3" means the sum of the RGB can be up to 3 away from the actual value.
- * @returns {Map} A map where the key is the color ID, and the value is the total pixels for that color ID
- * @since 0.88.6
- */
- calculateTotalPixelsFromImageData_fn = function(imageData, paletteBM) {
- const buffer32Arr = new Uint32Array(imageData.data.buffer);
- const { palette: _, LUT: lookupTable } = paletteBM;
- const _colorpalette = /* @__PURE__ */ new Map();
- for (let pixelIndex = 0; pixelIndex < buffer32Arr.length; pixelIndex++) {
- const pixel = buffer32Arr[pixelIndex];
- let bestColorID = -2;
- if (pixel >>> 24 == 0) {
- bestColorID = 0;
- } else {
- bestColorID = lookupTable.get(pixel) ?? -2;
- }
- const colorIDcount = _colorpalette.get(bestColorID);
- _colorpalette.set(bestColorID, colorIDcount ? colorIDcount + 1 : 1);
- }
- console.log(_colorpalette);
- return _colorpalette;
- };
-
// src/Overlay.js
var _Overlay_instances, createElement_fn, applyAttribute_fn;
var Overlay = class {
@@ -521,6 +331,7 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
this.name = name2;
this.version = version2;
this.apiManager = null;
+ this.settingsManager = null;
this.outputStatusId = "bm-output-status";
this.overlay = null;
this.currentParent = null;
@@ -533,6 +344,13 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
setApiManager(apiManager2) {
this.apiManager = apiManager2;
}
+ /** Populates the settingsManager variable with the settingsManager class.
+ * @param {SettingsManager} settingsManager - The settingsManager class instance
+ * @since 0.91.11
+ */
+ setSettingsManager(settingsManager2) {
+ this.settingsManager = settingsManager2;
+ }
/** Finishes building an element.
* Call this after you are finished adding children.
* If the element will have no children, call it anyways.
@@ -895,8 +713,15 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
addCheckbox(additionalProperties = {}, callback = () => {
}) {
const properties = { "type": "checkbox" };
- const label = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "label", { "textContent": additionalProperties["textContent"] ?? "" });
- delete additionalProperties["textContent"];
+ const labelContent = {};
+ if (!!additionalProperties["textContent"]) {
+ labelContent["textContent"] = additionalProperties["textContent"];
+ delete additionalProperties["textContent"];
+ } else if (!!additionalProperties["innerHTML"]) {
+ labelContent["innerHTML"] = additionalProperties["innerHTML"];
+ delete additionalProperties["textContent"];
+ }
+ const label = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "label", labelContent);
const checkbox = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "input", properties, additionalProperties);
label.insertBefore(checkbox, label.firstChild);
this.buildElement();
@@ -1713,15 +1538,1121 @@ Getting Y ${pixelY}-${pixelY + drawSizeY}`);
(part, i) => i == 0 ? part : part[0].toUpperCase() + part.slice(1)
).join("")] = value;
} else if (property.startsWith("aria")) {
- const camelCase = property.slice(5).split("-").map(
- (part, i) => i == 0 ? part : part[0].toUpperCase() + part.slice(1)
- ).join("");
- element["aria" + camelCase[0].toUpperCase() + camelCase.slice(1)] = value;
+ element.setAttribute(property, value);
} else {
element[property] = value;
}
};
+ // src/WindowSettings.js
+ var _WindowSettings_instances, errorOverrideFailure_fn;
+ var WindowSettings = class extends Overlay {
+ /** Constructor for the Settings window
+ * @param {string} name - The name of the userscript
+ * @param {string} version - The version of the userscript
+ * @since 0.91.11
+ * @see {@link Overlay#constructor} for examples
+ */
+ constructor(name2, version2) {
+ super(name2, version2);
+ __privateAdd(this, _WindowSettings_instances);
+ this.window = null;
+ this.windowID = "bm-window-settings";
+ this.windowParent = document.body;
+ }
+ /** Spawns a Settings window.
+ * If another settings window already exists, we DON'T spawn another!
+ * Parent/child relationships in the DOM structure below are indicated by indentation.
+ * @since 0.91.11
+ */
+ buildWindow() {
+ if (document.querySelector(`#${this.windowID}`)) {
+ document.querySelector(`#${this.windowID}`).remove();
+ return;
+ }
+ this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => {
+ button.onclick = () => instance.handleMinimization(button);
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
+ button.onclick = () => {
+ document.querySelector(`#${this.windowID}`)?.remove();
+ };
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Settings" }).buildElement().buildElement().addHr().buildElement().addP({ "textContent": "Settings take 5 seconds to save." }).buildElement().addDiv({ "class": "bm-container bm-scrollable" }, (instance, div) => {
+ this.buildHighlight();
+ this.buildTemplate();
+ }).buildElement().buildElement().buildElement().buildOverlay(this.windowParent);
+ this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
+ }
+ /** Builds the highlight section of the window.
+ * This should be overriden by {@link SettingsManager}
+ * @since 0.91.11
+ */
+ buildHighlight() {
+ __privateMethod(this, _WindowSettings_instances, errorOverrideFailure_fn).call(this, "Pixel Highlight");
+ }
+ /** Builds the template section of the window.
+ * This should be overriden by {@link SettingsManager}
+ * @since 0.91.68
+ */
+ buildTemplate() {
+ __privateMethod(this, _WindowSettings_instances, errorOverrideFailure_fn).call(this, "Template");
+ }
+ };
+ _WindowSettings_instances = new WeakSet();
+ /** Displays an error when a settings category fails to load.
+ * @param {string} name - The name of the category
+ * @since 0.91.11
+ */
+ errorOverrideFailure_fn = function(name2) {
+ this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": name2 }).buildElement().addHr().buildElement().addP({ "innerHTML": `An error occured loading the ${name2} category. SettingsManager failed to override the ${name2} function inside WindowSettings.` }).buildElement().buildElement();
+ };
+
+ // src/settingsManager.js
+ var _SettingsManager_instances, updateHighlightSettings_fn, updateHighlightToPreset_fn;
+ var SettingsManager = class extends WindowSettings {
+ /** Constructor for the SettingsManager class
+ * @param {string} name - The name of the userscript
+ * @param {string} version - The version of the userscript
+ * @param {Object} userSettings - The user settings as an object
+ * @since 0.91.11
+ */
+ constructor(name2, version2, userSettings2) {
+ var _a;
+ super(name2, version2);
+ __privateAdd(this, _SettingsManager_instances);
+ this.userSettings = userSettings2;
+ (_a = this.userSettings).flags ?? (_a.flags = []);
+ this.userSettingsOld = structuredClone(this.userSettings);
+ this.userSettingsSaveLocation = "bmUserSettings";
+ this.updateFrequency = 5e3;
+ this.lastUpdateTime = 0;
+ setInterval(this.updateUserStorage.bind(this), this.updateFrequency);
+ }
+ /** Updates the user settings in userscript storage
+ * @since 0.91.39
+ */
+ async updateUserStorage() {
+ const userSettingsCurrent = JSON.stringify(this.userSettings);
+ const userSettingsOld = JSON.stringify(this.userSettingsOld);
+ if (userSettingsCurrent != userSettingsOld && Date.now() - this.lastUpdateTime > this.updateFrequency) {
+ await GM.setValue(this.userSettingsSaveLocation, userSettingsCurrent);
+ this.userSettingsOld = structuredClone(this.userSettings);
+ this.lastUpdateTime = Date.now();
+ console.log(userSettingsCurrent);
+ }
+ }
+ /** Toggles a boolean flag to the state that was passed in.
+ * If no state was passed in, the flag will flip to the opposite state.
+ * The existence of the flag determines its state. If it exists, it is `true`.
+ * @param {string} flagName - The name of the flag to toggle
+ * @param {boolean} [state=undefined] - (Optional) The state to change the flag to
+ * @since 0.91.60
+ */
+ toggleFlag(flagName, state = void 0) {
+ const flagIndex = this.userSettings?.flags?.indexOf(flagName) ?? -1;
+ if (flagIndex != -1 && state !== true) {
+ this.userSettings?.flags?.splice(flagIndex, 1);
+ } else if (flagIndex == -1 && state !== false) {
+ this.userSettings?.flags?.push(flagName);
+ }
+ }
+ // This is one of the most insane OOP setups I have ever laid my eyes on
+ /** Builds the "highlight" category of the settings window
+ * @since 0.91.18
+ * @see WindowSettings#buildHighlight
+ */
+ buildHighlight() {
+ const highlightPresetOff = '';
+ const highlightPresetCross = '';
+ const storedHighlight = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]];
+ this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": "Pixel Highlight" }).buildElement().addHr().buildElement().addDiv({ "class": "bm-container", "style": "margin-left: 1.5ch;" }).addCheckbox({ "textContent": "Highlight transparent pixels" }, (instance, label, checkbox) => {
+ checkbox.checked = !this.userSettings?.flags?.includes("hl-noTrans");
+ checkbox.onchange = (event) => this.toggleFlag("hl-noTrans", !event.target.checked);
+ }).buildElement().addP({ "id": "bm-highlight-preset-label", "textContent": "Choose a preset:", "style": "font-weight: 700;" }).buildElement().addDiv({ "class": "bm-flex-center", "role": "group", "aria-labelledby": "bm-highlight-preset-label" }).addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "None" }).buildElement().addButton({ "innerHTML": highlightPresetOff, "aria-label": 'Preset "None"' }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "None");
+ }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "Cross" }).buildElement().addButton({ "innerHTML": highlightPresetCross, "aria-label": 'Preset "Cross Shape"' }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "Cross");
+ }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "X" }).buildElement().addButton({ "innerHTML": highlightPresetCross.replace('d="M1,0H2V1H3V2H2V3H1V2H0V1H1Z"', 'd="M0,0V1H3V0H2V3H3V2H0V3H1V0Z"'), "aria-label": 'Preset "X Shape"' }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "X");
+ }).buildElement().buildElement().addDiv({ "class": "bm-highlight-preset-container" }).addSpan({ "textContent": "Full" }).buildElement().addButton({ "innerHTML": highlightPresetOff.replace("#fff", "#2f4f4f"), "aria-label": 'Preset "Full Template"' }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightToPreset_fn).call(this, "Full");
+ }).buildElement().buildElement().buildElement().addP({ "id": "bm-highlight-grid-label", "textContent": "Create a custom pattern:", "style": "font-weight: 700;" }).buildElement().addDiv({ "class": "bm-highlight-grid", "role": "group", "aria-labelledby": "bm-highlight-grid-label" });
+ for (let buttonY = -1; buttonY <= 1; buttonY++) {
+ for (let buttonX = -1; buttonX <= 1; buttonX++) {
+ const buttonState = storedHighlight[storedHighlight.findIndex(([, x, y]) => x == buttonX && y == buttonY)]?.[0] ?? 0;
+ let buttonStateName = "Disabled";
+ if (buttonState == 1) {
+ buttonStateName = "Incorrect";
+ } else if (buttonState == 2) {
+ buttonStateName = "Template";
+ }
+ this.window = this.addButton({
+ "data-status": buttonStateName,
+ "aria-label": `Sub-pixel ${buttonStateName.toLowerCase()}`
+ }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _SettingsManager_instances, updateHighlightSettings_fn).call(this, button, [buttonX, buttonY]);
+ }).buildElement();
+ }
+ }
+ this.window = this.buildElement().buildElement().buildElement();
+ }
+ /** Build the "template" category of settings window
+ * @since 0.91.68
+ * @see WindowSettings#buildTemplate
+ */
+ buildTemplate() {
+ this.window = this.addDiv({ "class": "bm-container" }).addHeader(2, { "textContent": "Pixel Highlight" }).buildElement().addHr().buildElement().addDiv({ "class": "bm-container", "style": "margin-left: 1.5ch;" }).addCheckbox({ "textContent": "Template creation should skip transparent tiles" }, (instance, label, checkbox) => {
+ checkbox.checked = !this.userSettings?.flags?.includes("hl-noSkip");
+ checkbox.onchange = (event) => this.toggleFlag("hl-noSkip", !event.target.checked);
+ }).buildElement().addCheckbox({ "innerHTML": "Experimental: Template creation should aggressively skip transparent tiles" }, (instance, label, checkbox) => {
+ checkbox.checked = this.userSettings?.flags?.includes("hl-agSkip");
+ checkbox.onchange = (event) => this.toggleFlag("hl-agSkip", event.target.checked);
+ }).buildElement().buildElement().buildElement();
+ }
+ };
+ _SettingsManager_instances = new WeakSet();
+ /** Updates the display of the highlight buttons in the settings window.
+ * Additionally, it will update user settings with the new selection.
+ * @param {HTMLButtonElement} button - The button that was pressed
+ * @param {Array} coords - The relative coordinates of the button
+ * @since 0.91.46
+ */
+ updateHighlightSettings_fn = function(button, coords2) {
+ button.disabled = true;
+ const status = button.dataset["status"];
+ const userStorageOld = this.userSettings?.highlight ?? [[1, 0, 1], [2, 0, 0], [1, -1, 0], [1, 1, 0], [1, 0, -1]];
+ let userStorageChange = [2, 0, 0];
+ const userStorageNew = userStorageOld;
+ switch (status) {
+ // If the button was in the "Disabled" state
+ case "Disabled":
+ button.dataset["status"] = "Incorrect";
+ button.ariaLabel = "Sub-pixel incorrect";
+ userStorageChange = [1, ...coords2];
+ break;
+ // If the button was in the "Incorrect" state
+ case "Incorrect":
+ button.dataset["status"] = "Template";
+ button.ariaLabel = "Sub-pixel template";
+ userStorageChange = [2, ...coords2];
+ break;
+ // If the button was in the "Template" state
+ case "Template":
+ button.dataset["status"] = "Disabled";
+ button.ariaLabel = "Sub-pixel disabled";
+ userStorageChange = [0, ...coords2];
+ break;
+ }
+ const indexOfChange = userStorageOld.findIndex(([, x, y]) => x == userStorageChange[1] && y == userStorageChange[2]);
+ if (userStorageChange[0] != 0) {
+ if (indexOfChange != -1) {
+ userStorageNew[indexOfChange] = userStorageChange;
+ } else {
+ userStorageNew.push(userStorageChange);
+ }
+ } else if (indexOfChange != -1) {
+ userStorageNew.splice(indexOfChange, 1);
+ }
+ this.userSettings["highlight"] = userStorageNew;
+ button.disabled = false;
+ };
+ updateHighlightToPreset_fn = async function(preset) {
+ const presetButtons = document.querySelectorAll(".bm-highlight-preset-container button");
+ for (const button of presetButtons) {
+ button.disabled = true;
+ }
+ let presetArray = [0, 0, 0, 0, 2, 0, 0, 0, 0];
+ switch (preset) {
+ case "Cross":
+ presetArray = [0, 1, 0, 1, 2, 1, 0, 1, 0];
+ break;
+ case "X":
+ presetArray = [1, 0, 1, 0, 2, 0, 1, 0, 1];
+ break;
+ case "Full":
+ presetArray = [2, 2, 2, 2, 2, 2, 2, 2, 2];
+ break;
+ }
+ const buttons = document.querySelector(".bm-highlight-grid")?.childNodes ?? [];
+ for (let buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++) {
+ const button = buttons[buttonIndex];
+ let buttonState = button.dataset["status"];
+ buttonState = buttonState != "Disabled" ? buttonState != "Incorrect" ? 2 : 1 : 0;
+ let buttonStateDelta = presetArray[buttonIndex] - buttonState;
+ if (buttonStateDelta == 0) {
+ continue;
+ }
+ buttonStateDelta += buttonStateDelta < 0 ? 3 : 0;
+ button.click();
+ if (buttonStateDelta == 2) {
+ for (let timeWaited = 0; timeWaited < 200; timeWaited += 10) {
+ if (!button.disabled) {
+ break;
+ }
+ await sleep(10);
+ }
+ button.click();
+ }
+ }
+ for (const button of presetButtons) {
+ button.disabled = false;
+ }
+ };
+
+ // src/Template.js
+ var _Template_instances, calculateTotalPixelsFromImageData_fn;
+ var Template = class {
+ /** The constructor for the {@link Template} class with enhanced pixel tracking.
+ * @param {Object} [params={}] - Object containing all optional parameters
+ * @param {string} [params.displayName='My template'] - The display name of the template
+ * @param {number} [params.sortID=0] - The sort number of the template for rendering priority
+ * @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions)
+ * @param {string} [params.url=''] - The URL to the source image
+ * @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap)
+ * @param {Array} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY)
+ * @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk as a bitmap
+ * @param {Object} [params.chunked32={}] - The affected chunks of the template, and their template for each chunk as a Uint32Array
+ * @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles)
+ * @param {Object} [params.pixelCount={total:0, colors:Map}] - Total number of pixels in the template (calculated automatically during processing)
+ * @since 0.65.2
+ */
+ constructor({
+ displayName = "My template",
+ sortID = 0,
+ authorID = "",
+ url = "",
+ file = null,
+ coords: coords2 = null,
+ chunked = null,
+ chunked32 = {},
+ tileSize = 1e3
+ } = {}) {
+ __privateAdd(this, _Template_instances);
+ this.displayName = displayName;
+ this.sortID = sortID;
+ this.authorID = authorID;
+ this.url = url;
+ this.file = file;
+ this.coords = coords2;
+ this.chunked = chunked;
+ this.chunked32 = chunked32;
+ this.tileSize = tileSize;
+ this.pixelCount = { total: 0, colors: /* @__PURE__ */ new Map() };
+ this.shouldSkipTransTiles = true;
+ this.shouldAggSkipTransTiles = false;
+ }
+ /** Creates chunks of the template for each tile.
+ * @param {Number} tileSize - Size of the tile as determined by templateManager
+ * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses
+ * @param {boolean} shouldSkipTransTiles - Should transparent tiles be skipped over when creating the template?
+ * @param {boolean} shouldAggSkipTransTiles - Should transparent tiles be aggressively skipped over when creating the template?
+ * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates
+ * @since 0.65.4
+ */
+ async createTemplateTiles(tileSize, paletteBM, shouldSkipTransTiles, shouldAggSkipTransTiles) {
+ console.log("Template coordinates:", this.coords);
+ this.shouldSkipTransTiles = shouldSkipTransTiles;
+ this.shouldAggSkipTransTiles = shouldAggSkipTransTiles;
+ const shreadSize = 3;
+ const bitmap = await createImageBitmap(this.file);
+ const imageWidth = bitmap.width;
+ const imageHeight = bitmap.height;
+ this.tileSize = tileSize;
+ const templateTiles = {};
+ const templateTilesBuffers = {};
+ const canvas = new OffscreenCanvas(this.tileSize, this.tileSize);
+ const context = canvas.getContext("2d", { willReadFrequently: true });
+ const transCanvas = new OffscreenCanvas(this.tileSize, this.tileSize);
+ const transContext = transCanvas.getContext("2d", { willReadFrequently: true });
+ transContext.globalCompositeOperation = "destination-over";
+ canvas.width = imageWidth;
+ canvas.height = imageHeight;
+ context.imageSmoothingEnabled = false;
+ context.drawImage(bitmap, 0, 0);
+ let timer = Date.now();
+ const totalPixelMap = __privateMethod(this, _Template_instances, calculateTotalPixelsFromImageData_fn).call(this, context.getImageData(0, 0, imageWidth, imageHeight), paletteBM);
+ console.log(`Calculating total pixels took ${(Date.now() - timer) / 1e3} seconds`);
+ let totalPixels = 0;
+ const transparentColorID = 0;
+ for (const [color, total] of totalPixelMap) {
+ if (color == transparentColorID) {
+ continue;
+ }
+ totalPixels += total;
+ }
+ this.pixelCount = { total: totalPixels, colors: totalPixelMap };
+ timer = Date.now();
+ const canvasMask = new OffscreenCanvas(3, 3);
+ const contextMask = canvasMask.getContext("2d");
+ contextMask.clearRect(0, 0, 3, 3);
+ contextMask.fillStyle = "white";
+ contextMask.fillRect(1, 1, 1, 1);
+ for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) {
+ const drawSizeY = Math.min(this.tileSize - pixelY % this.tileSize, imageHeight - (pixelY - this.coords[3]));
+ console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`);
+ for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2]; ) {
+ console.log(`Pixel X: ${pixelX}
+Pixel Y: ${pixelY}`);
+ const drawSizeX = Math.min(this.tileSize - pixelX % this.tileSize, imageWidth - (pixelX - this.coords[2]));
+ if (shouldSkipTransTiles) {
+ const isTemplateTileTransparent = !this.calculateCanvasTransparency({
+ bitmap,
+ bitmapParams: [pixelX - this.coords[2], pixelY - this.coords[3], drawSizeX, drawSizeY],
+ // Top left X, Top left Y, Width, Height
+ transCanvas,
+ transContext
+ });
+ console.log(`Tile contains template: ${!isTemplateTileTransparent}`);
+ if (isTemplateTileTransparent) {
+ pixelX += drawSizeX;
+ continue;
+ }
+ }
+ console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`);
+ console.log(`Draw Size X: ${drawSizeX}
+Draw Size Y: ${drawSizeY}`);
+ const canvasWidth = drawSizeX * shreadSize;
+ const canvasHeight = drawSizeY * shreadSize;
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+ console.log(`Draw X: ${drawSizeX}
+Draw Y: ${drawSizeY}
+Canvas Width: ${canvasWidth}
+Canvas Height: ${canvasHeight}`);
+ context.imageSmoothingEnabled = false;
+ console.log(`Getting X ${pixelX}-${pixelX + drawSizeX}
+Getting Y ${pixelY}-${pixelY + drawSizeY}`);
+ context.clearRect(0, 0, canvasWidth, canvasHeight);
+ context.drawImage(
+ bitmap,
+ // Bitmap image to draw
+ pixelX - this.coords[2],
+ // Coordinate X to draw *from*
+ pixelY - this.coords[3],
+ // Coordinate Y to draw *from*
+ drawSizeX,
+ // X width to draw *from*
+ drawSizeY,
+ // Y height to draw *from*
+ 0,
+ // Coordinate X to draw *at*
+ 0,
+ // Coordinate Y to draw *at*
+ drawSizeX * shreadSize,
+ // X width to draw *at*
+ drawSizeY * shreadSize
+ // Y height to draw *at*
+ );
+ context.save();
+ context.globalCompositeOperation = "destination-in";
+ console.log(`Should Skip: ${shouldSkipTransTiles}; Should Agg Skip: ${shouldAggSkipTransTiles}`);
+ context.fillStyle = context.createPattern(canvasMask, "repeat");
+ context.fillRect(0, 0, canvasWidth, canvasHeight);
+ context.restore();
+ const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
+ console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData);
+ const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1e3)).toString().padStart(4, "0")},${(this.coords[1] + Math.floor(pixelY / 1e3)).toString().padStart(4, "0")},${(pixelX % 1e3).toString().padStart(3, "0")},${(pixelY % 1e3).toString().padStart(3, "0")}`;
+ this.chunked32[templateTileName] = new Uint32Array(imageData.data.buffer);
+ templateTiles[templateTileName] = await createImageBitmap(canvas);
+ const canvasBlob = await canvas.convertToBlob();
+ const canvasBuffer = await canvasBlob.arrayBuffer();
+ const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer));
+ templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes);
+ console.log(templateTiles);
+ pixelX += drawSizeX;
+ }
+ pixelY += drawSizeY;
+ }
+ console.log(`Parsing template took ${(Date.now() - timer) / 1e3} seconds`);
+ console.log("Template Tiles: ", templateTiles);
+ console.log("Template Tiles Buffers: ", templateTilesBuffers);
+ console.log("Template Tiles Uint32Array: ", this.chunked32);
+ return { templateTiles, templateTilesBuffers };
+ }
+ /** Detects if the canvas is transparent.
+ * @param {Object} param - Object that contains the parameters for the function
+ * @param {ImageBitmap} param.bitmap - The bitmap template image
+ * @param {Array} param.bitmapParams - The parameters to obtain the template tile image from the bitmap
+ * @param {OffscreenCanvas | HTMLCanvasElement} param.transCanvas - The canvas to draw to in order to calculate this
+ * @param {OffscreenCanvasRenderingContext2D} param.transContext - The context for the transparent canvas to draw to
+ * @return {boolean} Is the canvas transparent? If transparent, then `true` is returned. Otherwise, `false`.
+ * @since 0.91.75
+ */
+ calculateCanvasTransparency({
+ bitmap,
+ bitmapParams,
+ transCanvas,
+ transContext
+ }) {
+ console.log(`Calculating template tile transparency...`);
+ console.log(`Should Skip: ${this.shouldSkipTransTiles}; Should Agg: ${this.shouldAggSkipTransTiles}`);
+ const timer = Date.now();
+ const duplicationCoordinateArray = [
+ [0, 1],
+ // E.g. move 0 on the x axis, and 1 down on the y axis
+ [1, 0],
+ [0, -2],
+ // E.g. move 0 on the x axis, and 2 up on the y axis
+ [-2, 0],
+ [0, 4],
+ [4, 0],
+ [0, -8],
+ [-8, 0],
+ [0, 16],
+ [16, 0],
+ [0, -32],
+ [-32, 0]
+ ];
+ const transCanvasWidth = bitmapParams[2];
+ const transCanvasHeight = bitmapParams[3];
+ transCanvas.width = transCanvasWidth;
+ transCanvas.height = transCanvasHeight;
+ transContext.clearRect(0, 0, transCanvasWidth, transCanvasHeight);
+ if (this.shouldAggSkipTransTiles) {
+ transContext.drawImage(
+ bitmap,
+ ...bitmapParams,
+ // Bitmap image parameters (x, y, width, height)
+ 0,
+ 0,
+ // The coordinate draw the output *at*
+ 10,
+ 10
+ // The width and height of the output
+ );
+ } else {
+ transContext.drawImage(
+ bitmap,
+ ...bitmapParams,
+ // Bitmap image parameters (x, y, width, height)
+ 0,
+ 0,
+ // The coordinate draw the output *at*
+ transCanvasWidth,
+ transCanvasHeight
+ // Stretch to canvas (the canvas should already be the same size as the template image)
+ );
+ for (const [relativeX, relativeY] of duplicationCoordinateArray) {
+ transContext.drawImage(
+ transCanvas,
+ // The canvas we are drawing to *is* the source image
+ 0,
+ 0,
+ transCanvasWidth,
+ transCanvasHeight,
+ // The entire canvas (as a source image)
+ relativeX,
+ relativeY,
+ transCanvasWidth,
+ transCanvasHeight
+ // The output coordinates and size on the same canvas
+ );
+ }
+ transContext.drawImage(
+ transCanvas,
+ // The canvas we are drawing to *is* the source image
+ 0,
+ 0,
+ transCanvasWidth,
+ transCanvasHeight,
+ // The entire canvas (as a source image)
+ 0,
+ 0,
+ 10,
+ 10
+ // The output coordinates and size on the same canvas
+ );
+ }
+ const shunkCanvas = transContext.getImageData(0, 0, 10, 10);
+ const shunkCanvas32 = new Uint32Array(shunkCanvas.data.buffer);
+ console.log(`Calculated canvas transparency in ${(Date.now() - timer) / 1e3} seconds.`);
+ for (const pixel of shunkCanvas32) {
+ if (!!pixel) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /** Calculates top left coordinate of template.
+ * It uses `Template.chunked` to update `Template.coords`
+ * @since 0.88.504
+ */
+ calculateCoordsFromChunked() {
+ let topLeftCoord = [Infinity, Infinity, Infinity, Infinity];
+ const tileKeys = Object.keys(this.chunked).sort();
+ tileKeys.forEach((key, index) => {
+ const [tileX, tileY, pixelX, pixelY] = key.split(",").map(Number);
+ if (tileY < topLeftCoord[1] || tileY == topLeftCoord[1] && tileX < topLeftCoord[0]) {
+ topLeftCoord = [tileX, tileY, pixelX, pixelY];
+ }
+ });
+ this.coords = topLeftCoord;
+ }
+ };
+ _Template_instances = new WeakSet();
+ /** Calculates the total pixels for each color for the image.
+ *
+ * @param {ImageData} imageData - The pre-shreaded image "casted" onto a canvas
+ * @param {Object} paletteBM - The palette Blue Marble uses for colors
+ * @param {Number} paletteTolerance - How close an RGB color has to be in order to be considered a palette color. A tolerance of "3" means the sum of the RGB can be up to 3 away from the actual value.
+ * @returns {Map} A map where the key is the color ID, and the value is the total pixels for that color ID
+ * @since 0.88.6
+ */
+ calculateTotalPixelsFromImageData_fn = function(imageData, paletteBM) {
+ const buffer32Arr = new Uint32Array(imageData.data.buffer);
+ const { palette: _, LUT: lookupTable } = paletteBM;
+ const _colorpalette = /* @__PURE__ */ new Map();
+ for (let pixelIndex = 0; pixelIndex < buffer32Arr.length; pixelIndex++) {
+ const pixel = buffer32Arr[pixelIndex];
+ let bestColorID = -2;
+ if (pixel >>> 24 == 0) {
+ bestColorID = 0;
+ } else {
+ bestColorID = lookupTable.get(pixel) ?? -2;
+ }
+ const colorIDcount = _colorpalette.get(bestColorID);
+ _colorpalette.set(bestColorID, colorIDcount ? colorIDcount + 1 : 1);
+ }
+ console.log(_colorpalette);
+ return _colorpalette;
+ };
+
+ // src/confetttiManager.js
+ var ConfettiManager = class {
+ /** The constructor for the confetti manager.
+ * @since 0.88.356
+ */
+ constructor() {
+ this.confettiCount = Math.ceil(80 / 1300 * window.innerWidth);
+ this.colorPalette = colorpalette.slice(1);
+ }
+ /** Immedently creates confetti inside the parent element.
+ * @param {HTMLElement} parentElement - The parent element to create confetti inside of
+ * @since 0.88.356
+ */
+ createConfetti(parentElement) {
+ const confettiContainer = document.createElement("div");
+ for (let currentCount = 0; currentCount < this.confettiCount; currentCount++) {
+ const confettiShard = document.createElement("confetti-piece");
+ confettiShard.style.setProperty("--x", `${Math.random() * 100}vw`);
+ confettiShard.style.setProperty("--delay", `${Math.random() * 2}s`);
+ confettiShard.style.setProperty("--duration", `${3 + Math.random() * 3}s`);
+ confettiShard.style.setProperty("--rot", `${Math.random() * 360}deg`);
+ confettiShard.style.setProperty("--size", `${6 + Math.random() * 6}px`);
+ confettiShard.style.backgroundColor = `rgb(${this.colorPalette[Math.floor(Math.random() * this.colorPalette.length)].rgb.join(",")})`;
+ confettiShard.onanimationend = () => {
+ if (confettiShard.parentNode.childElementCount <= 1) {
+ confettiShard.parentNode.remove();
+ } else {
+ confettiShard.remove();
+ }
+ };
+ confettiContainer.appendChild(confettiShard);
+ }
+ parentElement.appendChild(confettiContainer);
+ }
+ };
+ var BlueMarbleConfettiPiece = class extends HTMLElement {
+ };
+ customElements.define("confetti-piece", BlueMarbleConfettiPiece);
+
+ // src/WindowCredits.js
+ var WindowCredts = class extends Overlay {
+ /** Constructor for the Credits window
+ * @param {string} name - The name of the userscript
+ * @param {string} version - The version of the userscript
+ * @since 0.90.9
+ * @see {@link Overlay#constructor} for examples
+ */
+ constructor(name2, version2) {
+ super(name2, version2);
+ this.window = null;
+ this.windowID = "bm-window-credits";
+ this.windowParent = document.body;
+ }
+ /** Spawns a Credits window.
+ * If another credits window already exists, we DON'T spawn another!
+ * Parent/child relationships in the DOM structure below are indicated by indentation.
+ * @since 0.90.9
+ */
+ buildWindow() {
+ const ascii = `
+\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
+\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
+\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
+\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
+\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
+\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
+
+\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
+\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
+\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557
+\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D
+\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
+\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
+`;
+ if (document.querySelector(`#${this.windowID}`)) {
+ document.querySelector(`#${this.windowID}`).remove();
+ return;
+ }
+ this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => {
+ }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Credits"', "data-button-status": "expanded" }, (instance, button) => {
+ button.onclick = () => instance.handleMinimization(button);
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().addDiv().buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Credits"' }, (instance, button) => {
+ button.onclick = () => {
+ document.querySelector(`#${this.windowID}`)?.remove();
+ };
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Credits" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addSpan({ "role": "img", "aria-label": this.name }).addSpan({ "innerHTML": ascii, "class": "bm-ascii", "aria-hidden": "true" }).buildElement().buildElement().addBr().buildElement().addHr().buildElement().addBr().buildElement().addSpan({ "textContent": '"Blue Marble" userscript is made by SwingTheVine.' }).buildElement().addBr().buildElement().addSpan({ "innerHTML": 'The Blue Marble Website is made by crqch.' }).buildElement().addBr().buildElement().addSpan({ "textContent": `The Blue Marble Website used until ${localizeDate(new Date(1756069320 * 1e3))} was made by Camille Daguin.` }).buildElement().addBr().buildElement().addSpan({ "textContent": 'The favicon "Blue Marble" is owned by NASA. (The image of the Earth is owned by NASA)' }).buildElement().addBr().buildElement().addSpan({ "textContent": "Special Thanks:" }).buildElement().addUl().addLi({ "textContent": "Espresso, Meqa, and Robot for moderating SwingTheVine's community." }).buildElement().addLi({ "innerHTML": 'nof, darkness for creating similar userscripts!' }).buildElement().addLi({ "innerHTML": 'Wonda for the Blue Marble banner image!' }).buildElement().addLi({ "innerHTML": 'BullStein, allanf181 for being early beta testers!' }).buildElement().addLi({ "innerHTML": 'guidu_ and Nick-machado for the original "Minimize" Button code!' }).buildElement().addLi({ "innerHTML": 'Nomad and Gustav for the tutorials!' }).buildElement().addLi({ "innerHTML": 'cfp for creating the template overlay that Blue Marble was based on!' }).buildElement().addLi({ "innerHTML": 'Force Network for hosting the telemetry server!' }).buildElement().addLi({ "innerHTML": 'TheBlueCorner for getting me interested in online pixel canvases!' }).buildElement().buildElement().addBr().buildElement().addSpan({ "innerHTML": 'Donators:' }).buildElement().addUl().addLi({ "textContent": "Espresso" }).buildElement().addLi({ "textContent": "BEST FAN" }).buildElement().addLi({ "textContent": "FuchsDresden" }).buildElement().addLi({ "textContent": "Jack" }).buildElement().addLi({ "textContent": "raiken_au" }).buildElement().addLi({ "textContent": "Jacob" }).buildElement().addLi({ "textContent": "StupidOne" }).buildElement().addLi({ "textContent": "2 Anonymous Supporters" }).buildElement().buildElement().buildElement().buildElement().buildElement().buildOverlay(this.windowParent);
+ this.handleDrag(`#${this.windowID}.bm-window`, `#${this.windowID} .bm-dragbar`);
+ }
+ };
+
+ // src/WindowFilter.js
+ var _WindowFilter_instances, buildColorList_fn, sortColorList_fn, selectColorList_fn, calculatePixelStatistics_fn;
+ var WindowFilter = class extends Overlay {
+ /** Constructor for the color filter window
+ * @param {*} executor - The executing class
+ * @since 0.88.329
+ * @see {@link Overlay#constructor}
+ */
+ constructor(executor) {
+ super(executor.name, executor.version);
+ __privateAdd(this, _WindowFilter_instances);
+ this.window = null;
+ this.windowID = "bm-window-filter";
+ this.colorListID = "bm-filter-flex";
+ this.windowParent = document.body;
+ this.templateManager = executor.apiManager?.templateManager;
+ this.eyeOpen = '';
+ this.eyeClosed = '';
+ const { palette, LUT: _ } = this.templateManager.paletteBM;
+ this.palette = palette;
+ this.tilesLoadedTotal = 0;
+ this.tilesTotal = 0;
+ this.allPixelsColor = /* @__PURE__ */ new Map();
+ this.allPixelsCorrect = /* @__PURE__ */ new Map();
+ this.allPixelsCorrectTotal = 0;
+ this.allPixelsTotal = 0;
+ this.timeRemaining = 0;
+ this.timeRemainingLocalized = "";
+ this.sortPrimary = "id";
+ this.sortSecondary = "ascending";
+ this.showUnused = false;
+ }
+ /** Spawns a Color Filter window.
+ * If another color filter window already exists, we DON'T spawn another!
+ * Parent/child relationships in the DOM structure below are indicated by indentation.
+ * @since 0.88.149
+ */
+ buildWindow() {
+ if (document.querySelector(`#${this.windowID}`)) {
+ document.querySelector(`#${this.windowID}`).remove();
+ return;
+ }
+ this.window = this.addDiv({ "id": this.windowID, "class": "bm-window" }, (instance, div) => {
+ }).addDragbar().addButton({ "class": "bm-button-circle", "textContent": "\u25BC", "aria-label": 'Minimize window "Color Filter"', "data-button-status": "expanded" }, (instance, button) => {
+ button.onclick = () => instance.handleMinimization(button);
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().addDiv().buildElement().addDiv({ "class": "bm-flex-center" }).addButton({ "class": "bm-button-circle", "textContent": "\u{1F5D7}", "aria-label": 'Switch to windowed mode for "Color Filter"' }, (instance, button) => {
+ button.onclick = () => {
+ document.querySelector(`#${this.windowID}`)?.remove();
+ this.buildWindowed();
+ };
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().addButton({ "class": "bm-button-circle", "textContent": "\u2716", "aria-label": 'Close window "Color Filter"' }, (instance, button) => {
+ button.onclick = () => {
+ document.querySelector(`#${this.windowID}`)?.remove();
+ };
+ button.ontouchend = () => {
+ button.click();
+ };
+ }).buildElement().buildElement().buildElement().addDiv({ "class": "bm-window-content" }).addDiv({ "class": "bm-container bm-center-vertically" }).addHeader(1, { "textContent": "Color Filter" }).buildElement().buildElement().addHr().buildElement().addDiv({ "class": "bm-container bm-flex-between bm-center-vertically", "style": "gap: 1.5ch;" }).addButton({ "textContent": "Hide All Colors" }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, false);
+ }).buildElement().addButton({ "textContent": "Refresh Data" }, (instance, button) => {
+ button.onclick = () => {
+ button.disabled = true;
+ this.updateColorList();
+ button.disabled = false;
+ };
+ }).buildElement().addButton({ "textContent": "Show All Colors" }, (instance, button) => {
+ button.onclick = () => __privateMethod(this, _WindowFilter_instances, selectColorList_fn).call(this, true);
+ }).buildElement().buildElement().addDiv({ "class": "bm-container bm-scrollable" }).addDiv({ "class": "bm-container", "style": "margin-left: 2.5ch; margin-right: 2.5ch;" }).addDiv({ "class": "bm-container" }).addSpan({ "id": "bm-filter-tile-load", "innerHTML": "Tiles Loaded: 0 / ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-correct", "innerHTML": "Correct Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-total", "innerHTML": "Total Pixels: ???" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-remaining", "innerHTML": "Complete: ??? (???)" }).buildElement().addBr().buildElement().addSpan({ "id": "bm-filter-tot-completed", "innerHTML": "??? ???" }).buildElement().buildElement().addDiv({ "class": "bm-container" }).addP({ "innerHTML": `Press the \u{1F5D7} button to make this window smaller. Colors with the icon ${this.eyeOpen.replace("