const capImageSize = 50; const halfCapImageSize = capImageSize / 2; const numberOfColumns = 40; const numberOfRows = Math.ceil(numberOfCaps / numberOfColumns); const effectiveHeight = Math.ceil(capImageSize*Math.cos(Math.PI/6)); function sourcePath(capId) { return "thumbnails/" + `${capId}`.padStart(4, '0') + '.jpg' } function createImage(position) { return '' }; function setCapImageAtPosition(capId, position) { elements[position].src = sourcePath(capId); capPositions[position] = capId; } function getCapIdAtPosition(position) { return capPositions[position]; } const imageCanvas = document.getElementById("image-canvas"); var capPositions = Array.from( {length: numberOfCaps}, (_, index) => index + 1 ); imageCanvas.innerHTML += capPositions.map(createImage).join(""); var selectedPosition = null; const elements = imageCanvas.getElementsByTagName("img"); const selectedCapCircle = document.getElementById("selected-cap-circle"); imageCanvas.addEventListener("click", function(event) { const yOffset = 50; const xPosition = event.clientX - imageCanvas.getBoundingClientRect().left; const yPosition = event.clientY - imageCanvas.getBoundingClientRect().top - yOffset; var row = Math.floor(yPosition / effectiveHeight); var rowIsEven = (row % 2 == 0) const adjustedX = rowIsEven ? xPosition - halfCapImageSize : xPosition; var column = Math.floor(adjustedX / capImageSize); const rowRemainder = yPosition % effectiveHeight - halfCapImageSize; const columnRemainder = adjustedX % capImageSize - halfCapImageSize; const distanceToCenter = Math.sqrt(rowRemainder**2 + columnRemainder**2); if (distanceToCenter > halfCapImageSize && rowRemainder < 6 - halfCapImageSize) { // Move to the top left cap row -= 1; if (!rowIsEven && (columnRemainder < 0)) { // Move to the top right cap column -= 1; } if (rowIsEven && (columnRemainder > 0)) { // Move to the top left cap column += 1; } rowIsEven = !rowIsEven } if (row < 0 || row >= numberOfRows) { return; } if (column < 0 || column >= numberOfColumns) { return; } const currentPosition = row * numberOfColumns + column; if (currentPosition >= numberOfCaps) { return; } if (selectedPosition === null) { selectedPosition = currentPosition; const circlePositionX = rowIsEven ? column * capImageSize + halfCapImageSize : column * capImageSize; const circlePositionY = row * effectiveHeight + yOffset; selectedCapCircle.style.left = circlePositionX + "px"; selectedCapCircle.style.top = circlePositionY + "px"; selectedCapCircle.style.display = "block"; return; } selectedCapCircle.style.display = "none"; if (currentPosition === selectedPosition) { selectedPosition = null; return; } // Switch cap images const currentCapId = getCapIdAtPosition(currentPosition); const selectedCapId = getCapIdAtPosition(selectedPosition); setCapImageAtPosition(selectedCapId, currentPosition); setCapImageAtPosition(currentCapId, selectedPosition); selectedPosition = null; }); function averageColor(imageElement) { var canvas = document.createElement('canvas'); var context = canvas.getContext && canvas.getContext('2d'); if (!context) { return { r: 255, g: 0, b: 0 }; } // Set the height and width equal // to that of the canvas and the image var height = canvas.height = imageElement.naturalHeight || imageElement.offsetHeight || imageElement.height; var width = canvas.width = imageElement.naturalWidth || imageElement.offsetWidth || imageElement.width; // Draw the image to the canvas context.drawImage(imageElement, 0, 0); // Get the data of the image try { var imgData = context.getImageData(0, 0, width, height); } catch(e) { /* security error, img on diff domain */alert('x'); return { r: 255, g: 0, b: 0 }; } // Get the length of image data object var length = imgData.data.length; const pixelCount = height * width; const halfX = width / 2; const halfY = height / 2; var rgb = { r: 0, g: 0, b: 0 }; var count = 0; for (var i = 0; i < pixelCount; i += 1) { // Exclude everything outside of circle const x = i % width - halfX; const y = Math.floor(i / width) - halfY; const distanceToCenter = Math.sqrt(x**2 + y**2); if (distanceToCenter > halfY) { continue; } // Sum all values of the colour const offset = 4 * i; rgb.r += imgData.data[offset]; rgb.g += imgData.data[offset + 1]; rgb.b += imgData.data[offset + 2]; count += 1; } // Find the averages rgb.r = Math.floor(rgb.r / count); rgb.g = Math.floor(rgb.g / count); rgb.b = Math.floor(rgb.b / count); return rgb; } for (let i = 0; i < capPositions.length; i += 1) { const element = elements[i]; element.onload = function() { const rgb = averageColor(element); const color = `rgb(${rgb.r},${rgb.g},${rgb.b})`; element.style.backgroundColor = color; } } var showColors = false; function toggleColors() { showColors = !showColors; const padding = showColors ? "50px" : "0px"; for (let i = 0; i < capPositions.length; i += 1) { elements[i].style.paddingLeft = padding; } } function exportCapArrangement() { const a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([JSON.stringify(capPositions)], { type: "application/json" })); a.setAttribute("download", "caps.json"); document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href) } function resetCaps() { // Set each cap in the data for (let i = 0; i < capPositions.length; i++) { setCapImageAtPosition(i+1, i); } } function isInt(value) { if (isNaN(value)) { return false; } var x = parseFloat(value); return (x | 0) === x; } function decodeCapFile(content) { var caps = JSON.parse(content); if (!Array.isArray(caps)) { alert("Invalid file (not a JSON array)"); return; } const capCount = capPositions.length; if (caps.length > capPositions.length) { alert("Invalid file (Too many entries)"); return; } for (let i = 0; i < caps.length; i++) { const cap = caps[i]; if (!Number.isInteger(cap)) { alert("Invalid file (Not all integers)"); return; } if (cap < 1 || cap > capCount) { alert("Invalid file (Not all valid cap counts)"); return; } // TODO: Check if each id is used once } // Set each cap in the data for (let i = 0; i < caps.length; i++) { const cap = caps[i]; setCapImageAtPosition(cap, i); } // Reset the remaining caps which may be missing from the loaded file // since they are newer for (let i = caps.length; i < capCount; i++) { setCapImageAtPosition(i+1, i); } console.log("Cap image loaded") } function loadCapArrangementFile(e) { console.log("File loaded") var file = e.target.files[0]; if (!file) { return; } var reader = new FileReader(); reader.onload = function(e) { var contents = e.target.result; document.getElementById('caps-upload').value = null; decodeCapFile(contents); }; reader.readAsText(file); } // Register when user selects a file document.getElementById('caps-upload') .addEventListener('change', loadCapArrangementFile, false);