diff --git a/imageMage.js b/imageMage.js new file mode 100644 index 0000000..f9cb653 --- /dev/null +++ b/imageMage.js @@ -0,0 +1,2046 @@ +var imageInput = document.getElementById('imageInput'); +var imageContainer = document.getElementById('imageContainer'); +var pixelColors = document.getElementById('pixelColors'); +var newImageContainer = document.getElementById('newImageContainer'); +var originalImage; +var clickXPosition; +var clickYPosition; +var visualizationChoiceMenu = document.getElementById('visualizationChoice'); +var visualizationChoice = visualizationChoiceMenu.value; +var previousVisualizationChoice = visualizationChoice; +var loadingScreen = document.getElementById("coverScreen"); + +var redInput = document.getElementById('red'); +var greenInput = document.getElementById('green'); +var blueInput = document.getElementById('blue'); +var alphaInput = document.getElementById('alpha'); + +var smearWidthInput = document.getElementById('smearWidth'); +var smearWidth = smearWidthInput.value; +var chosenPixelInput = document.getElementById('chosenPixel'); +var chosenPixel = chosenPixelInput.value; + +var noiseProbabilityInput = document.getElementById('noiseProbability'); +var noiseProbability = noiseProbabilityInput.value; + +var noiseColorRangeInput = document.getElementById('noiseColorRange'); +var noiseColorRange = noiseColorRangeInput.value; +var rgbColorRange = noiseColorRange/100 * 255; + +var dotSizeFactorInput = document.getElementById('dotSizeFactor'); +var dotSizeFactor = dotSizeFactorInput.value; + +var lightnessLevelInput = document.getElementById('lightnessLevel'); +var lightnessLevel = lightnessLevelInput.value; + +var saturationLevelInput = document.getElementById('saturationLevel'); +var saturationLevel = saturationLevelInput.value; + +var isImageLoaded = false; + +var redrawButton = document.getElementById('generate-button'); +redrawButton.addEventListener('click', refresh); + +var actualWidth; +var actualHeight; +var scaledWidth; +var scaledHeight; +var widthScalingRatio; + +var newCanvas = document.createElement('canvas'); +var newCtx = newCanvas.getContext('2d'); + +var pixelData; +var pixels; + +var redShift = redInput.value; +var greenShift = greenInput.value; +var blueShift = blueInput.value; +var alphaShift = alphaInput.value; + +var screenWidth = window.innerWidth; // get the width of the browser screen +var maxImageWidth = (screenWidth*0.96) / 2; // max width for each of the two images +var maxImageHeight = window.innerHeight * 0.78; +console.log("max image dimensions: "+maxImageWidth+", "+maxImageHeight); + +//color pickers +var paletteChoiceInput = document.getElementById('paletteChoice'); +var colorPicker = document.getElementById('color-picker'); +var colorPicker2 = document.getElementById('color-picker2'); +var colorPicker3 = document.getElementById('color-picker3'); +var colorPicker4 = document.getElementById('color-picker4'); +var colorPicker5 = document.getElementById('color-picker5'); +var colorPicker6 = document.getElementById('color-picker6'); +var pickers = [colorPicker, colorPicker2, colorPicker3, colorPicker4, colorPicker5, colorPicker6]; + +var backgroundColorInput = document.getElementById('backgroundColorInput'); +var backgroundColor = backgroundColorInput.value; + +var palettePresets = [ + {name: "mage", displayName: "Mage", palette: ["#0066A4","#640000","#006400","#FFC300","#FFFFFF","#000000"]}, + {name: "viridis", displayName: "Viridis", palette: ["#fde725","#7ad151","#22a884","#2a788e","#414487","#440154"]}, + {name: "analog", displayName: "Analog", palette: ["#d27575","#675a55","#529b9c","#9cba8f","#eac392","#FFFFFF"]}, + {name: "inferno", displayName: "Inferno", palette: ["#fcffa4","#fca50a","#dd513a","#932667","#420a68","#000004"]}, + {name: "vaporwave", displayName: "Vaporwave", palette: ["#D336BE","#E1A5EE","#05C3DD","#1E22AA","#D1EFED","#FFFFFF"]}, + {name: "bohemian", displayName: "Bohemian", palette: ["#3F2021","#B04A5A","#BA5B3F","#CB9576","#7FA0AC","#EEE5D3"]}, + {name: "earth", displayName: "Earth", palette: ["#8e412e","#ba6f4d","#e6cebc","#a2a182","#687259","#123524"]}, + {name: "primary", displayName: "Primary", palette: ["#c90000","#fff400","#0004ff","#ffffff","#ffffff","#000000"]}, + {name: "custom", displayName: "Custom >>", palette: ["#FFFFFF","#DDDDDD","#BBBBBB","#000000","#000000","#000000"]} +]; + +var chosenPalette = palettePresets[0].palette; + +//set as equal to mage palette upon first load, in RGB space +var chosenPaletteRGBValues = [ + [0, 102, 164], + [100, 0, 0], + [0, 100, 0], + [255, 195, 0], + [255, 255, 255], + [0, 0, 0] +]; + +//fill the paletteChoice HTML element dynamically +palettePresets.forEach((preset) => { + const option = document.createElement('option'); + option.value = preset.name; + option.text = preset.displayName; + paletteChoiceInput.appendChild(option); +}); + +var paletteChoice = paletteChoiceInput.value; + +//dual color picker +var dualColorPicker1 = document.getElementById('dualColorInput1'); +var dualColorPicker2 = document.getElementById('dualColorInput2'); + +var dualColor1 = dualColorPicker1.value; +var dualColor2 = dualColorPicker2.value; + +//Pop-up for grid visual style +var popup = document.querySelector('.popup'); + +// hide the popup when the user clicks on the image +popup.addEventListener('click', () => { + popup.style.display = 'none'; +}); + +var drawImageCounter = 0; +var gridLoadCounter = 0; +var ringsLoadCounter = 0; +var frontierLoadCounter = 0; +var eclipseLoadCounter = 0; + +//Save and export the new image in png format +var saveButton = document.getElementById('save-image-button'); + +saveButton.addEventListener('click', () => { + saveImage(); +}); + + +// Add event listeners to the input boxes +imageInput.addEventListener('change', readSourceImage); + +visualizationChoiceMenu.addEventListener('change',refresh); +redInput.addEventListener('change', refresh); +greenInput.addEventListener('change', refresh); +blueInput.addEventListener('change', refresh); +alphaInput.addEventListener('change', refresh); +smearWidthInput.addEventListener('change', refresh); +chosenPixelInput.addEventListener('change', refresh); +noiseProbabilityInput.addEventListener('change', refresh); +noiseColorRangeInput.addEventListener('change', refresh); +dotSizeFactorInput.addEventListener('change', refresh); + +paletteChoiceInput.addEventListener('change', changePalette); +dualColorPicker1.addEventListener('change', refresh); +dualColorPicker2.addEventListener('change', refresh); +lightnessLevelInput.addEventListener('change', refresh); +saturationLevelInput.addEventListener('change', refresh); + + +//main method +initPhotoCarousel(); +getUserInputs(); +initColorPickers(); +showDefaultImage(); + +// Grab new user inputs from control menu +function getUserInputs() { + + visualizationChoice = String(visualizationChoiceMenu.value); + + redShift = parseInt(redInput.value); + greenShift = parseInt(greenInput.value); + blueShift = parseInt(blueInput.value); + alphaShift = parseFloat(alphaInput.value); + + smearWidth = Math.min(100,Math.max(0,Number(smearWidthInput.value))); + chosenPixel = Math.min(100,Math.max(0,Number(chosenPixelInput.value))); + noiseProbability = Math.min(100,Math.max(0,Number(noiseProbabilityInput.value))); + noiseColorRange = Math.min(100,Math.max(0,Number(noiseColorRangeInput.value))); + dotSizeFactor = Math.min(100,Math.max(0,Number(dotSizeFactorInput.value))); + + rgbColorRange = noiseColorRange/100 * 255; + + dualColor1 = dualColorPicker1.value; + dualColor2 = dualColorPicker2.value; + + lightnessLevel = Math.min(100,Math.max(0,Number(lightnessLevelInput.value))); + saturationLevel = Math.min(100,Math.max(0,Number(saturationLevelInput.value))); + + //set background color + if(visualizationChoice == previousVisualizationChoice){ + backgroundColor = backgroundColorInput.value; + } else if(visualizationChoice == "eclipse"){ + backgroundColorInput.value = "#000000"; + backgroundColor = "#000000"; + } else { + backgroundColorInput.value = "#FFF9EB"; + backgroundColor = "#FFF9EB"; + } + + toggleInputMenu(); +} + +function toggleInputMenu(){ + + var numColumns = 12; + + //columns: Style, RGBA shift, Smear, Sensitivity, Color Range, Max Dot Size, Palette, Color pickers, Background, dual color picker + //Value of 1 if the columnn should be shown for that style, 0 if hidden + var menuControlFlags = [ + {menuOptions: [1,0,0,0,1,1,0,0,0,0,0,0], name: "pointillist"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "sketch"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "roller"}, + {menuOptions: [1,0,0,1,0,0,1,1,0,0,0,0], name: "palletize"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "pixel"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,0,0,0], name: "clippings"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "grid"}, + {menuOptions: [1,0,0,1,0,0,1,1,0,0,0,0], name: "mondrian"}, + {menuOptions: [1,0,0,1,0,1,0,0,1,0,0,0], name: "rings"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,0,0,0], name: "gumball"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "noisySort"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "void"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,0,0,0], name: "braille"}, + {menuOptions: [1,0,0,1,0,0,1,1,0,0,0,0], name: "dust"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,1,0,0], name: "outlines"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,0,0,0], name: "frontier"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,0,0,0], name: "eclipse"}, + {menuOptions: [1,0,0,0,0,0,0,0,1,0,1,1], name: "satLight"}, + {menuOptions: [1,0,0,1,0,0,0,0,1,1,0,0], name: "edgy"}, + {menuOptions: [1,0,0,1,0,0,0,0,0,0,0,0], name: "shadow"}, + ]; + + var styleIndex = menuControlFlags.findIndex(obj => obj.name == visualizationChoice); + + for(var idx=0; idx { + if(menuControlFlags[styleIndex].menuOptions[idx] == 1){ + element.classList.remove('hidden'); + } else { + element.classList.add('hidden'); + } + }); + } + +} + +function showDefaultImage() { + + var defaultImage = new Image(); + defaultImage.src = 'images/HK2024.jpg'; + + defaultImage.onload = () => { + + actualWidth = defaultImage.width; + actualHeight = defaultImage.height; + + //adjust for max width + if(actualWidth >= maxImageWidth){ + scaledWidth = maxImageWidth; + } else{ + scaledWidth = Math.min(maxImageWidth,actualWidth*1.0); + } + + widthScalingRatio = scaledWidth / actualWidth; + scaledHeight = actualHeight * widthScalingRatio; + + //adjust for max height + if(scaledHeight > maxImageHeight){ + scaledWidth = (maxImageHeight / scaledHeight) * scaledWidth; + widthScalingRatio = scaledWidth / actualWidth; + scaledHeight = actualHeight * widthScalingRatio; + } + + newCanvas = document.createElement('canvas'); + newCtx = newCanvas.getContext('2d'); + + newCanvas.width = actualWidth; + newCanvas.height = actualHeight; + + newCtx.drawImage(defaultImage, 0, 0); + + const newImageData = newCanvas.toDataURL(); + const newImage = new Image(); + newImage.src = newImageData; + newImage.style.width = `${scaledWidth}px`; + imageContainer.appendChild(newImage); + + var img = imageContainer.querySelector('img'); + + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = actualWidth; + canvas.height = actualHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + pixelData = ctx.getImageData(0, 0, actualWidth, actualHeight); + pixels = pixelData.data; + isImageLoaded = true; + drawNewImage(); + + img.addEventListener('click', (e) => { + clickXPosition = e.offsetX / widthScalingRatio; + clickYPosition = e.offsetY / widthScalingRatio; + console.log(`Clicked at (${clickXPosition}, ${clickYPosition})`); + if(visualizationChoice=="grid" || visualizationChoice=="rings" || visualizationChoice=="frontier"){ + drawNewImage(); + } + }); + + window.scrollTo(0, 0); + } + + } +} + +function readSourceImage(){ + +//remove any existing images +while (imageContainer.firstChild) { + imageContainer.removeChild(imageContainer.firstChild); +} + +while (newImageContainer.firstChild) { + newImageContainer.removeChild(newImageContainer.firstChild); +} + +//read image file + var file = imageInput.files[0]; + var reader = new FileReader(); + reader.onload = (event) => { + var imageData = event.target.result; + var image = new Image(); + image.src = imageData; + image.onload = () => { + + actualWidth = image.width; + actualHeight = image.height; + + //adjust for max width + if(actualWidth >= maxImageWidth){ + scaledWidth = maxImageWidth; + } else{ + scaledWidth = Math.min(maxImageWidth,actualWidth*2); + } + + widthScalingRatio = scaledWidth / actualWidth; + scaledHeight = actualHeight * widthScalingRatio; + + //adjust for max height + if(scaledHeight > maxImageHeight){ + scaledWidth = (maxImageHeight / scaledHeight) * scaledWidth; + widthScalingRatio = scaledWidth / actualWidth; + scaledHeight = actualHeight * widthScalingRatio; + } + + var originalImg = document.createElement('img'); + originalImg.src = imageData; + originalImg.width = scaledWidth; + originalImg.height = scaledHeight; + imageContainer.appendChild(originalImg); + + // Get the pixel colors + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = actualWidth; + canvas.height = actualHeight; + ctx.drawImage(image, 0, 0); + pixelData = ctx.getImageData(0, 0, actualWidth, actualHeight); + pixels = pixelData.data; + + //add click position event listener + originalImg.addEventListener('click', (e) => { + clickXPosition = e.offsetX / widthScalingRatio; + clickYPosition = e.offsetY / widthScalingRatio; + console.log(`Clicked at (${clickXPosition}, ${clickYPosition})`); + if(visualizationChoice=="grid" || visualizationChoice=="rings" || visualizationChoice=="frontier"){ + drawNewImage(); + } + }); + + refresh(); + + }; + }; + reader.readAsDataURL(file); + + isImageLoaded = true; + +} + +function refresh(){ + + console.log("refresh"); + + //show the loading screen + loadingScreen.classList.remove("hidden"); + loadingScreen.classList.add("lockOn"); + + getUserInputs(); + setTimeout(drawNewImage,5); + +} + +function drawNewImage(){ + + if (!isImageLoaded) { + //hide the loading screen + loadingScreen.classList.remove("lockOn"); + loadingScreen.classList.add("hidden"); + return; // exit the function if isImageLoaded is false + } + + //remove any existing new images + while (newImageContainer.firstChild) { + newImageContainer.removeChild(newImageContainer.firstChild); + } + + originalImage = imageContainer.querySelector('img'); + + // Create a new image + newCanvas = document.createElement('canvas'); + newCtx = newCanvas.getContext('2d'); + + newCanvas.width = actualWidth; + newCanvas.height = actualHeight; + + //set background color of new canvas + newCtx.fillStyle = backgroundColor; + newCtx.fillRect(0, 0, actualWidth, actualHeight); + + console.log("actual width: "+actualWidth); + console.log("actual height: "+actualHeight); + + drawImageCounter++; + + if(visualizationChoice == "rgba"){ + console.log("running rgba visual"); + for (let j = 0; j < pixels.length; j += 4) { + const newRed = pixels[j] * (redShift/100); + const newGreen = pixels[j + 1] * (greenShift/100); + const newBlue = pixels[j + 2] * (blueShift/100); + const newAlpha = pixels[j + 3] * (alphaShift/100); + newCtx.fillStyle = `rgba(${newRed}, ${newGreen}, ${newBlue}, ${newAlpha})`; + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), 1, 1); + } + } else if(visualizationChoice == "smear"){ + console.log("running smear visual"); + for (let j = 0; j < pixels.length; j += 4) { + var currentColNum = j / 4 % actualWidth; + var currentRowNum = Math.floor(j / 4 / actualWidth); + var currentRightPixel = (Math.floor(actualWidth * chosenPixel/100) + (actualWidth*currentRowNum))-1; + + var newRed = pixels[currentRightPixel*4]; + var newGreen = pixels[currentRightPixel*4+1]; + var newBlue = pixels[currentRightPixel*4+2]; + var newAlpha = 1; + if(currentColNum < (actualWidth * smearWidth/100)){ + newAlpha = pixels[currentRightPixel*4+3]; + } else { + newAlpha = 0; + } + newCtx.fillStyle = `rgba(${newRed}, ${newGreen}, ${newBlue}, ${newAlpha})`; + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), 1, 1); + } + } else if(visualizationChoice == "roller"){ + console.log("running roller visual"); + + //faithful reproduction + newCtx.drawImage(originalImage, 0, 0); + + //smear effect + var numSmears = 4; + + for(var smearCounter=0; smearCounter noiseProbability/100){ + //newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + } else { + newCtx.fillStyle = `rgba(${newRed}, ${newGreen}, ${newBlue}, ${newAlpha})`; + } + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), 1, 1); + } + + } else if(visualizationChoice == "perlin2"){ + console.log("running perlin2 visual"); + generatePerlinNoise(); + + for (let j = 0; j < pixels.length; j += 4) { + + var pixelX = j / 4 % actualWidth; + var pixelY = Math.floor(j / 4 / actualWidth); + + var perlinXGridSize = actualWidth / dataWidth; + var perlinYGridSize = actualHeight / dataHeight; + + var perlinX = Math.floor(pixelX / perlinXGridSize); + var perlinY = Math.floor(pixelY / perlinYGridSize); + + var perlinDataValue = perlinDataArray[perlinY][perlinX]; + + var red = pixels[j]; + var green = pixels[j + 1]; + var blue = pixels[j + 2]; + var alpha = pixels[j + 3]; + + var randomRed = chosenColorR - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomGreen = chosenColorG - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomBlue = chosenColorB - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomAlpha = 1; + + if((Math.pow(perlinDataValue,2.5) * Math.random()) < ((noiseProbability/100) * 0.025) || ((((100 - noiseProbability)/100) * Math.random() * Math.pow(perlinDataValue,2)) < 0.005)){ + newCtx.fillStyle = `rgba(${randomRed}, ${randomGreen}, ${randomBlue}, ${randomAlpha})`; + + } else { + newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + } + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), 1, 1); + } + + } else if(visualizationChoice == "perlin3"){ + console.log("running perlin3 visual"); + + var maxPixelSize = 15; + + generatePerlinNoise(); + + for (let j = pixels.length-4; j >= 0; j -= 4) { + + var pixelSize = Math.max(1, Math.round(Math.random() * maxPixelSize * (noiseProbability/100))); + + var pixelX = j / 4 % actualWidth; + var pixelY = Math.floor(j / 4 / actualWidth); + + var perlinXGridSize = actualWidth / dataWidth; + var perlinYGridSize = actualHeight / dataHeight; + + var perlinX = Math.floor(pixelX / perlinXGridSize); + var perlinY = Math.floor(pixelY / perlinYGridSize); + + var perlinDataValue = perlinDataArray[perlinY][perlinX]; + + var red = pixels[j]; + var green = pixels[j + 1]; + var blue = pixels[j + 2]; + var alpha = pixels[j + 3]; + + var randomRed = chosenColorR - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomGreen = chosenColorG - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomBlue = chosenColorB - rgbColorRange/2 + (Math.random() * rgbColorRange); + var randomAlpha = 1; + + if((Math.pow(perlinDataValue,1.6) * Math.random()) < ((noiseProbability/100) * 0.010) || ((((100 - noiseProbability)/100) * Math.random() * Math.pow(perlinDataValue,2)) < 0.0001)){ + newCtx.fillStyle = `rgba(${randomRed}, ${randomGreen}, ${randomBlue}, ${randomAlpha})`; + + } else { + //pixelSize = 1; + newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + } + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelSize, pixelSize); + } + + } else if(visualizationChoice == "pixelPop"){ + console.log("running pixelPop visual"); + + var numChangePixels = 300; + var maxWidth = 500; + var maxHeight = 500; + var backgroundAlphaValue = 0.8; + var foregroundAlphaValue = 0.5; + + + for(i=0; i 0; j -= 4) { + + var currentRed = pixels[j]; + var currentGreen = pixels[j + 1]; + var currentBlue = pixels[j + 2]; + var currentLum = Math.pow((0.299 * currentRed + 0.587 * currentGreen + 0.114 * currentBlue), 1/2.2); + + var previousRed = pixels[j-4]; + var previousGreen = pixels[j-4+1]; + var previousBlue = pixels[j-4+2]; + var previousLum = Math.pow((0.299 * currentRed + 0.587 * currentGreen + 0.114 * currentBlue), 1/2.2); + + var redDelta = Math.abs(currentRed - previousRed); + var greenDelta = Math.abs(currentGreen - previousGreen); + var blueDelta = Math.abs(currentBlue - previousBlue); + var lumDelta = Math.abs(currentLum - previousLum); + + var alpha = Math.pow(Math.min(1,Math.max(0,(redDelta + greenDelta + blueDelta)/100)), 4); + + var primaryThreshold = 14 * (Math.pow((noiseProbability/100 + 0.5),5)); + + var pixelWidth = Math.round(Math.random()*actualWidth*0.01); + var pixelHeight = Math.round(Math.random()*7); + + if(redDelta > primaryThreshold || greenDelta > primaryThreshold || blueDelta > primaryThreshold || lumDelta > 1){ + newCtx.fillStyle = `rgba(${currentRed}, ${currentGreen}, ${currentBlue}, ${alpha})`; //colour sketch + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelWidth, pixelHeight); + + } else { + if(Math.random() < 0.04){ + var alpha = 0.2; + newCtx.fillStyle = `rgba(${currentRed}, ${currentGreen}, ${currentBlue}, ${alpha})`; //colour sketch + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelWidth*0.75, pixelHeight*0.75); + } + } + + } + + } else if(visualizationChoice == "palletize"){ + console.log("running palletize visual"); + console.log("Color Palette: "+paletteChoice); + + //faithful reproduction + newCtx.drawImage(originalImage, 0, 0); + + for (let j = 0; j < pixels.length; j += 4) { + + var red = pixels[j]; + var green = pixels[j + 1]; + var blue = pixels[j + 2]; + + var lowestDistance = 0; + var targetR; + var targetG; + var targetB; + var alpha = Math.min(1,Math.max(0,noiseProbability/100)); + + for(i=0; i a.score - b.score); + + for (let i = 0; i < sortedPixelData.length; i++) { + var red = sortedPixelData[i].red; + var green = sortedPixelData[i].green; + var blue = sortedPixelData[i].blue; + var alpha = 1; + newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + newCtx.fillRect(i % actualWidth, Math.floor(i / actualWidth), 1, 1); + } + + } else if(visualizationChoice == "void"){ + console.log("running void visual"); + + var numPixels = actualWidth * actualHeight; + + for(var i=numPixels-1; i>=0; i--){ + + var red = pixels[i*4]; + var green = pixels[i*4+1]; + var blue = pixels[i*4+2]; + var lum = Math.pow((0.299 * red + 0.587 * green + 0.114 * blue), 1/2.2) + var threshold = noiseProbability / 9; + var alpha = 1; + var pixelWidth = 1; + var pixelHeight = 1; + + if(lum > threshold){ + newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + pixelWidth = 1; + pixelHeight = 1; + } else { + if(Math.random()<0.7){ + newCtx.fillStyle = `black`; + } else { + var randomRed = Math.random() * 255; + var randomGreen = Math.random() * 255; + var randomBlue = Math.random() * 255; + newCtx.fillStyle = `rgba(${randomRed}, ${randomGreen}, ${randomBlue}, ${alpha})`; + } + pixelWidth = Math.ceil(Math.random()* actualWidth*0.006); + pixelHeight = Math.ceil(Math.random()* actualHeight*0.006); + + } + newCtx.fillRect(i % actualWidth, Math.floor(i / actualWidth), pixelWidth, pixelHeight); + + } + + } else if(visualizationChoice == "braille"){ + console.log("running braille visual"); + + var numPixels = actualWidth * actualHeight; + var colSpacing = Math.floor(actualWidth/50 * Math.pow((noiseProbability/100 + 0.5),2) ); + var rowSpacing = colSpacing; + + var alpha = 1; + var pixelWidth = 1; + var pixelHeight = 1; + var skipRow = true; + var skipCol = true; + + for(var row=0; row 0; j -= 4*skipStep) { + + var currentRed = pixels[j]; + var currentGreen = pixels[j + 1]; + var currentBlue = pixels[j + 2]; + var currentLum = Math.pow((0.299 * currentRed + 0.587 * currentGreen + 0.114 * currentBlue), 1/2.2); + + var previousRed = pixels[j-4]; + var previousGreen = pixels[j-4+1]; + var previousBlue = pixels[j-4+2]; + var previousLum = Math.pow((0.299 * previousRed + 0.587 * previousGreen + 0.114 * previousBlue), 1/2.2); + + var redDelta = Math.abs(currentRed - previousRed); + var greenDelta = Math.abs(currentGreen - previousGreen); + var blueDelta = Math.abs(currentBlue - previousBlue); + var lumDelta = Math.abs(currentLum - previousLum); + + var alpha = Math.pow(Math.min(1,Math.max(0,(redDelta + greenDelta + blueDelta)/100)), 4); + + var primaryThreshold = 7 * (Math.pow((noiseProbability/100 + 0.5),1.1)); + + var pixelWidth = Math.random()*3; + var pixelHeight = Math.random()*3; + + var pixelColor = chosenPalette[ Math.floor(Math.random() * chosenPalette.length) ]; + + if(currentLum < primaryThreshold){ + newCtx.fillStyle = pixelColor; + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelWidth, pixelHeight); + } + } + } else if(visualizationChoice == "outlines"){ + console.log("running outlines visual"); + var skipStep = 1; + + for (let j = pixels.length-4; j > 0; j -= 4*skipStep) { + + var currentRed = pixels[j]; + var currentGreen = pixels[j + 1]; + var currentBlue = pixels[j + 2]; + //var currentLum = Math.pow((0.299 * currentRed + 0.587 * currentGreen + 0.114 * currentBlue), 1/2.2); + var currentLight = rgbToLightness(currentRed,currentGreen,currentBlue); + + var previousRed = pixels[j-4]; + var previousGreen = pixels[j-4+1]; + var previousBlue = pixels[j-4+2]; + //var previousLum = Math.pow((0.299 * previousRed + 0.587 * previousGreen + 0.114 * previousBlue), 1/2.2); + var previousLight = rgbToLightness(previousRed,previousGreen,previousBlue); + + + var nextRed = pixels[j+4]; + var nextGreen = pixels[j+4 + 1]; + var nextBlue = pixels[j+4 + 2]; + //var nextLum = Math.pow((0.299 * nextRed + 0.587 * nextGreen + 0.114 * nextBlue), 1/2.2); + var nextLight = rgbToLightness(nextRed,nextGreen,nextBlue); + + var lightDelta = Math.abs(currentLight - previousLight) + Math.abs(currentLight - nextLight); + newCtx.globalAlpha = Math.max(0.2,lightDelta*3); + + //var primaryThreshold = 7 * (Math.pow((noiseProbability/100 + 0.5),1.1)); + //var secondaryThreshold = 7 * (Math.pow(((100-noiseProbability)/100 + 0.5),1.1)); + + var primaryThreshold = (3+0.94*noiseProbability)/100; + var secondaryThreshold = (100 - (3+0.94*noiseProbability)) / 100; + + var pixelWidth = Math.random()*actualWidth*0.004; + var pixelHeight = Math.random()*actualHeight*0.004; + + if( (currentLight < primaryThreshold && previousLight > primaryThreshold && nextLight < primaryThreshold) || (currentLight < primaryThreshold && previousLight < primaryThreshold && nextLight > primaryThreshold) ){ + newCtx.fillStyle = dualColor1; + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelWidth, pixelHeight); + } else if( (currentLight < secondaryThreshold && previousLight > secondaryThreshold && nextLight < secondaryThreshold) || (currentLight < secondaryThreshold && previousLight < secondaryThreshold && nextLight > secondaryThreshold) ){ + newCtx.fillStyle = dualColor2; + newCtx.fillRect(j / 4 % actualWidth, Math.floor(j / 4 / actualWidth), pixelWidth, pixelHeight); + } + + } + } else if(visualizationChoice == "frontier"){ + console.log("running frontier visual"); + + if(frontierLoadCounter == 0){ + // show the popup + popup.style.display = 'block'; + + clickXPosition = actualWidth/2; + clickYPosition = actualHeight/2; + } + frontierLoadCounter++; + + var heightWidthRatio = actualHeight / actualWidth; + var alpha = 1; + var pixelWidth = 1; + var pixelHeight = 1; + + for(var y=0; y threshold ){ + newCtx.fillStyle = `rgba(${actualRed}, ${actualGreen}, ${actualBlue}, ${alpha})`; + newCtx.fillRect(x, y, 1, 1); + } + } + } + } else if(visualizationChoice == "satLight"){ + console.log("running satLight visual"); + + var alpha = 1; + var lightnessThreshold = (3+0.7*lightnessLevel)/100; + var saturationThreshold = (100 - (3+0.8*saturationLevel))/100; + console.log(lightnessLevel + ", "+saturationLevel); + + + for(var y=0; y < actualHeight; y++ ){ + for(var x=0; x < actualWidth; x++ ){ + + var actualPixel = (y * actualWidth + x) * 4; + var actualRed = pixels[actualPixel]; + var actualGreen = pixels[actualPixel + 1]; + var actualBlue = pixels[actualPixel + 2]; + var actualSaturation = rgbToSaturation(actualRed, actualGreen, actualBlue); + var actualLightness = rgbToLightness(actualRed, actualGreen, actualBlue); + var actualAlpha = 1; + + if(actualLightness < lightnessThreshold || actualSaturation > saturationThreshold){ + newCtx.fillStyle = `rgba(${actualRed}, ${actualGreen}, ${actualBlue}, ${actualAlpha})`; + newCtx.fillRect(x, y, 1, 1); + + } + } + } + } else if(visualizationChoice == "edgy"){ + console.log("running edgy visual"); + + var lightDataArray = []; + + //generate data array for all pixel lightness values + for(var y=0; y < actualHeight; y++ ){ + + lightDataArray[y] = []; + + for(var x=0; x < actualWidth; x++ ){ + + var actualPixel = (y * actualWidth + x) * 4; + var actualRed = pixels[actualPixel]; + var actualGreen = pixels[actualPixel + 1]; + var actualBlue = pixels[actualPixel + 2]; + //var actualLightness = rgbToLightness(actualRed, actualGreen, actualBlue); + var actualLum = (0.2989 * actualRed + 0.5870 * actualGreen + 0.1140 * actualBlue)/255; + + lightDataArray[y][x] = actualLum; + + } + } + + console.log("lightness data array filled"); + + //gaussian smoothing function + var smoothedLightDataArray = [] + var kernelWidth = 5; + var kernelHeight = kernelWidth; + var middlePixel = Math.floor(kernelWidth/2); + var kernelWeights = [0.003663004, 0.014652015, 0.025641026, 0.014652015, 0.003663004, 0.014652015, 0.058608059, 0.095238095, 0.058608059, 0.014652015, 0.025641026, 0.095238095, 0.15018315, 0.095238095, 0.025641026, 0.014652015, 0.058608059, 0.095238095, 0.058608059, 0.014652015, 0.003663004, 0.014652015, 0.025641026, 0.014652015, 0.003663004]; + + for(var y=0; y < actualHeight; y++ ){ + smoothedLightDataArray[y] = []; + + for(var x=0; x < actualWidth; x++ ){ + + var kernelData = []; + + for(var kernelY=0; kernelY= 0 && pixelXPosition < actualWidth && pixelYPosition >= 0 && pixelYPosition < actualHeight){ + kernelData.push(lightDataArray[pixelYPosition][pixelXPosition]); + }else{ + kernelData.push(0); + } + } + } + + var weightedAverageLight = calcWeightedAverage(kernelData,kernelWeights); + smoothedLightDataArray[y][x] = weightedAverageLight; + + } + } + + //draw vertical edges + var alpha = 1; + //var threshold = 0.02 + ((0.8*noiseProbability)/100); + var threshold = 0.165 - (noiseProbability/100 * 0.15); + var pixelWidth = 2; + var pixelHeight = 2; + + for(var y=0; y < actualHeight; y++ ){ + for(var x=0; x < actualWidth; x++ ){ + + if(x==0 || y==0 || x==actualWidth-1 || y==actualHeight-1){ + continue; + } + var lightValue = smoothedLightDataArray[y][x]; + var leftLight = smoothedLightDataArray[y][x-1]; + var rightLight = smoothedLightDataArray[y][x+1]; + + var delta1 = Math.abs(lightValue - leftLight); + var delta2 = Math.abs(lightValue - rightLight); + + var red = lightValue * 255; + var green = lightValue * 255; + var blue = lightValue * 255; + + //if(lightValue < threshold && (leftLight > threshold || rightLight > threshold)){ + if(delta1 > threshold){ + //newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + newCtx.fillStyle = dualColor1; + newCtx.globalAlpha = 0.5; + newCtx.fillRect(x, y, pixelWidth, pixelHeight); + } + + } + } + + //draw horizontal edges + for(var y=0; y < actualHeight; y++ ){ + for(var x=0; x < actualWidth; x++ ){ + + if(x==0 || y==0 || x==actualWidth-1 || y==actualHeight-1){ + continue; + } + var lightValue = smoothedLightDataArray[y][x]; + var topLight = smoothedLightDataArray[y-1][x]; + var bottomLight = smoothedLightDataArray[y+1][x]; + + var delta1 = Math.abs(lightValue - topLight); + var delta2 = Math.abs(lightValue - bottomLight); + + var red = lightValue * 255; + var green = lightValue * 255; + var blue = lightValue * 255; + + //if(lightValue < threshold && (topLight > threshold || bottomLight > threshold)){ + if(delta1 > threshold){ + //newCtx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + newCtx.fillStyle = dualColor2; + newCtx.globalAlpha = 0.5; + newCtx.fillRect(x, y, pixelWidth, pixelHeight); + } + + } + } + + } else if(visualizationChoice == "shadow"){ + + //faithful reproduction + newCtx.drawImage(originalImage, 0, 0); + + var numLayers = 4; + var alpha = 0.3; + var maxXOffset = 0.03 * actualWidth; + var maxYOffset = 0.03 * actualHeight + + for(var layerCounter=0; layerCounter= actualWidth || newY<0 || newY>= actualHeight){ + continue; + } + + var actualPixel = (y * actualWidth + x) * 4; + var actualRed = pixels[actualPixel]; + var actualGreen = pixels[actualPixel + 1]; + var actualBlue = pixels[actualPixel + 2]; + var actualLum = (0.2989 * actualRed + 0.5870 * actualGreen + 0.1140 * actualBlue)/255; + + /* + if(actualLum < 0.3){ + continue; + } + */ + + newCtx.fillStyle = `rgba(${actualRed}, ${actualGreen}, ${actualBlue}, ${alpha})`; + newCtx.fillRect(x+xOffset, y+yOffset, 1, 1); + + } + } + + } + + + + } + + const newImageData = newCanvas.toDataURL(); + const newImage = new Image(); + newImage.src = newImageData; + newImage.style.width = `${scaledWidth}px`; + newImageContainer.appendChild(newImage); + + resizeTable(); + + //hide the loading screen + loadingScreen.classList.remove("lockOn"); + loadingScreen.classList.add("hidden"); + + previousVisualizationChoice = visualizationChoice; + +} + +//Helper Functions + +//shortcut key presses +document.addEventListener('keydown', function(event) { + if (event.key === 'r') { + refresh(); + + } else if (event.key === 's') { + saveImage(); + } else if (event.key === 'e') { + saveBothImages(); + } +}); + +function saveImage(){ + const image = newImageContainer.querySelector('img'); + const imageUrl = image.src; + const link = document.createElement('a'); + const date = new Date(); + const filename = `image_${date.toLocaleDateString()}_${date.toLocaleTimeString()}.png`; + + // Create a blob from the image + fetch(imageUrl) + .then(response => response.blob()) + .then(blob => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + }); + +} + +function saveBothImages(){ + + // Get the two images + const originalImage = imageContainer.querySelector('img'); + const newImage = newImageContainer.querySelector('img'); + + // Create a canvas element + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + // Set the canvas dimensions to match the combined width of the two images + canvas.width = actualWidth*2; + canvas.height = actualHeight; + console.log("Save both images -- canvas width / height: "+canvas.width+", "+canvas.height); + + // Draw the original image on the left side of the canvas + ctx.drawImage(originalImage, 0, 0, actualWidth, actualHeight); + + // Draw the new image on the right side of the canvas + ctx.drawImage(newImage, actualWidth, 0, actualWidth, actualHeight); + + // Use the canvas.toDataURL() method to generate a data URL for the combined image + const combinedImageURL = canvas.toDataURL(); + + const link = document.createElement('a'); + const date = new Date(); + const filename = `image_${date.toLocaleDateString()}_${date.toLocaleTimeString()}.png`; + + // Create a blob from the image + fetch(combinedImageURL) + .then(response => response.blob()) + .then(blob => { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + }); + +} + +function rgbToHue(r, g, b) { + const rNorm = r / 255; + const gNorm = g / 255; + const bNorm = b / 255; + const hue = Math.atan2(Math.sqrt(3) * (gNorm - bNorm), 2 * rNorm - gNorm - bNorm); + return hue * 180 / Math.PI; +} + +function rgbToSaturation(r, g, b) { + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + return (max - min) / max; +} + +function rgbToLightness(r, g, b) { + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + return (max + min) / 2 / 255; +} + +//returns random number between 0-1 based on normal distribution +function randomBM() { + let u = 0, v = 0; + while(u === 0) u = Math.random(); //Converting [0,1) to (0,1) + while(v === 0) v = Math.random(); + let num = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v ); + num = num / 10.0 + 0.5; // Translate to 0 -> 1 + if (num > 1 || num < 0) return randn_bm() // resample between 0 and 1 + return num +} + +function extractRGB(rgbString) { + const rgbRegex = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/; + const match = rgbString.match(rgbRegex); + if (match) { + return { + r: parseInt(match[1]), + g: parseInt(match[2]), + b: parseInt(match[3]), + }; + } else { + return null; + } +} + +function calcWeightedAverage(data,weights){ + var weightedAverage = 0; + for(var i=0; i { + updateColorPickers(); + }); + + colorPicker2.addEventListener('change', (e) => { + updateColorPickers(); + }); + + colorPicker3.addEventListener('change', (e) => { + updateColorPickers(); + }); + + colorPicker4.addEventListener('change', (e) => { + updateColorPickers(); + }); + + colorPicker5.addEventListener('change', (e) => { + updateColorPickers(); + }); + + colorPicker6.addEventListener('change', (e) => { + updateColorPickers(); + }); + + backgroundColorInput.addEventListener('change', (e) => { + refresh(); + }); +} + +function changePalette(){ + + paletteChoice = paletteChoiceInput.value; + + for (let idx = 0; idx < palettePresets.length; idx++){ + if (palettePresets[idx].name == paletteChoice){ + chosenPalette = palettePresets[idx].palette; + break; + } + } + + for (let idx = 0; idx < pickers.length; idx++){ + pickers[idx].value = chosenPalette[idx]; + } + + updateColorPickers(); +} + +function updateColorPickers(){ + + for (let idx = 0; idx < pickers.length; idx++){ + var currentColor = pickers[idx].value; + chosenPalette[idx] = currentColor; + var currentColorRGB = hexToRgb(currentColor); + chosenPaletteRGBValues[idx] = [currentColorRGB.r, currentColorRGB.g, currentColorRGB.b]; + } + + //Modify and save changes to custom palette + var customIndex = palettePresets.findIndex(obj => obj.name === "custom"); + console.log("Palette choice: "+paletteChoice); + if(paletteChoice == "custom"){ + palettePresets[customIndex].palette = chosenPalette; + } + + refresh(); +} + +var carouselClickCounter = 0; + +function initPhotoCarousel(){ + + const carousel = document.querySelector('.carousel'); + const carouselInner = carousel.querySelector('.carousel-inner'); + const carouselItems = carouselInner.querySelectorAll('.carousel-item'); + const carouselDots = carousel.querySelectorAll('.carousel-dot'); + + let currentSlide = 0; + + carouselDots.forEach((dot, index) => { + dot.addEventListener('click', () => { + carouselClickCounter++; + currentSlide = index; + updateCarousel(); + }); + }); + + function updateCarousel() { + + if (currentSlide < 0) { + currentSlide = carouselItems.length - 1; + } else if (currentSlide >= carouselItems.length) { + currentSlide = 0; + } + + carouselItems.forEach((item, index) => { + item.classList.remove('active'); + if (index === currentSlide) { + item.classList.add('active'); + } + }); + + carouselDots.forEach((dot, index) => { + dot.classList.remove('active'); + if (index === currentSlide) { + dot.classList.add('active'); + } + }); + } + + //Autoplay three times only + let iterationCount = 0; + let autoplayIntervalId = setInterval(() => { + if(carouselClickCounter>0){ + return; + } + currentSlide++; + updateCarousel(); + iterationCount++; + if (iterationCount >= 3) { + clearInterval(autoplayIntervalId); + } + }, 4800); //milliseconds before slide change + +} + +// Mondrian object and functions + +function randInt (min, max) { + return Math.floor(Math.random() * (max - min) + min) +} + +class Point { + constructor (x, y) { + this.x = x + this.y = y + } +} + +class Rectangle { + constructor (min, max) { + this.min = min + this.max = max + } + + get width () { + return this.max.x - this.min.x + } + + get height () { + return this.max.y - this.min.y + } + + draw (ctx) { + // Draw clockwise + ctx.moveTo(this.min.x, this.min.y) + ctx.lineTo(this.max.x, this.min.y) + ctx.lineTo(this.max.x, this.max.y) + ctx.lineTo(this.min.x, this.max.y) + ctx.lineTo(this.min.x, this.min.y) + } + + split (xPad, yPad, depth, limit, ctx) { + ctx.fillStyle = chosenPalette[randInt(0, chosenPalette.length)] + ctx.fillRect(this.min.x, this.min.y, this.max.x, this.max.y) + this.draw(ctx) + + // Check the level of recursion + if (depth === limit) { + return + } + + // Check the rectangle is enough large and tall + if (this.width < 2 * xPad || this.height < 2 * yPad) { + return + } + + // If the rectangle is wider than it's height do a left/right split + var r1 = new Rectangle() + var r2 = new Rectangle() + if (this.width > this.height) { + var x = randInt(this.min.x + xPad, this.max.x - xPad) + r1 = new Rectangle(this.min, new Point(x, this.max.y)) + r2 = new Rectangle(new Point(x, this.min.y), this.max) + // Else do a top/bottom split + } else { + var y = randInt(this.min.y + yPad, this.max.y - yPad) + r1 = new Rectangle(this.min, new Point(this.max.x, y)) + r2 = new Rectangle(new Point(this.min.x, y), this.max) + } + + // Split the sub-rectangles + r1.split(xPad, yPad, depth + 1, limit, ctx) + r2.split(xPad, yPad, depth + 1, limit, ctx) + } +} \ No newline at end of file diff --git a/index.html b/index.html index 4985eff..b0ebc8e 100644 --- a/index.html +++ b/index.html @@ -1,96 +1,290 @@ - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - -
- - - - - - - - + + + + + + + + + + Image Mage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
IMAGE MAGE
+ + + +

