Discuss Scratch
- Discussion Forums
 - » Developing Scratch Extensions
 - » Stage effects
        
         
- Umhead22
 - 
                            
						
						
                            Scratcher
                        
						
						 
100+ posts
Stage effects
I've made stage extension, it works but the problem is that pielation doesn't work so i removed it from menus. I need help with pixelation effect
It adds two blocks:
My browser / operating system: Windows NT 10.0, Chrome 141.0.0.0, No Flash versions detected
                        
                        
                    // you can use this extension however you like, credits aren't required. // simple extension for applying visual effects to the Scratch stage // Pixelated effect is implemented via overlay canvas as CSS filter doesn't support it, it crashes extension. // all credits goes to me, Umhead. // all credit goes to Umhead for creating this extension. (aka me) // version 0.1 2025.10.29 (function (Scratch) { 'use strict'; if (!Scratch.extensions || !Scratch.extensions.unsandboxed) { alert("Extension MUST be unsandboxed to work properly. Please enable 'Unsandboxed extensions' before loading it"); throw new Error('Unsandboxed mode required.'); } const vm = Scratch.vm; const renderer = vm && vm.renderer; let stageCanvas = null; let usedCssFilter = false; // Overlay fallback vars let overlayCanvas = null; let overlayCtx = null; let overlayTempCanvas = null; // offscreen temp canvas for pixelation, not working with main overlay. let overlayTempCtx = null; let currentMode = 'none'; // 'css', 'overlay' let rafId = null; let activeEffects = []; function log(...args) { console.log('[StageFX]', ...args); } function findStageCanvas() { try { if (renderer && renderer.canvas) return renderer.canvas; const candidates = Array.from(document.querySelectorAll('canvas')); if (!candidates.length) return null; if (candidates.length === 1) return candidates[0]; for (const c of candidates) { const r = c.getBoundingClientRect(); if (r.width >= 200 && r.height >= 120) return c; } return candidates[0]; } catch (e) { log('Error finding stage canvas:', e); return null; } } function composeCssFilterFromActive() { return activeEffects .filter(e => e.name !== 'pixelate') .map(e => { switch (e.name) { case 'blur': return `blur(${e.value}px)`; case 'brightness': return `brightness(${e.value}%)`; case 'contrast': return `contrast(${e.value}%)`; case 'grayscale': return `grayscale(${e.value}%)`; case 'sepia': return `sepia(${e.value}%)`; case 'invert': return `invert(${e.value}%)`; case 'hue-rotate': return `hue-rotate(${e.value}deg)`; case 'saturate': return `saturate(${e.value}%)`; default: return ''; } }) .filter(Boolean) .join(' '); } function applyCssFilterToCanvas(filter) { try { stageCanvas = findStageCanvas(); if (!stageCanvas) { log('No stage canvas found for CSS filter.'); return false; } stageCanvas.style.filter = filter; stageCanvas.style.pointerEvents = 'auto'; usedCssFilter = true; currentMode = 'css'; log('Applied CSS filter to stage canvas:', filter); return true; } catch (e) { log('CSS filter failed:', e); return false; } } function clearCssFilterFromCanvas() { try { stageCanvas = findStageCanvas(); if (stageCanvas) { stageCanvas.style.filter = ''; log('Cleared CSS filter from canvas'); } } catch (e) { log('Error clearing CSS filter:', e); } usedCssFilter = false; } function setupOverlay() { try { stageCanvas = findStageCanvas(); if (!stageCanvas) { log('No stage canvas found for overlay fallback.'); return false; } if (!overlayCanvas) { overlayCanvas = document.createElement('canvas'); overlayCanvas.style.position = 'fixed'; overlayCanvas.style.zIndex = 999999; overlayCanvas.style.pointerEvents = 'none'; overlayCanvas.style.imageRendering = 'pixelated'; document.body.appendChild(overlayCanvas); overlayCtx = overlayCanvas.getContext('2d'); overlayCtx.imageSmoothingEnabled = false; log('Overlay created'); } updateOverlayPositionAndSize(); return true; } catch (e) { log('setupOverlay error:', e); return false; } } function updateOverlayPositionAndSize() { if (!overlayCanvas) return; stageCanvas = findStageCanvas(); if (!stageCanvas) return; const rect = stageCanvas.getBoundingClientRect(); overlayCanvas.style.left = rect.left + 'px'; overlayCanvas.style.top = rect.top + 'px'; overlayCanvas.style.width = rect.width + 'px'; overlayCanvas.style.height = rect.height + 'px'; overlayCanvas.width = stageCanvas.width || Math.round(rect.width); overlayCanvas.height = stageCanvas.height || Math.round(rect.height); } function drawOverlayFilter() { if (!overlayCtx || !stageCanvas) return; try { overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); const hasPixel = activeEffects.some(e => e.name === 'pixelate'); if (hasPixel) { const eff = activeEffects.find(e => e.name === 'pixelate'); const scale = Math.max(1, Math.round(eff.value) || 4); const w = Math.max(1, Math.floor(overlayCanvas.width / scale)); const h = Math.max(1, Math.floor(overlayCanvas.height / scale)); if (!overlayTempCanvas) { overlayTempCanvas = document.createElement('canvas'); overlayTempCtx = overlayTempCanvas.getContext('2d'); } if (overlayTempCanvas.width !== w || overlayTempCanvas.height !== h) { overlayTempCanvas.width = w; overlayTempCanvas.height = h; } overlayTempCtx.imageSmoothingEnabled = false; overlayTempCtx.clearRect(0, 0, w, h); overlayTempCtx.drawImage(stageCanvas, 0, 0, stageCanvas.width || overlayCanvas.width, stageCanvas.height || overlayCanvas.height, 0, 0, w, h); overlayCtx.imageSmoothingEnabled = false; overlayCtx.drawImage(overlayTempCanvas, 0, 0, w, h, 0, 0, overlayCanvas.width, overlayCanvas.height); return; } const filterString = activeEffects .filter(e => e.name !== 'pixelate') .map(e => { switch (e.name) { case 'blur': return `blur(${e.value}px)`; case 'brightness': return `brightness(${e.value}%)`; case 'contrast': return `contrast(${e.value}%)`; case 'grayscale': return `grayscale(${e.value}%)`; case 'sepia': return `sepia(${e.value}%)`; case 'invert': return `invert(${e.value}%)`; case 'hue-rotate': return `hue-rotate(${e.value}deg)`; case 'saturate': return `saturate(${e.value}%)`; default: return ''; } }) .filter(Boolean) .join(' '); overlayCtx.filter = filterString || 'none'; overlayCtx.imageSmoothingEnabled = true; overlayCtx.drawImage(stageCanvas, 0, 0, overlayCanvas.width, overlayCanvas.height); } catch (e) { log('drawOverlayFilter error:', e); } } function startOverlayLoop() { if (!setupOverlay()) return false; if (rafId) return true; const loop = () => { updateOverlayPositionAndSize(); drawOverlayFilter(); rafId = requestAnimationFrame(loop); }; rafId = requestAnimationFrame(loop); currentMode = 'overlay'; log('Overlay loop started'); return true; } function stopOverlayLoop() { if (rafId) cancelAnimationFrame(rafId); rafId = null; if (overlayCtx && overlayCanvas) overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); if (overlayCanvas && overlayCanvas.parentNode) overlayCanvas.parentNode.removeChild(overlayCanvas); overlayCanvas = null; overlayCtx = null; overlayTempCanvas = null; overlayTempCtx = null; currentMode = 'none'; log('Overlay stopped'); } // --- Auto-fix for disappearing effects --- function handleCanvasChange() { stageCanvas = findStageCanvas(); if (!stageCanvas) return; if (activeEffects.length > 0) { const css = composeCssFilterFromActive(); if (css) applyCssFilterToCanvas(css); else startOverlayLoop(); } if (overlayCanvas) updateOverlayPositionAndSize(); } window.addEventListener('resize', handleCanvasChange); window.addEventListener('scroll', handleCanvasChange, true); document.addEventListener('fullscreenchange', handleCanvasChange); const observer = new MutationObserver(() => { handleCanvasChange(); }); function startObservingStage() { const stageWrapper = document.querySelector('.stage-wrapper, .stage_canvas_wrapper, .stage'); if (stageWrapper) { observer.observe(stageWrapper, { childList: true, subtree: true }); log('Stage canvas observer attached.'); } else { setTimeout(startObservingStage, 1000); } } startObservingStage(); class StageEffects { getInfo() { return { id: 'stagefx', name: 'Stage Effects', color1: '#4b6ef1', blocks: [ { opcode: 'applyEffect', blockType: Scratch.BlockType.COMMAND, text: 'apply [EFFECT] strength [VALUE]', arguments: { EFFECT: { type: Scratch.ArgumentType.STRING, menu: 'effects' }, VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } } }, { opcode: 'clearEffects', blockType: Scratch.BlockType.COMMAND, text: 'clear effects' } ], menus: { effects: { acceptReporters: true, items: ['blur','brightness','contrast','grayscale','sepia','invert','hue-rotate','saturate'] } } }; } applyEffect(args) { const eff = (args.EFFECT || '').toString().toLowerCase(); const val = Number(args.VALUE) || 0; log('applyEffect requested:', eff, val); const name = eff === 'pixel' ? 'pixelate' : eff; const existingIndex = activeEffects.findIndex(e => e.name === name); if (existingIndex >= 0) activeEffects[existingIndex].value = val; else activeEffects.push({ name, value: val }); const hasPixel = activeEffects.some(e => e.name === 'pixelate'); const css = composeCssFilterFromActive(); if (hasPixel) { stageCanvas = findStageCanvas(); if (stageCanvas) stageCanvas.style.filter = ''; startOverlayLoop(); return; } if (css) { const ok = applyCssFilterToCanvas(css); if (ok) { if (overlayCanvas) stopOverlayLoop(); log('Using CSS filter path.'); return; } } startOverlayLoop(); } clearEffects() { activeEffects = []; clearCssFilterFromCanvas(); stopOverlayLoop(); log('clearEffects executed'); } } Scratch.extensions.register(new StageEffects()); log('Stage Effects extension loaded.'); })(Scratch);
apply (blur v) strenght (5) :: motion
clear effects :: motion
My browser / operating system: Windows NT 10.0, Chrome 141.0.0.0, No Flash versions detected
- Umhead22
 - 
                            
						
						
                            Scratcher
                        
						
						 
100+ posts
Stage effects
I don't expect you to view whole code, i expect to give small solution how i can make pixel effect
                        
                        
                    - Discussion Forums
 - » Developing Scratch Extensions
 - 
            » Stage effects 
         
            