From 93fa5a3a919296d530f7e4af1d43f6050eb7cbfc Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 28 Jan 2024 00:27:39 -0600 Subject: [PATCH] TheShovel/CanvasEffects: separate x & y scale, add border, add change effect (#1262) Co-authored-by: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> --- extensions/TheShovel/CanvasEffects.js | 197 ++++++++++++++++++++------ 1 file changed, 157 insertions(+), 40 deletions(-) diff --git a/extensions/TheShovel/CanvasEffects.js b/extensions/TheShovel/CanvasEffects.js index 4bbd31ef46..ec2e736f0a 100644 --- a/extensions/TheShovel/CanvasEffects.js +++ b/extensions/TheShovel/CanvasEffects.js @@ -2,6 +2,7 @@ // ID: theshovelcanvaseffects // Description: Apply visual effects to the entire stage. // By: TheShovel +// By: SharkPool (function (Scratch) { "use strict"; @@ -13,7 +14,7 @@ const updateStyle = () => { // Gotta keep the translation to % because of the stage size, window size and so on - const transform = `rotate(${rotation}deg) scale(${scale}%) skew(${skewX}deg, ${skewY}deg) translate(${offsetX}%, ${ + const transform = `rotate(${rotation}deg) scale(${scaleX}%, ${scaleY}%) skew(${skewX}deg, ${skewY}deg) translate(${offsetX}%, ${ 0 - offsetY }%)`; if (canvas.style.transform !== transform) { @@ -27,15 +28,27 @@ if (canvas.style.filter !== filter) { canvas.style.filter = filter; } + const cssBorderRadius = borderRadius === 0 ? "" : `${borderRadius}%`; if (canvas.style.borderRadius !== cssBorderRadius) { canvas.style.borderRadius = cssBorderRadius; } + const imageRendering = resizeMode === "pixelated" ? "pixelated" : ""; if (canvas.style.imageRendering !== imageRendering) { canvas.style.imageRendering = imageRendering; } + + const border = `${borderWidth}px ${borderStyle} ${borderColor}`; + if (canvas.style.border !== border) { + canvas.style.border = border; + } + + if (canvas.style.backgroundColor !== backgroundColor) { + canvas.style.backgroundColor = backgroundColor; + } }; + // scratch-gui may reset canvas styles when resizing the window or going in/out of fullscreen new MutationObserver(updateStyle).observe(canvas, { attributeFilter: ["style"], @@ -48,7 +61,8 @@ let offsetX = 0; let skewY = 0; let skewX = 0; - let scale = 100; + let scaleX = 100; + let scaleY = 100; // Thanks SharkPool for telling me about these let transparency = 0; let sepia = 0; @@ -59,6 +73,10 @@ let brightness = 100; let invert = 0; let resizeMode = "default"; + let borderStyle = "solid"; + let borderWidth = 0; + let borderColor = "#000000"; + let backgroundColor = "transparent"; const resetStyles = () => { borderRadius = 0; @@ -67,7 +85,8 @@ offsetX = 0; skewY = 0; skewX = 0; - scale = 100; + scaleX = 100; + scaleY = 100; transparency = 0; sepia = 0; blur = 0; @@ -77,6 +96,10 @@ brightness = 100; invert = 0; resizeMode = "default"; + borderStyle = "solid"; + borderWidth = 0; + borderColor = "#000000"; + backgroundColor = "transparent"; updateStyle(); }; @@ -102,6 +125,21 @@ }, }, }, + { + opcode: "changeEffect", + blockType: Scratch.BlockType.COMMAND, + text: "change canvas [EFFECT] by [NUMBER]", + arguments: { + EFFECT: { + type: Scratch.ArgumentType.STRING, + menu: "EFFECTMENU", + }, + NUMBER: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 5, + }, + }, + }, { opcode: "geteffect", blockType: Scratch.BlockType.REPORTER, @@ -118,10 +156,34 @@ blockType: Scratch.BlockType.COMMAND, text: "clear canvas effects", }, + "---", + { + opcode: "setBorder", + blockType: Scratch.BlockType.COMMAND, + text: "set canvas border to [WIDTH] pixels [STYLE] with color [COLOR1] and background [COLOR2]", + arguments: { + STYLE: { + type: Scratch.ArgumentType.STRING, + menu: "borderTypes", + }, + WIDTH: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 5, + }, + COLOR1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#ff0000", + }, + COLOR2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#0000ff", + }, + }, + }, { opcode: "renderscale", blockType: Scratch.BlockType.COMMAND, - text: "set canvas render size to width:[X] height:[Y]", + text: "set canvas render size to width: [X] height: [Y]", arguments: { X: { type: Scratch.ArgumentType.NUMBER, @@ -148,23 +210,7 @@ menus: { EFFECTMENU: { acceptReporters: true, - items: [ - "blur", - "contrast", - "saturation", - "color shift", - "brightness", - "invert", - "sepia", - "transparency", - "scale", - "skew X", - "skew Y", - "offset X", - "offset Y", - "rotation", - "border radius", - ], + items: this._getMenuItems(false), }, RENDERMODE: { acceptReporters: true, @@ -172,29 +218,52 @@ }, EFFECTGETMENU: { acceptReporters: true, - // this contains 'resize rendering mode', EFFECTMENU does not + items: this._getMenuItems(true), + }, + borderTypes: { + acceptReporters: true, items: [ - "blur", - "contrast", - "saturation", - "color shift", - "brightness", - "invert", - "resize rendering mode", - "sepia", - "transparency", - "scale", - "skew X", - "skew Y", - "offset X", - "offset Y", - "rotation", - "border radius", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "none", ], }, }, }; } + + _getMenuItems(isGetter) { + return [ + "blur", + "contrast", + "saturation", + "color shift", + "brightness", + "invert", + ...(isGetter ? ["resize rendering mode"] : []), + "sepia", + "transparency", + ...(isGetter ? [] : ["scale"]), + "scale X", + "scale Y", + "skew X", + "skew Y", + "offset X", + "offset Y", + "rotation", + "border radius", + ...(isGetter + ? ["border width", "border style", "border color", "background"] + : []), + ]; + } + geteffect({ EFFECT }) { if (EFFECT === "blur") { return blur; @@ -215,7 +284,12 @@ } else if (EFFECT === "transparency") { return transparency; } else if (EFFECT === "scale") { - return scale; + // old extension compatibility + return scaleX; + } else if (EFFECT === "scale X") { + return scaleX; + } else if (EFFECT === "scale Y") { + return scaleY; } else if (EFFECT === "skew X") { return skewX; } else if (EFFECT === "skew Y") { @@ -228,6 +302,14 @@ return rotation; } else if (EFFECT === "border radius") { return borderRadius; + } else if (EFFECT === "border width") { + return borderWidth; + } else if (EFFECT === "border style") { + return borderStyle; + } else if (EFFECT === "border color") { + return borderColor; + } else if (EFFECT === "background") { + return backgroundColor; } return ""; } @@ -250,7 +332,12 @@ } else if (EFFECT === "transparency") { transparency = NUMBER; } else if (EFFECT === "scale") { - scale = NUMBER; + scaleX = NUMBER; + scaleY = NUMBER; + } else if (EFFECT === "scale X") { + scaleX = NUMBER; + } else if (EFFECT === "scale Y") { + scaleY = NUMBER; } else if (EFFECT === "skew X") { skewX = NUMBER; } else if (EFFECT === "skew Y") { @@ -266,6 +353,23 @@ } updateStyle(); } + changeEffect(args) { + // Scale needs some special treatment to change x & y separately + if (args.EFFECT === "scale") { + scaleX = scaleX + Scratch.Cast.toNumber(args.NUMBER); + scaleY = scaleY + Scratch.Cast.toNumber(args.NUMBER); + updateStyle(); + return; + } + + // Everything else is really generic + const currentEffect = Scratch.Cast.toNumber(this.geteffect(args)); + const newValue = Scratch.Cast.toNumber(args.NUMBER) + currentEffect; + this.seteffect({ + EFFECT: args.EFFECT, + NUMBER: newValue, + }); + } cleareffects() { resetStyles(); } @@ -276,6 +380,19 @@ renderscale({ X, Y }) { Scratch.vm.renderer.resize(X, Y); } + setBorder(args) { + borderWidth = Scratch.Cast.toNumber(args.WIDTH); + borderStyle = Scratch.Cast.toString(args.STYLE).replace(/[^a-z]/gi, ""); + borderColor = Scratch.Cast.toString(args.COLOR1).replace( + /[^#0-9a-z]/gi, + "" + ); + backgroundColor = Scratch.Cast.toString(args.COLOR2).replace( + /[^#0-9a-z]/gi, + "" + ); + updateStyle(); + } } Scratch.extensions.register(new CanvasEffects()); })(Scratch);