Remix and rediscover your photos. Turn photos into comic book art, abstract paintings, pixel art, and more.

+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StyleRGBA ShiftSmearSensitivityColor RangeMax Dot SizePaletteColorsBackgroundColorsLightnessSaturation
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + +
+ + + + + + +
+
+ + + + + + + + + + + + +
Source ImageNew Image
+ +
+ +
+
+ +

ABOUT

+ +

This web tool is completely free, open source, without any paywalls or premium options. You are welcome to use it for personal or commercial purposes.

+

If you found this tool useful, feel free to buy me a coffee. This would be much appreciated during late-night coding sessions!

+ + Buy Me A Coffee + +

A few tips for using this tool:

+
    +
  • Press "r" to re-generate a new image. Some visual styles (such as Roller and Pointilist) have randomness built-in, so this gives you a fresh image
  • +
  • Press "s" to save an image
  • +
  • You can generate some neat effects by "stacking" the visual styles. Try running "Palletize" on your source image, saving the output, then loading that image as a source file. After, run the "Pointillist visual. This combines two manipulation effects into one image
  • +
  • The gif at the top of the page was created by taking 10 different variations of a photo using higher/lower sensitivity values (the r / s hotkeys make this pretty speedy to do!)
  • +
  • The GIF animation was then created using the Gif Maker tool provided by Ezgif
  • +
