Last active
May 11, 2025 10:56
-
-
Save yankooliveira/d12a6e1a20930b485b687d8847f380d1 to your computer and use it in GitHub Desktop.
Shadertoy Channel Uploader bookmarklet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Shadertoy is awesome but it's hard to set your own textures. This adds a small bar to the top that lets you | |
| // upload images to each channel. | |
| // | |
| // Installation: create a new bookmark and copy the following code into the `url` field: | |
| javascript:(function(){const s=[{filter:'mipmap',wrap:'clamp',vflip:'true',srgb:'true',internal:'byte'},{filter:'linear',wrap:'clamp',vflip:'true',srgb:'false',internal:'byte'},{filter:'nearest',wrap:'clamp',vflip:'true',srgb:'false',internal:'byte'},{filter:'mipmap',wrap:'clamp',vflip:'true',srgb:'true',internal:'byte'}];const N=4;if(typeof gShaderToy==='undefined'){console.error("ShaderToy environment (gShaderToy) not found.");alert("ShaderToy environment (gShaderToy) not found. Ensure you are on a ShaderToy page.");return;}let ec=document.getElementById('customUploadControls');if(ec){ec.remove();}let ef=document.getElementById('customFileInput');if(ef){ef.remove();}const u=document.createElement('div');u.style.cssText='padding:10px;background-color:#2a2a2a;border-bottom:1px solid #444;display:flex;gap:10px;align-items:center;';u.id='customUploadControls';const h=document.querySelector('#header');if(h){h.parentNode.insertBefore(u,h.nextSibling);}else{document.body.insertBefore(u,document.body.firstChild);}const f=document.createElement('input');f.type='file';f.accept='image/png, image/jpeg, image/jpg';f.style.display='none';f.id='customFileInput';let t=0;f.onchange=(e)=>{if(e.target.files&&e.target.files[0]){const l=e.target.files[0];const r=new FileReader();const c=t;r.onload=(v)=>{const d=v.target.result;console.log(`Read file for iChannel${c}, size: ${d.length} bytes`);try{let i=c+1;if(gShaderToy.mEffect&&gShaderToy.mEffect.mPasses&&gShaderToy.mEffect.mPasses[0]&&gShaderToy.mEffect.mPasses[0].mInputs[c]){i=gShaderToy.mEffect.mPasses[0].mInputs[c].mInfo.mID;console.log(`Using existing texture ID ${i} for iChannel${c}`);}else{console.warn(`Could not get existing texture ID for iChannel${c}, using fallback ID ${i}.`);}const o={mSrc:d,mType:'texture',mID:i,mSampler:s[c]||s[0]};console.log(`Calling gShaderToy.SetTexture(${c}, ...)`,o);gShaderToy.SetTexture(c,o);console.log(`Texture set for iChannel${c}.`);}catch(x){console.error(`Error setting texture for iChannel${c}:`,x);alert(`Error setting texture for iChannel${c}: ${x.message}`);}};r.onerror=(x)=>{console.error("FileReader error:",x);alert("Error reading file.");};r.readAsDataURL(l);}e.target.value=null;};document.body.appendChild(f);for(let i=0;i<N;i++){const b=document.createElement('button');b.textContent=`Upload iChannel${i}`;b.style.cssText='padding:5px 10px;cursor:pointer;background-color:#444;color:#eee;border:1px solid #666;border-radius:3px;';b.onclick=()=>{t=i;f.click();};u.appendChild(b);}console.log(`Custom upload buttons added for iChannel0-${N-1}.`);})(); | |
| // Here's the full, un-minified version: | |
| (function() { | |
| // --- Configuration --- | |
| const samplerSettings = [ | |
| // Channel 0 (Color) - Assume sRGB, use mipmaps, clamp, vflip | |
| { filter: 'mipmap', wrap: 'clamp', vflip: 'true', srgb: 'true', internal: 'byte' }, | |
| // Channel 1 (Normal) - Not sRGB, linear filtering, clamp, vflip | |
| { filter: 'linear', wrap: 'clamp', vflip: 'true', srgb: 'false', internal: 'byte' }, | |
| // Channel 2 (Depth) - Not sRGB, nearest filtering often best, clamp, vflip | |
| { filter: 'nearest', wrap: 'clamp', vflip: 'true', srgb: 'false', internal: 'byte' }, | |
| // Channel 3 (e.g., Aux/Data/Another Color) - Assume sRGB, mipmaps, clamp, vflip (like Channel 0) | |
| { filter: 'mipmap', wrap: 'clamp', vflip: 'true', srgb: 'true', internal: 'byte' } | |
| ]; | |
| const NUM_CHANNELS = 4; // Explicitly 4 channels | |
| // --- End Configuration --- | |
| if (typeof gShaderToy === 'undefined') { | |
| console.error("ShaderToy environment (gShaderToy) not found. Are you on a ShaderToy page?"); | |
| alert("ShaderToy environment (gShaderToy) not found. Ensure you are on a ShaderToy page."); | |
| return; | |
| } | |
| // Remove existing controls if they exist (for re-running the bookmarklet) | |
| const existingControls = document.getElementById('customUploadControls'); | |
| if (existingControls) { | |
| existingControls.remove(); | |
| } | |
| const existingFileInput = document.getElementById('customFileInput'); | |
| if (existingFileInput) { | |
| existingFileInput.remove(); | |
| } | |
| // Create a container for the buttons | |
| const uploadContainer = document.createElement('div'); | |
| uploadContainer.style.padding = '10px'; | |
| uploadContainer.style.backgroundColor = '#2a2a2a'; | |
| uploadContainer.style.borderBottom = '1px solid #444'; | |
| uploadContainer.style.display = 'flex'; | |
| uploadContainer.style.gap = '10px'; | |
| uploadContainer.style.alignItems = 'center'; | |
| uploadContainer.id = 'customUploadControls'; | |
| // Inject the container near the top | |
| const header = document.querySelector('#header'); | |
| if (header) { | |
| header.parentNode.insertBefore(uploadContainer, header.nextSibling); | |
| } else { | |
| document.body.insertBefore(uploadContainer, document.body.firstChild); // Fallback | |
| } | |
| // Create a hidden file input element | |
| const fileInput = document.createElement('input'); | |
| fileInput.type = 'file'; | |
| fileInput.accept = 'image/png, image/jpeg, image/jpg'; | |
| fileInput.style.display = 'none'; | |
| fileInput.id = 'customFileInput'; // Add an ID for potential removal | |
| // Store the target channel index when a button is clicked | |
| let targetChannel = 0; | |
| // Function to handle file selection | |
| fileInput.onchange = (event) => { | |
| if (event.target.files && event.target.files[0]) { | |
| const file = event.target.files[0]; | |
| const reader = new FileReader(); | |
| const currentChannel = targetChannel; // Capture channel index for the closure | |
| reader.onload = (e) => { | |
| const base64DataUrl = e.target.result; | |
| console.log(`Read file for iChannel${currentChannel}, size: ${base64DataUrl.length} bytes`); | |
| try { | |
| let textureId = currentChannel + 1; // Fallback ID | |
| if (gShaderToy.mEffect && gShaderToy.mEffect.mPasses && gShaderToy.mEffect.mPasses[0] && gShaderToy.mEffect.mPasses[0].mInputs[currentChannel]) { | |
| textureId = gShaderToy.mEffect.mPasses[0].mInputs[currentChannel].mInfo.mID; | |
| console.log(`Using existing texture ID ${textureId} for iChannel${currentChannel}`); | |
| } else { | |
| console.warn(`Could not get existing texture ID for iChannel${currentChannel}, using fallback ID ${textureId}. This might cause issues.`); | |
| } | |
| const textureOptions = { | |
| mSrc: base64DataUrl, | |
| mType: 'texture', | |
| mID: textureId, | |
| mSampler: samplerSettings[currentChannel] || samplerSettings[0] // Use specific or default | |
| }; | |
| console.log(`Calling gShaderToy.SetTexture(${currentChannel}, ...)`, textureOptions); | |
| gShaderToy.SetTexture(currentChannel, textureOptions); | |
| console.log(`Texture hopefully set for iChannel${currentChannel}.`); | |
| // gShaderToy.Compile(); // Uncomment if needed, but often SetTexture is enough | |
| } catch (error) { | |
| console.error(`Error setting texture for iChannel${currentChannel}:`, error); | |
| alert(`Error setting texture for iChannel${currentChannel}: ${error.message}`); | |
| } | |
| }; | |
| reader.onerror = (e) => { | |
| console.error("FileReader error:", e); | |
| alert("Error reading file."); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| event.target.value = null; // Reset file input | |
| }; | |
| document.body.appendChild(fileInput); | |
| // Create upload buttons for channels 0, 1, 2, 3 | |
| for (let i = 0; i < NUM_CHANNELS; i++) { | |
| const button = document.createElement('button'); | |
| button.textContent = `Upload iChannel${i}`; | |
| button.style.padding = '5px 10px'; | |
| button.style.cursor = 'pointer'; | |
| button.style.backgroundColor = '#444'; | |
| button.style.color = '#eee'; | |
| button.style.border = '1px solid #666'; | |
| button.style.borderRadius = '3px'; | |
| button.onclick = () => { | |
| targetChannel = i; | |
| fileInput.click(); | |
| }; | |
| uploadContainer.appendChild(button); | |
| } | |
| console.log(`Custom upload buttons added for iChannel0-${NUM_CHANNELS-1}.`); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment