/* eslint-disable no-console */ /* eslint-disable radix */ /* eslint-disable max-len */ /* eslint-disable no-plusplus */ // A bunch of settings used when converting const settings = { screenWidth: 128, screenHeight: 64, scaleToFit: true, preserveRatio: true, centerHorizontally: false, centerVertically: false, flipHorizontally: false, flipVertically: false, backgroundColor: 'white', scale: 1, drawMode: 'horizontal', removeZeroesCommas: false, ditheringThreshold: 128, ditheringMode: 0, outputFormat: 'plain', invertColors: false, rotation: 0, }; let outputString = ''; function bitswap(b) { if (settings.bitswap) { // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators b = (b & 0xCC) >> 2 | (b & 0x33) << 2; // eslint-disable-next-line no-param-reassign, no-bitwise, no-mixed-operators b = (b & 0xAA) >> 1 | (b & 0x55) << 1; } return b; } const ConversionFunctions = { // Output the image as a string for horizontally drawing displays // eslint-disable-next-line no-unused-vars horizontal1bit(data, canvasWidth, canvasHeight) { // eslint-disable-next-line no-shadow outputString = ''; let outputIndex = 0; let byteIndex = 7; let number = 0; // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { // Get the average of the RGB (we ignore A) const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; if (avg > settings.ditheringThreshold) { number += 2 ** byteIndex; } byteIndex--; // if this was the last pixel of a row or the last pixel of the // image, fill up the rest of our byte with zeros so it always contains 8 bits if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { // for(var i=byteIndex;i>-1;i--){ // number += Math.pow(2, i); // } byteIndex = -1; } // When we have the complete 8 bits, combine them into a hex value if (byteIndex < 0) { let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } if (!settings.removeZeroesCommas) { outputString += `0x${byteSet}, `; } else { outputString += byteSet; } outputIndex++; if (outputIndex >= 16) { if (!settings.removeZeroesCommas) { outputString += '\n'; } outputIndex = 0; } number = 0; byteIndex = 7; } } return outputString; }, // Output the image as a string for vertically drawing displays // eslint-disable-next-line no-unused-vars vertical1bit(data, canvasWidth, canvasHeight) { // eslint-disable-next-line no-shadow outputString = ''; let outputIndex = 0; for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) { for (let x = 0; x < settings.screenWidth; x++) { let byteIndex = 7; let number = 0; for (let y = 7; y >= 0; y--) { const index = ((p * 8) + y) * (settings.screenWidth * 4) + x * 4; const avg = (data[index] + data[index + 1] + data[index + 2]) / 3; if (avg > settings.ditheringThreshold) { number += 2 ** byteIndex; } byteIndex--; } let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } if (!settings.removeZeroesCommas) { outputString += `0x${byteSet.toString(16)}, `; } else { outputString += byteSet.toString(16); } outputIndex++; if (outputIndex >= 16) { outputString += '\n'; outputIndex = 0; } } } return outputString; }, // Output the image as a string for 565 displays (horizontally) // eslint-disable-next-line no-unused-vars horizontal565(data, canvasWidth, canvasHeight) { // eslint-disable-next-line no-shadow outputString = ''; let outputIndex = 0; // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { // Get the RGB values const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; // calculate the 565 color value // eslint-disable-next-line no-bitwise const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3); // Split up the color value in two bytes // const firstByte = (rgb >> 8) & 0xff; // const secondByte = rgb & 0xff; let byteSet = bitswap(rgb).toString(16); while (byteSet.length < 4) { byteSet = `0${byteSet}`; } if (!settings.removeZeroesCommas) { outputString += `0x${byteSet}, `; } else { outputString += byteSet; } // add newlines every 16 bytes outputIndex++; if (outputIndex >= 16) { outputString += '\n'; outputIndex = 0; } } return outputString; }, // Output the image as a string for rgb888 displays (horizontally) // eslint-disable-next-line no-unused-vars horizontal888(data, canvasWidth, canvasHeight) { // eslint-disable-next-line no-shadow outputString = ''; let outputIndex = 0; // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { // Get the RGB values const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; // calculate the 565 color value // eslint-disable-next-line no-bitwise const rgb = (r << 16) | (g << 8) | (b); // Split up the color value in two bytes // const firstByte = (rgb >> 8) & 0xff; // const secondByte = rgb & 0xff; let byteSet = bitswap(rgb).toString(16); while (byteSet.length < 8) { byteSet = `0${byteSet}`; } if (!settings.removeZeroesCommas) { outputString += `0x${byteSet}, `; } else { outputString += byteSet; } // add newlines every 16 bytes outputIndex++; if (outputIndex >= canvasWidth) { outputString += '\n'; outputIndex = 0; } } return outputString; }, // Output the alpha mask as a string for horizontally drawing displays // eslint-disable-next-line no-unused-vars horizontalAlpha(data, canvasWidth, canvasHeight) { // eslint-disable-next-line no-shadow outputString = ''; let outputIndex = 0; let byteIndex = 7; let number = 0; // format is RGBA, so move 4 steps per pixel for (let index = 0; index < data.length; index += 4) { // Get alpha part of the image data const alpha = data[index + 3]; if (alpha > settings.ditheringThreshold) { number += 2 ** byteIndex; } byteIndex--; // if this was the last pixel of a row or the last pixel of the // image, fill up the rest of our byte with zeros so it always contains 8 bits if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) { byteIndex = -1; } // When we have the complete 8 bits, combine them into a hex value if (byteIndex < 0) { let byteSet = bitswap(number).toString(16); if (byteSet.length === 1) { byteSet = `0${byteSet}`; } if (!settings.removeZeroesCommas) { outputString += `0x${byteSet}, `; } else { outputString += byteSet; } outputIndex++; if (outputIndex >= 16) { outputString += '\n'; outputIndex = 0; } number = 0; byteIndex = 7; } } return outputString; }, }; settings.ConversionFunction = ConversionFunctions.horizontal1bit; // An images collection with helper methods function Images() { const collection = []; this.push = (img, canvas, glyph) => { collection.push({ img, canvas, glyph }); }; this.remove = (image) => { const i = collection.indexOf(image); if (i !== -1) collection.splice(i, 1); }; this.each = (f) => { collection.forEach(f); }; this.length = () => collection.length; this.first = () => collection[0]; this.last = () => collection[collection.length - 1]; this.getByIndex = (index) => collection[index]; this.setByIndex = (index, img) => { collection[index] = img; }; this.get = (img) => { if (img) { for (let i = 0; i < collection.length; i++) { if (collection[i].img === img) { return collection[i]; } } } return collection; }; return this; } const images = new Images(); let output = ''; // Filetypes accepted by the file picker // const fileTypes = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg']; // Variable name, when "arduino code" is required const identifier = 'myBitmap'; // Invert the colors of the canvas function invert(canvas, ctx) { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const { data } = imageData; for (let i = 0; i < data.length; i += 4) { data[i] = 255 - data[i]; // red data[i + 1] = 255 - data[i + 1]; // green data[i + 2] = 255 - data[i + 2]; // blue } ctx.putImageData(imageData, 0, 0); } // Draw the image onto the canvas, taking into account color and scaling function placeImage(_image) { const { img } = _image; const { canvas } = _image; const ctx = canvas.getContext('2d'); // eslint-disable-next-line no-param-reassign _image.ctx = ctx; ctx.save(); const flipHorizontal = settings.flipHorizontally ? -1 : 1; const flipVertical = settings.flipVertically ? -1 : 1; if (settings.rotation === 0) { canvas.width = settings.screenWidth; canvas.height = settings.screenHeight; const xTranslate = settings.flipHorizontally ? canvas.width : 0; const yTranslate = settings.flipVertically ? canvas.height : 0; ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); } else if (settings.rotation === 90) { canvas.width = settings.screenHeight; canvas.height = settings.screenWidth; const xTranslate = settings.flipHorizontally ? canvas.width : 0; const yTranslate = settings.flipVertically ? canvas.height : 0; ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); // ctx = canvas.getContext("2d"); ctx.translate(canvas.width, 0); ctx.rotate(Math.PI / 2); // ctx.translate(-canvas.width/2, 0); } else if (settings.rotation === 180) { canvas.width = settings.screenWidth; canvas.height = settings.screenHeight; const xTranslate = settings.flipHorizontally ? canvas.width : 0; const yTranslate = settings.flipVertically ? canvas.height : 0; ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); // Matrix transformation ctx.translate(canvas.width / 2.0, canvas.height / 2.0); ctx.rotate(Math.PI); ctx.translate(-canvas.width / 2.0, -canvas.height / 2.0); } else if (settings.rotation === 270) { canvas.width = settings.screenHeight; canvas.height = settings.screenWidth; const xTranslate = settings.flipHorizontally ? canvas.width : 0; const yTranslate = settings.flipVertically ? canvas.height : 0; ctx.setTransform(flipHorizontal, 0, 0, flipVertical, xTranslate, yTranslate); ctx.translate(0, canvas.height); ctx.rotate(-Math.PI * 0.5); } // Draw background if (settings.backgroundColor === 'transparent') { ctx.fillStyle = 'rgba(0,0,0,0.0)'; ctx.globalCompositeOperation = 'copy'; } else { if (settings.invertColors) { if (settings.backgroundColor === 'white') { ctx.fillStyle = 'black'; } else { ctx.fillStyle = 'white'; } } else { ctx.fillStyle = settings.backgroundColor; } ctx.globalCompositeOperation = 'source-over'; } ctx.fillRect(0, 0, canvas.width, canvas.height); // Offset used for centering the image when requested let offsetX = 0; let offsetY = 0; const imgW = img.width; const imgH = img.height; switch (settings.scale) { case 1: // Original if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW) / 2); } if (settings.centerVertically) { offsetY = Math.round((canvas.height - imgH) / 2); } ctx.drawImage( img, 0, 0, imgW, imgH, offsetX, offsetY, imgW, imgH, ); break; case 2: // Fit (make as large as possible without changing ratio) // eslint-disable-next-line no-case-declarations const horRatio = canvas.width / imgW; // eslint-disable-next-line no-case-declarations const verRatio = canvas.height / imgH; // eslint-disable-next-line no-case-declarations const useRatio = Math.min(horRatio, verRatio); if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW * useRatio) / 2); } if (settings.centerVertically) { offsetY = Math.round((canvas.height - imgH * useRatio) / 2); } // offsetX *= offsetX_dir; // offsetY *= offsetY_dir; ctx.drawImage( img, 0, 0, imgW, imgH, offsetX, offsetY, imgW * useRatio, imgH * useRatio, ); break; case 3: // Stretch x+y (make as large as possible without keeping ratio) ctx.drawImage( img, 0, 0, imgW, imgH, offsetX, offsetY, canvas.width, canvas.height, ); break; case 4: // Stretch x (make as wide as possible) offsetX = 0; if (settings.centerVertically) { Math.round(offsetY = (canvas.height - imgH) / 2); } // offsetY *= offsetY_dir; ctx.drawImage( img, 0, 0, imgW, imgH, offsetX, offsetY, canvas.width, imgH, ); break; case 5: // Stretch y (make as tall as possible) if (settings.centerHorizontally) { offsetX = Math.round((canvas.width - imgW) / 2); } // offsetX *= offsetX_dir; offsetY = 0; ctx.drawImage( img, 0, 0, imgW, imgH, offsetX, offsetY, imgW, canvas.height, ); break; default: // console.log('unknown scale'); break; } ctx.restore(); // Make sure the image is black and white console.log('dithering: ', dithering); if (settings.conversionFunction === ConversionFunctions.horizontal1bit || settings.conversionFunction === ConversionFunctions.vertical1bit) { dithering(ctx, canvas.width, canvas.height, settings.ditheringThreshold, settings.ditheringMode); if (settings.invertColors) { invert(canvas, ctx); } } } function update() { images.each((image) => { placeImage(image); }); } // Easy way to update settings controlled by a textfield function updateInteger(fieldName) { settings[fieldName] = parseInt(document.getElementById(fieldName).value); update(); } // Easy way to update settings controlled by a checkbox // eslint-disable-next-line no-unused-vars function updateBoolean(fieldName) { settings[fieldName] = document.getElementById(fieldName).checked; update(); } // Convert hex to binary function hexToBinary(s) { let i; let ret = ''; // lookup table for easier conversion. "0" characters are // padded for "1" to "7" const lookupTable = { 0: '0000', 1: '0001', 2: '0010', 3: '0011', 4: '0100', 5: '0101', 6: '0110', 7: '0111', 8: '1000', 9: '1001', a: '1010', b: '1011', c: '1100', d: '1101', e: '1110', f: '1111', A: '1010', B: '1011', C: '1100', D: '1101', E: '1110', F: '1111', }; for (i = 0; i < s.length; i += 1) { // eslint-disable-next-line no-prototype-builtins if (lookupTable.hasOwnProperty(s[i])) { ret += lookupTable[s[i]]; } else { return { valid: false, s }; } } return { valid: true, result: ret }; } // get the type (in arduino code) of the output image // this is a bit of a hack, it's better to make this a property of the conversion function (should probably turn it into objects) function getType() { if (settings.conversionFunction === ConversionFunctions.horizontal565) { return 'uint16_t'; } if (settings.conversionFunction === ConversionFunctions.horizontal888) { return 'unsigned long'; } return 'unsigned char'; } // Use the horizontally oriented list to draw the image function listToImageHorizontal(list, canvas) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); const imgData = ctx.createImageData(canvas.width, canvas.height); let index = 0; // round the width up to the next byte const widthRoundedUp = Math.floor(canvas.width / 8 + (canvas.width % 8 ? 1 : 0)) * 8; let widthCounter = 0; // Move the list into the imageData object for (let i = 0; i < list.length; i++) { let binString = hexToBinary(list[i]); if (!binString.valid) { // eslint-disable-next-line no-alert alert('Something went wrong converting the string. Did you forget to remove any comments from the input?'); console.log('invalid hexToBinary: ', binString.s); return; } binString = binString.result; if (binString.length === 4) { binString += '0000'; } // Check if pixel is white or black for (let k = 0; k < binString.length; k++, widthCounter++) { // if we've counted enough bits, reset counter for next line if (widthCounter >= widthRoundedUp) { widthCounter = 0; } // skip 'artifact' pixels due to rounding up to a byte if (widthCounter >= canvas.width) { // eslint-disable-next-line no-continue continue; } let color = 0; if (binString.charAt(k) === '1') { color = 255; } imgData.data[index] = color; imgData.data[index + 1] = color; imgData.data[index + 2] = color; imgData.data[index + 3] = 255; index += 4; } } // Draw the image onto the canvas, then save the canvas contents // inside the img object. This way we can reuse the img object when // we want to scale / invert, etc. ctx.putImageData(imgData, 0, 0); const img = new Image(); img.src = canvas.toDataURL('image/png'); images.first().img = img; } // Quick and effective way to draw single pixels onto the canvas // using a global 1x1px large canvas function drawPixel(ctx, x, y, color) { const singlePixel = ctx.createImageData(1, 1); const d = singlePixel.data; d[0] = color; d[1] = color; d[2] = color; d[3] = 255; ctx.putImageData(singlePixel, x, y); } // Use the vertically oriented list to draw the image function listToImageVertical(list, canvas) { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); let page = 0; let x = 0; let y = 7; // Move the list into the imageData object for (let i = 0; i < list.length; i++) { let binString = hexToBinary(list[i]); if (!binString.valid) { // eslint-disable-next-line no-alert alert('Something went wrong converting the string. Did you forget to remove any comments from the input?'); console.log('invalid hexToBinary: ', binString.s); return; } binString = binString.result; if (binString.length === 4) { binString += '0000'; } // Check if pixel is white or black for (let k = 0; k < binString.length; k++) { let color = 0; if (binString.charAt(k) === '1') { color = 255; } drawPixel(ctx, x, (page * 8) + y, color); y--; if (y < 0) { y = 7; x++; if (x >= settings.screenWidth) { x = 0; page++; } } } } // Save the canvas contents inside the img object. This way we can // reuse the img object when we want to scale / invert, etc. const img = new Image(); img.src = canvas.toDataURL('image/png'); images.first().img = img; } // Handle inserting an image by pasting code // eslint-disable-next-line no-unused-vars function handleTextInput(drawMode) { const canvasContainer = document.getElementById('images-canvas-container'); const canvas = document.createElement('canvas'); canvas.width = parseInt(document.getElementById('text-input-width').value); canvas.height = parseInt(document.getElementById('text-input-height').value); settings.screenWidth = canvas.width; settings.screenHeight = canvas.height; if (canvasContainer.children.length) { canvasContainer.removeChild(canvasContainer.firstChild); } canvasContainer.appendChild(canvas); const image = new Image(); images.setByIndex(0, { img: image, canvas }); let input = document.getElementById('byte-input').value; // Remove Arduino code input = input.replace(/const\s+(unsigned\s+char|uint8_t)\s+[a-zA-Z0-9]+\s*\[\]\s*(PROGMEM\s*)?=\s*/g, ''); input = input.replace(/\};|\{/g, ''); // Convert newlines to comma (helps to remove comments later) input = input.replace(/\r\n|\r|\n/g, ','); // Convert multiple commas in a row into a single one input = input.replace(/,{2,}/g, ','); // Remove whitespace input = input.replace(/\s/g, ''); // Remove comments input = input.replace(/\/\/(.+?),/g, ''); // Remove "0x" input = input.replace(/0[xX]/g, ''); // Split into list const list = input.split(','); if (drawMode === 'horizontal') { listToImageHorizontal(list, canvas); } else { listToImageVertical(list, canvas); } } function allSameSize(_images, files) { const allSameSizeButton = document.getElementById('all-same-size'); if (_images.length() > 1 && _images.length() === files.length) { // multiple images settings container // var imageSizeSettings = document.getElementById("image-size-settings"); const inputs = document.querySelectorAll('#image-size-settings input'); // all images same size button allSameSizeButton.onclick = () => { for (let i = 2; i < inputs.length; i++) { if (inputs[i].name === 'width') { inputs[i].value = inputs[0].value; inputs[i].oninput(); } if (inputs[i].name === 'height') { inputs[i].value = inputs[1].value; inputs[i].oninput(); } } }; allSameSizeButton.style.display = 'block'; } } // Handle selecting an image with the file picker function handleImageSelection(evt) { const files = Array.from(evt.target.files); files.sort((a, b) => a.name > b.name); // error message const onlyImagesFileError = document.getElementById('only-images-file-error'); onlyImagesFileError.style.display = 'none'; // initial message const noFileSelected = document.getElementById('no-file-selected'); if (files.length > 0) { noFileSelected.style.display = 'none'; } else { noFileSelected.style.display = 'block'; } for (let i = 0; files[i]; i++) { // Only process image files. if (!files[i].type.match('image.*')) { onlyImagesFileError.style.display = 'block'; // eslint-disable-next-line no-continue continue; } const reader = new FileReader(); // eslint-disable-next-line no-loop-func reader.onload = (function (file) { return (e) => { // Render thumbnail. const img = new Image(); img.onload = () => { const fileInputColumnEntry = document.createElement('div'); fileInputColumnEntry.className = 'file-input-entry'; const fileInputColumnEntryLabel = document.createElement('span'); fileInputColumnEntryLabel.textContent = file.name; const fileInputColumnEntryRemoveButton = document.createElement('button'); fileInputColumnEntryRemoveButton.className = 'remove-button'; fileInputColumnEntryRemoveButton.innerHTML = 'remove'; const canvas = document.createElement('canvas'); const imageEntry = document.createElement('li'); imageEntry.setAttribute('data-img', file.name); const w = document.createElement('input'); w.type = 'number'; w.name = 'width'; w.id = 'screenWidth'; w.min = 0; w.className = 'size-input'; w.value = img.width; settings.screenWidth = img.width; w.oninput = () => { canvas.width = this.value; update(); updateInteger('screenWidth'); }; const h = document.createElement('input'); h.type = 'number'; h.name = 'height'; h.id = 'screenHeight'; h.min = 0; h.className = 'size-input'; h.value = img.height; settings.screenHeight = img.height; h.oninput = () => { canvas.height = this.value; update(); updateInteger('screenHeight'); }; const gil = document.createElement('span'); gil.innerHTML = 'glyph'; gil.className = 'file-info'; const gi = document.createElement('input'); gi.type = 'text'; gi.name = 'glyph'; gi.className = 'glyph-input'; gi.onchange = () => { const image = images.get(img); image.glyph = gi.value; }; const fn = document.createElement('span'); fn.className = 'file-info'; fn.innerHTML = `${file.name} (file resolution: ${img.width} x ${img.height})`; fn.innerHTML += '
'; const rb = document.createElement('button'); rb.className = 'remove-button'; rb.innerHTML = 'remove'; const fileInputColumn = document.getElementById('file-input-column'); const imageSizeSettings = document.getElementById('image-size-settings'); const canvasContainer = document.getElementById('images-canvas-container'); const allSameSizeButton = document.getElementById('all-same-size'); const removeButtonOnClick = () => { const image = images.get(img); canvasContainer.removeChild(image.canvas); images.remove(image); imageSizeSettings.removeChild(imageEntry); fileInputColumn.removeChild(fileInputColumnEntry); if (imageSizeSettings.children.length === 1) { allSameSizeButton.style.display = 'none'; } if (images.length() === 0) noFileSelected.style.display = 'block'; update(); }; rb.onclick = removeButtonOnClick; fileInputColumnEntryRemoveButton.onclick = removeButtonOnClick; fileInputColumnEntry.appendChild(fileInputColumnEntryLabel); fileInputColumnEntry.appendChild(fileInputColumnEntryRemoveButton); fileInputColumn.appendChild(fileInputColumnEntry); imageEntry.appendChild(fn); imageEntry.appendChild(w); imageEntry.appendChild(document.createTextNode(' x ')); imageEntry.appendChild(h); imageEntry.appendChild(gil); imageEntry.appendChild(gi); imageEntry.appendChild(rb); imageSizeSettings.appendChild(imageEntry); canvas.width = img.width; canvas.height = img.height; canvasContainer.appendChild(canvas); images.push(img, canvas, file.name.split('.')[0]); placeImage(images.last()); allSameSize(images, files); }; img.src = e.target.result; }; }(files[i])); reader.readAsDataURL(files[i]); } } function imageToString(image) { // extract raw image data const { ctx } = image; const { canvas } = image; const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const { data } = imageData; return settings.conversionFunction(data, canvas.width, canvas.height); } // Get the custom arduino output variable name, if any function getIdentifier() { const vn = document.getElementById('identifier'); return vn && vn.value.length ? vn.value : identifier; } // Output the image string to the textfield // eslint-disable-next-line no-unused-vars function generateOutputString() { outputString = ''; let code = ''; switch (settings.outputFormat) { case 'arduino': { const varQuickArray = []; let bytesUsed = 0; // -- images.each((image) => { code = imageToString(image); // Trim whitespace from end and remove trailing comma code = code.replace(/,\s*$/, ''); code = `\t${code.split('\n').join('\n\t')}\n`; // const variableCount = images.length() > 1 ? count++ : ''; const comment = `// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`; bytesUsed += code.split('\n').length * 16; // 16 bytes per line. const varname = getIdentifier() + image.glyph.replace(/[^a-zA-Z0-9]/g, '_'); varQuickArray.push(varname); code = `${comment}const ${getType()} ${varname} [] PROGMEM = {\n${code}};\n`; outputString += code; }); // -- varQuickArray.sort(); // -- outputString += `\n// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = ${bytesUsed})\n`; outputString += `const int ${getIdentifier()}allArray_LEN = ${varQuickArray.length};\n`; outputString += `const ${getType()}* ${getIdentifier()}allArray[${varQuickArray.length}] = {\n\t${varQuickArray.join(',\n\t')}\n};\n`; // -- break; } case 'arduino_single': { let comment = ''; images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; comment = `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n`; outputString += comment + code; }); outputString = outputString.replace(/,\s*$/, ''); outputString = `const ${getType()} ${ +getIdentifier() } [] PROGMEM = {` + `\n${outputString}\n};`; break; } case 'adafruit_gfx': { // bitmap let comment = ''; let useGlyphs = 0; images.each((image) => { code = imageToString(image); code = `\t${code.split('\n').join('\n\t')}\n`; comment = `\t// '${image.glyph}, ${image.canvas.width}x${image.canvas.height}px\n`; outputString += comment + code; if (image.glyph.length === 1) { useGlyphs++; } }); outputString = outputString.replace(/,\s*$/, ''); outputString = `const unsigned char ${ getIdentifier() }Bitmap` + ' [] PROGMEM = {' + `\n${outputString}\n};\n\n` + `const GFXbitmapGlyph ${ getIdentifier() }Glyphs [] PROGMEM = {\n`; let firstAschiiChar = document.getElementById('first-ascii-char').value; const xAdvance = parseInt(document.getElementById('x-advance').value); let offset = 0; code = ''; // GFXbitmapGlyph images.each((image) => { code += `\t{ ${ offset}, ${ image.canvas.width}, ${ image.canvas.height}, ${ xAdvance}, ` + `'${images.length() === useGlyphs ? image.glyph : String.fromCharCode(firstAschiiChar++)}'` + ' }'; if (image !== images.last()) { code += ','; } code += `// '${image.glyph}'\n`; offset += image.canvas.width; }); code += '};\n'; outputString += code; // GFXbitmapFont outputString += `\nconst GFXbitmapFont ${ getIdentifier() }Font PROGMEM = {\n` + `\t(uint8_t *)${ getIdentifier()}Bitmap,\n` + `\t(GFXbitmapGlyph *)${ getIdentifier() }Glyphs,\n` + `\t${images.length() }\n};\n`; break; } default: { // plain images.each((image) => { code = imageToString(image); let comment = ''; if (image.glyph) { comment = (`// '${image.glyph}', ${image.canvas.width}x${image.canvas.height}px\n`); } if (image.img !== images.first().img) { comment = `\n${comment}`; } code = comment + code; outputString += code; }); // Trim whitespace from end and remove trailing comma outputString = outputString.replace(/,\s*$/g, ''); } } document.getElementById('code-output').value = outputString; output = outputString; document.getElementById('copy-button').disabled = false; } // eslint-disable-next-line no-unused-vars function copyOutput() { navigator.clipboard.writeText(output); } // eslint-disable-next-line no-unused-vars function downloadFile() { let raw = []; images.each((image) => { const data = imageToString(image) .split(',') .map((s) => s.trim()) .filter(Boolean) .map((byte) => parseInt(byte, 16)); raw = raw.concat(data); }); const data = new Uint8Array(raw); const a = document.createElement('a'); a.style = 'display: none'; document.body.appendChild(a); const blob = new Blob([data], { type: 'octet/stream' }); const url = window.URL.createObjectURL(blob); a.href = url; a.download = `${getIdentifier()}.bin`; a.click(); window.URL.revokeObjectURL(url); } // eslint-disable-next-line no-unused-vars function updateDrawMode(elm) { // var note = document.getElementById("note1bit"); // if(elm.value == "horizontal1bit" || elm.value == "vertical1bit") { // note.style.display = "block"; // } else { // note.style.display = "none"; // } const conversionFunction = ConversionFunctions[elm.value]; if (conversionFunction) { settings.conversionFunction = conversionFunction; } } // Updates Arduino code check-box // eslint-disable-next-line no-unused-vars function updateOutputFormat(elm) { let caption = document.getElementById('format-caption-container'); const adafruitGfx = document.getElementById('adafruit-gfx-settings'); const arduino = document.getElementById('arduino-identifier'); const removeZeroesCommasContainer = document.getElementById('remove-zeroes-commas-container'); for (let i = 0; i < caption.children.length; i++) { caption.children[i].style.display = 'none'; } caption = document.querySelector(`div[data-caption='${elm.value}']`); if (caption) caption.style.display = 'block'; if (elm.value !== 'plain') { arduino.style.display = 'block'; removeZeroesCommasContainer.style.display = 'none'; settings.removeZeroesCommas = false; } else { arduino.style.display = 'none'; removeZeroesCommasContainer.style.display = 'table-row'; } if (elm.value === 'adafruit_gfx') { adafruitGfx.style.display = 'block'; } else { adafruitGfx.style.display = 'none'; } settings.outputFormat = elm.value; } // Easy way to update settings controlled by a radiobutton // eslint-disable-next-line no-unused-vars function updateRadio(fieldName) { const radioGroup = document.getElementsByName(fieldName); for (let i = 0; i < radioGroup.length; i++) { if (radioGroup[i].checked) { settings[fieldName] = radioGroup[i].value; } } update(); } window.onload = () => { document.getElementById('copy-button').disabled = true; // Add events to the file input button const fileInput = document.getElementById('file-input'); fileInput.addEventListener('click', () => { this.value = null; }, false); fileInput.addEventListener('change', handleImageSelection, false); // The variable to hold our images. Global so we can easily reuse it when the // user updates the settings (change canvas size, scale, invert, etc) // // Make the canvas black and white // function blackAndWhite(canvas, ctx){ // var imageData = ctx.getImageData(0,0,canvas.width, canvas.height); // var data = imageData.data; // for (var i = 0; i < data.length; i += 4) { // var avg = (data[i] + data[i +1] + data[i +2]) / 3; // avg > settings["threshold"] ? avg = 255 : avg = 0; // data[i] = avg; // red // data[i + 1] = avg; // green // data[i + 2] = avg; // blue // } // ctx.putImageData(imageData, 0, 0); // } document.getElementById('outputFormat').value = 'arduino'; document.getElementById('outputFormat').onchange(); };