+

This project is coded using Javascript, HTML, and CSS (see github repo linked below). I do not have access to any of the images that you upload here, as all processing is done "client-side" (i.e., no images are saved or stored by me — they stay on your computer only).

+

I'm working on some new visual styles which I hope to add soon. Let me know if you have ideas 💡

+

Feel free to reach out to discuss, ask questions, or to share your creations! You can find my instagram and email accounts through the buttons below.

+
+
+ +
+ + + + + + + +
+
+ + + + + \ No newline at end of file diff --git a/style.css b/style.css index 52b793b..4d1f7a9 100644 --- a/style.css +++ b/style.css @@ -1,85 +1,71 @@ -html, body{ - font-size: 18px; - font-family: Helvetica; - /*max-width: 100%; - overflow-x: hidden;*/ - margin: 0; - padding: 0; - position: absolute; - background-color: #000000; -} - -#gui { - left: 0; - position: fixed; - top: 0; -} - -.closed{ - height: 0px; -} - #introDiv{ width: 100vw; margin: 0; padding: 0; box-sizing: border-box; - background-color: #2D0A3D; + background-color: #581845; padding-top: 2px; padding-bottom: 10px; text-align: center; color:white; } -#siteNameText{ - font-size: 42px; - font-family: "Permanent Marker"; - font-weight: 400; - font-style: normal; - display:block; +#siteHeaderTable td{ + padding-left: 8px; + padding-right: 8px; } -#subtitleText{ - font-size: 24px; - font-family: "Permanent Marker"; +#siteNameText{ + font-size: 42px; + /*font-weight: bold;*/ + font-family: "Tiny5", sans-serif; font-weight: 400; font-style: normal; - font-style: italic; - margin:0 auto; } #aboutText{ font-size: 32px; - font-family: "Permanent Marker"; + font-family: "Tiny5", sans-serif; font-weight: 400; font-style: normal; - display:block; padding: 0; margin: 0; } +#siteLogo{ + width: 90px; +} + +/* applies when screen is wide enough */ +@media (min-width: 600px) { + #siteLogo { + width: 115px; + } + + #siteNameText{ + font-size: 50px; + } +} + #toolDiv{ width:100vw; box-sizing: border-box; - /*overflow: hidden;*/ + overflow: hidden; background-color: white; - text-align: center; - justify-content: center; - background-color: #000000; + padding-top: 10px; + padding-bottom: 10px; } #notesDiv{ width:100vw; box-sizing: border-box; - /*overflow: hidden;*/ + overflow: hidden; padding: 10px; display: flex; justify-content: center; align-items: center; - /*border-top: 5px solid #581845;*/ - background-color: #17193e; - color: white; + border-top: 5px solid #581845; } #textBox { @@ -89,6 +75,12 @@ html, body{ li { margin-bottom: 6px; + + /* + margin-bottom: 10px; + padding-bottom: 5px; + line-height: 1.5; + */ } #linksDiv{ @@ -98,32 +90,82 @@ li { align-items: center; } -h2 { - font-size: 40px; - font-family: Helvetica; +#buttonTableDiv{ + margin-top: 2px; + margin-bottom: 10px; } -#canvas { - display: block; - margin: 0 auto; - margin-top: 2.5vh; +#inputTable{ + font-size:0.90em; + text-align: center; + min-width: 280px; + width:100vw; + max-width: 600px; + margin-top: 10px; + margin-bottom: 10px; + padding-bottom: 7px; + border-bottom: 1px solid black; +} + +/* applies when screen is wide enough */ +@media (min-width: 600px) { + #inputTable { + font-size:1em; + } +} + +#imageTable { + border-collapse: collapse; + border: none; + padding: 0; + margin-top: 10px; + margin-bottom: 10px; + max-width: 100vw; +} + +.imageTableCell { padding: 0; - border: 3px solid black; + margin: 0; + text-align: left; + border: none; +} - /* note cursor is fixed 30px PNG - locked to BRUSH_SIZE of 30 */ - /* TODO: SVG cursor? */ - cursor: url(paintbrush-cursor.cur) 16 16, crosshair !important; +.imageHeaderText { + font-size: 0.85em; + padding: 0px 0 5px 0; + margin: 0; + text-align: left; + border: none; + min-width: 100px; + text-align: center; } -.exampleImage { - max-width: 70vw; - margin: 0 auto; +body, input, button { + font-family: "Helvetica"; + font-style: normal; +} + +html, body { + max-width: 100%; + overflow-x: hidden; + margin: 0; padding: 0; - display: block; } -/* user input styling */ +table { + border-collapse: collapse; + margin:auto; + max-width:95%; +} +th { + border: 2px solid white; + padding: 5px; + text-align: center; + vertical-align:middle; + border-collapse: collapse; + background-color: #e9e9e9; +} :-ms-input-placeholder { /* Internet Explorer 10-11 */ color: rgb(194, 194, 194); @@ -157,7 +199,7 @@ input:hover{ } .select-style { - border-bottom: 2px solid #00b7ff; + border-bottom: 2px solid rgb(0, 183, 255); border-left:none; border-top:none; border-right:none; @@ -185,10 +227,36 @@ input:hover{ } .hidden { - display: none !important; + display: none; +} + +.inputCol8{ + max-width:150px; } +.lockOn { + display: block; + visibility: visible; + position: fixed; + z-index: 999; + top: 0px; + left: 0px; + width: 100vw; + height: 100vh; + background-color: rgba(255, 255, 255, 0.25); + padding-top: 0%; + opacity: 0.9; + font-size:large; + color:blue; + font-style:italic; + font-weight:400; + background-image: url(https://github.com/imageMageAge/imageMageAge.github.io/blob/main/images/loadingCircle.gif?raw=true); + background-repeat: no-repeat; + background-attachment: fixed; + background-position: center; +} + /* Styling for image upload field */ input, select { cursor: pointer; @@ -203,10 +271,10 @@ input[type="file"] { background-color: #226f63; color: #fff; font-weight: bold; - font-size: 14px; + font-size: 16px; font-family: Helvetica; display: inline-block; - padding: 8px; + padding: 9px; cursor: pointer; margin-left: 3px; margin-right: 2px; @@ -214,19 +282,13 @@ input[type="file"] { /* Styling for color inputs */ input[type="color"] { + width: 40px; + height: 40px; border: none; background-color: none; padding:0; - margin: 0px; -} - -input[type="range"] { - max-width:25vw; -} + margin: 5px; -input[type="checkbox"] { - min-width: 20px; - min-height: 20px; } button { @@ -234,9 +296,8 @@ button { font-weight: bold; font-size: 13px; - margin: 10px 3px 10px 3px; - /*margin-left: 3px; - margin-right: 3px;*/ + margin-left: 3px; + margin-right: 3px; border: 3px solid #79e2d2; border-radius: 6px; @@ -245,122 +306,13 @@ button { cursor: pointer; } -.recordButton{ - border: 3px solid #4a2bb0; - background-color: #4a2bb0; - color: white; -} - -.recordButtonStop{ - border: 3px solid #b02b9c; - background-color: #b02b9c; - color: white; -} - -.tableText{ - font-weight: bold; - font-size: 0.7em; - font-family: Helvetica; - color:white; -} - -pre { - margin: 0; -} - /* applies when screen is wide enough */ -@media (min-width: 700px) { - button, .tableText { - font-size: 0.9em; +@media (min-width: 600px) { + button { + font-size: 14px; } } -table { - border-collapse: collapse; - margin:auto; -} - -td { - max-width: 250px; -} - -#inputTable{ - font-size:0.8em; - text-align: center; - /*min-width: 280px; - width:80vw; - max-width: 350px; - margin-top: 15px; - margin-bottom: 15px; - border-top: 1px solid black; - border-bottom: 1px solid black; - */ - width:100vw; - /*background-color: rgb(228, 218, 255);*/ - background-color: #2D0A3D; -} - -#textInput{ - width: 85vw !important; - border: 4px solid rgb(161, 50, 154); - font-size: 1.1em; - margin-top: 5px; - margin-bottom: 5px; - text-align: left; -} - -/* applies when screen is wide enough */ -@media (min-width: 700px) { - #inputTable { - font-size:1.0em; - /*max-width: 700px;*/ - } -} - -#videoRecordingMessageDiv{ - background: rgba(0, 0, 0, 0.75); - color:white; - font-family: helvetica; - font-size: 20px; - font-weight: bold; - padding: 8px; -} - -.sticky-bottom { - position: fixed; - bottom: 0; - left: 0; - width: 100%; -} - -#secondsLeft { - font-size: 1.2em; - color: #b02b9c; -} - -/*floating loading screen*/ -.lockOn { - display: block; - visibility: visible; - position: fixed; - z-index: 999; - top: 0px; - left: 0px; - width: 100vw; - height: 100vh; - background-color: rgba(255, 255, 255, 0.25); - padding-top: 0%; - opacity: 0.9; - font-size:large; - color:blue; - font-style:italic; - font-weight:400; - background-image: url(https://github.com/imageMageAge/imageMageAge.github.io/blob/main/images/loadingCircle.gif?raw=true); - background-repeat: no-repeat; - background-attachment: fixed; - background-position: center; -} - /* Styling for table with github and social links */ #infoMenuTable td { @@ -374,12 +326,8 @@ td { margin: 0px; } -a, a:visited{ - color:rgb(161, 50, 154); -} - .socialMediaButton{ - color:rgb(161, 50, 154); + color:rgb(55, 60, 133); font-size: 26px; margin: 0; padding: 4px; @@ -391,4 +339,67 @@ a, a:visited{ #buyMeACoffeeButton { font-size: 10px; +} + +/* Carousel formatting */ +.carousel { + position: relative; + } + + .carousel-inner { + overflow: hidden; + } + + .carousel-item { + display: none; + /*max-height: 65vh; + max-width: 96vw;*/ + position: relative; + } + + .carousel-item.active { + display: block; + } + + .carousel-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: #a3ede2; + cursor: pointer; + margin: 0 5px; + border: none; + } + + .carousel-dot.active { + background-color: #179d89; + border: 2px solid #6fc5f3; + } + + .carouselImage { + max-height: 75vh; + max-width: 96vw; + } + +/* Pop-up styling */ +.popup { + background-color: #fff; + padding: 5px; + border: 3px solid #601785; + display: none; /* hide by default */ + visibility: visible; + position: fixed; + z-index: 999; + max-width: 200px; + max-height: 100px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +#closeIcon{ + font-size:30px; + cursor: pointer; } \ No newline at end of file