mirror of
https://github.com/javl/image2cpp.git
synced 2026-09-11 17:13:20 +00:00
593 lines
18 KiB
HTML
593 lines
18 KiB
HTML
<!--
|
|
"image2cpp"
|
|
Original utility by:
|
|
https://jaspervanloenen.com
|
|
|
|
This is a modified version by:
|
|
https://wiredolphine.net
|
|
-->
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>image2cpp</title>
|
|
<style type="text/css">
|
|
body{
|
|
font-family: arial;
|
|
}
|
|
.wrapper{
|
|
width: 800px;
|
|
margin: auto;
|
|
}
|
|
.numberInput{
|
|
width: 50px;
|
|
}
|
|
label{
|
|
width: 220px;
|
|
display: inline-block;
|
|
font-weight: bold;
|
|
}
|
|
label.smallLabel{
|
|
width: auto;
|
|
}
|
|
td{
|
|
text-align: left;
|
|
vertical-align: top;
|
|
}
|
|
td.or{
|
|
padding: 20px;
|
|
width: 100px;
|
|
font-size: 30px;
|
|
/*vertical-align: center;*/
|
|
}
|
|
hr{
|
|
height: 3px;
|
|
background-color: black;
|
|
border: none;
|
|
}
|
|
img.inlineImg{
|
|
display: inline-block;
|
|
}
|
|
textarea.fullWidth{
|
|
width: 100%;
|
|
height: 100px;
|
|
}
|
|
table{
|
|
width: 100%;
|
|
}
|
|
h1{
|
|
margin: 0;
|
|
}
|
|
#variableNameContainer {
|
|
display: none;
|
|
margin: 0;
|
|
}
|
|
canvas{
|
|
border: 1px solid black;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrapper">
|
|
<h1>image2cpp</h1>
|
|
<p>
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
<h2>1. Select image</h2>
|
|
<input type="file" id="fileInput" name="fileinput"/><br />
|
|
<label>Selected image size:</label> <span id="imageSize">no image selected</span>
|
|
</h4>
|
|
<td class="or">or</td>
|
|
<td>
|
|
<h2>1. Paste byte array</h2>
|
|
<textarea id="textInput" class="fullWidth"></textarea><br />
|
|
<button onclick="handleTextInput('horizontal')">Read as horizontal</button>
|
|
<button onclick="handleTextInput('vertical')">Read as vertical</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</p>
|
|
<hr />
|
|
|
|
<p>
|
|
<h2>2. Image Settings</h2>
|
|
<label>Canvas size: </label> <input id="screenWidth" class="numberInput" type="text" name="screenWidth" onchange="updateInteger('screenWidth')" value="128"/> * <input id="screenHeight" class="numberInput" type="text" name="screenHeight" onchange="updateInteger('screenHeight')" value="64"/> pixels<br />
|
|
<label>Background color:</label>
|
|
<input id="backgroundColorWhite" type="radio" name="backgroundColor" value="white" checked="checked" onchange="updateRadio('backgroundColor')"/><label for="backgroundColorWhite" class="smallLabel">White</label>
|
|
<input id="backgroundColorBlack" type="radio" name="backgroundColor" value="black" onchange="updateRadio('backgroundColor')"/><label for="backgroundColorBlack" class="smallLabel">Black</label><br />
|
|
<label>Brightness threshold: </label> <input id="threshold" class="numberInput" type="text" name="threshold" onchange="updateInteger('threshold')" value="128"/> <i>0 - 255; pixels with brightness above become white, below become black.</i><br/>
|
|
<label for="scale">Scaling</label>
|
|
<select id="scale" name="scale" onchange="updateInteger('scale')">
|
|
<option value="1">original size</option>
|
|
<option value="2">scale to fit, keeping proportions</option>
|
|
<option value="3">stretch to fill canvas</option>
|
|
<option value="4">stretch to fill canvas horizontally</option>
|
|
<option value="5">stretch to fill canvas vertically</option>
|
|
</select><br />
|
|
|
|
<label for="centerHorizontally">Center horizontally</label><input id="centerHorizontally" type="checkbox" onchange="updateBoolean('centerHorizontally')" /><br />
|
|
<label for="centerVertically">Center vertically</label><input id="centerVertically" type="checkbox" onchange="updateBoolean('centerVertically')" /><br />
|
|
<i>Centering the image only works when using a canvas larger than the selected image.</i><br />
|
|
<label for="invertColors">Invert image colors</label><input id="invertColors" type="checkbox" onchange="updateBoolean('invertColors')" />
|
|
</p>
|
|
|
|
<hr />
|
|
|
|
<p>
|
|
<h2>3. Preview</h2>
|
|
<canvas id="previewCanvas" width="128" height="64"></canvas>
|
|
</p>
|
|
|
|
<hr />
|
|
|
|
<p>
|
|
<h2>4. Output</h2>
|
|
<label for="addArduinoCode">Add Arduino code</label><input id="addArduinoCode" type="checkbox" onchange="updateArduinoCode(this)" /><br />
|
|
<i>Adds some extra Arduino code around the output for easy copy-paste into <a href="https://github.com/javl/image2cpp/blob/master/oled_example/oled_example.ino" target="_blank">this example</a>.</i><br />
|
|
<p id="variableNameContainer">
|
|
<label>Variable name:</label>
|
|
<input id="variableName" class="textInput" type="text" name="variableName" onchange="" value="myBitmap"/>
|
|
</p>
|
|
<br />
|
|
<label>Draw mode:</label>
|
|
<input id="drawModeHorizontal" type="radio" name="drawMode" value="horizontal" checked="checked" onchange="updateRadio('drawMode')"/><label for="drawModeHorizontal" class="smallLabel">Horizontal</label>
|
|
<input id="drawModeVertical" type="radio" name="drawMode" value="vertical" onchange="updateRadio('drawMode')"/><label for="drawModeVertical" class="smallLabel">Vertical</label><br />
|
|
<i>If your image looks all messed up on your display, like the image below, try the other mode.</i><br />
|
|
<br />
|
|
<img class="inlineImg" src="img/error.jpg" width="150" height="64" alt="" /><br /><br />
|
|
<button type="button" onclick="outputString()">Generate code</button><br /><br />
|
|
<textarea id="outputField" class="fullWidth"></textarea>
|
|
</p>
|
|
</div>
|
|
<script type="text/javascript">
|
|
|
|
// Add events to the file input button
|
|
var fileInput = document.getElementById('fileInput');
|
|
fileInput.addEventListener('click', function(){this.value = null;}, false);
|
|
fileInput.addEventListener('change', handleImageSelection, false);
|
|
|
|
|
|
// Filetypes accepted by the file picker
|
|
var fileTypes = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg'];
|
|
|
|
|
|
// The canvas we will draw on
|
|
var canvas = document.getElementById('previewCanvas');
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
|
|
// The variable to hold our image. Global so we can easily reuse it when the
|
|
// user updates the settings (change canvas size, scale, invert, etc)
|
|
var img = new Image();
|
|
|
|
|
|
// A bunch of settings used when converting
|
|
var settings = {
|
|
screenWidth: 128,
|
|
screenHeight: 64,
|
|
scaleToFit: true,
|
|
preserveRatio: true,
|
|
centerHorizontally: false,
|
|
centerVertically: false,
|
|
backgroundColor: "white",
|
|
scale: '1',
|
|
drawMode: 'horizontal',
|
|
threshold: 128,
|
|
addArduinoCode: false,
|
|
invertColors: false
|
|
};
|
|
|
|
// Variable name, when 'arduino code' is required
|
|
var variableName = 'myBitmap';
|
|
|
|
|
|
// Easy way to update settings controlled by a textfield
|
|
function updateInteger(fieldName){
|
|
settings[fieldName] = document.getElementById(fieldName).value;
|
|
place_image();
|
|
}
|
|
|
|
|
|
// Easy way to update settings controlled by a checkbox
|
|
function updateBoolean(fieldName){
|
|
settings[fieldName] = document.getElementById(fieldName).checked;
|
|
place_image();
|
|
}
|
|
|
|
|
|
// Easy way to update settings controlled by a radiobutton
|
|
function updateRadio(fieldName){
|
|
var radioGroup = document.getElementsByName(fieldName);
|
|
for (var i = 0; i < radioGroup.length; i++) {
|
|
if (radioGroup[i].checked) {
|
|
settings[fieldName] = radioGroup[i].value;
|
|
}
|
|
}
|
|
place_image();
|
|
}
|
|
|
|
// Updates Arduino code check-box
|
|
function updateArduinoCode(checkbox) {
|
|
var c = document.getElementById('variableNameContainer');
|
|
c && checkbox.checked ? c.style.display = "block" : c.style.display = "none";
|
|
settings['addArduinoCode'] = checkbox.checked;
|
|
}
|
|
|
|
// Make the canvas black and white
|
|
function blackAndWhite(){
|
|
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);
|
|
};
|
|
|
|
|
|
// Invert the colors of the canvas
|
|
function invert() {
|
|
var imageData = ctx.getImageData(0,0,canvas.width, canvas.height);
|
|
var data = imageData.data;
|
|
for (var 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 place_image(){
|
|
|
|
// Make sure we're using the right canvas size
|
|
canvas.width = settings['screenWidth'];
|
|
canvas.height = settings['screenHeight'];
|
|
|
|
// Invert background if needed
|
|
if (settings['invertColors']){
|
|
settings['backgroundColor'] == 'white' ? ctx.fillStyle = 'black' : ctx.fillStyle = 'white';
|
|
}else{
|
|
ctx.fillStyle = settings['backgroundColor'];
|
|
}
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Offset used for centering the image when requested
|
|
var offset_x = 0;
|
|
var offset_y = 0;
|
|
|
|
switch(settings['scale']){
|
|
case '1': // Original
|
|
if(settings['centerHorizontally']){ offset_x = (canvas.width - img.width) / 2; }
|
|
if(settings['centerVertically']){ offset_y = (canvas.height - img.height) / 2; }
|
|
ctx.drawImage(img, 0, 0, img.width, img.height,
|
|
offset_x, offset_y, img.width, img.height);
|
|
break;
|
|
case '2': // Fit (make as large as possible without changing ratio)
|
|
var horRatio = canvas.width / img.width;
|
|
var verRatio = canvas.height / img.height;
|
|
var useRatio = Math.min(horRatio, verRatio);
|
|
|
|
if(settings['centerHorizontally']){ offset_x = (canvas.width - img.width*useRatio) / 2; }
|
|
if(settings['centerVertically']){ offset_y = (canvas.height - img.height*useRatio) / 2; }
|
|
ctx.drawImage(img, 0, 0, img.width, img.height,
|
|
offset_x, offset_y, img.width * useRatio, img.height * useRatio);
|
|
break;
|
|
case '3': // Stretch x+y (make as large as possible without keeping ratio)
|
|
ctx.drawImage(img, 0, 0, img.width, img.height,
|
|
offset_x, offset_y, canvas.width, canvas.height);
|
|
break;
|
|
case '4': // Stretch x (make as wide as possible)
|
|
offset_x = 0;
|
|
if(settings['centerVertically']){ offset_y = (canvas.height - img.height) / 2; }
|
|
ctx.drawImage(img, 0, 0, img.width, img.height,
|
|
offset_x, offset_y, canvas.width, img.height);
|
|
break;
|
|
case '5': // Stretch y (make as tall as possible)
|
|
if(settings['centerHorizontally']){ offset_x = (canvas.width - img.width) / 2; }
|
|
offset_y = 0;
|
|
ctx.drawImage(img, 0, 0, img.width, img.height,
|
|
offset_x, offset_y, img.width, canvas.height);
|
|
break;
|
|
}
|
|
// Make sure the image is black and white
|
|
blackAndWhite();
|
|
if(settings['invertColors']){
|
|
invert();
|
|
}
|
|
}
|
|
|
|
|
|
// Handle inserting an image by pasting code
|
|
function handleTextInput(drawMode){
|
|
|
|
var input = document.getElementById('textInput').value;
|
|
|
|
// Remove Arduino code
|
|
input = input.replace(/const unsigned char myBitmap \[\] PROGMEM = \{/g, "");
|
|
input = input.replace(/\};/g, "");
|
|
|
|
// Remove newlines
|
|
input = input.replace(/\r\n|\r|\n/g, "");
|
|
// Remove whitespace
|
|
input = input.replace(/\s/g, "");
|
|
// Remove "0x"
|
|
input = input.replace(/0x/g, "");
|
|
// Split into list
|
|
var list = input.split(",");
|
|
|
|
if(drawMode == 'horizontal'){
|
|
listToImageHorizontal(list);
|
|
}else{
|
|
listToImageVertical(list);
|
|
}
|
|
}
|
|
|
|
|
|
// Handle selecting an image with the file picker
|
|
function handleImageSelection(e){
|
|
var reader = new FileReader();
|
|
reader.onload = function(event){
|
|
// Crude check to see if the uploaded file is an image
|
|
if (reader.result.substring (0, 10) != "data:image"){
|
|
alert("Please select an image file.");
|
|
return false;
|
|
}
|
|
|
|
img.onload = function(){
|
|
// Show the size underneath the file picker
|
|
document.getElementById('imageSize').innerHTML = img.width+" * " + img.height + " pixels";
|
|
|
|
// Resize the canvas and update the input fields
|
|
document.getElementById('screenWidth').value = img.width;
|
|
document.getElementById('screenHeight').value = img.height;
|
|
settings['screenWidth'] = img.width;
|
|
settings['screenHeight'] = img.height;
|
|
|
|
place_image();
|
|
}
|
|
img.src = event.target.result;
|
|
}
|
|
reader.readAsDataURL(e.target.files[0]);
|
|
}
|
|
|
|
|
|
// Output the image as a string for horizontally drawing displays
|
|
function imageToStringHorizontal(){
|
|
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
var output_string = "";
|
|
var output_index = 0;
|
|
|
|
var byteIndex = 7;
|
|
var number = 0;
|
|
|
|
for (var index=0; index < imageData.data.length; index+=4){
|
|
var avg = (imageData.data[index] + imageData.data[index +1] + imageData.data[index +2]) / 3;
|
|
if (avg > settings['threshold']){
|
|
number += Math.pow(2, byteIndex);
|
|
}
|
|
byteIndex--;
|
|
|
|
if(byteIndex < 0){
|
|
var byteSet = number.toString(16);
|
|
if (byteSet.length == 1){ byteSet = "0"+byteSet; }
|
|
var b = "0x"+byteSet;
|
|
output_string += b+", ";
|
|
output_index++;
|
|
if (output_index >= 16){
|
|
output_string += "\n";
|
|
output_index = 0;
|
|
}
|
|
number = 0;
|
|
byteIndex = 7;
|
|
}
|
|
}
|
|
return output_string;
|
|
}
|
|
|
|
|
|
// Output the image as a string for vertically drawing displays
|
|
function imageToStringVertical(){
|
|
var x = 0;
|
|
var y = 7;
|
|
var page = 0;
|
|
|
|
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
var data = imageData.data;
|
|
|
|
var output_string = "";
|
|
var output_index = 0;
|
|
|
|
|
|
for (var p=0; p<Math.ceil(settings['screenHeight']/8);p++){
|
|
for (var x=0;x<=settings['screenWidth'];x++){
|
|
// for (var x=0;x<1;x++){
|
|
var byteIndex = 7;
|
|
var number = 0;
|
|
|
|
for (var y = 7; y >= 0; y--){
|
|
var index = ((p*8)+y)*(settings['screenWidth']*4)+x;
|
|
var avg = (data[index] + data[index +1] + data[index +2]) / 3;
|
|
if (avg > settings['threshold']){
|
|
number += Math.pow(2, byteIndex);
|
|
}
|
|
byteIndex--;
|
|
}
|
|
var byteSet = number.toString(16);
|
|
if (byteSet.length == 1){ byteSet = "0"+byteSet; }
|
|
var b = "0x"+byteSet.toString(16);
|
|
output_string += b+", ";
|
|
output_index++;
|
|
if (output_index >= 16){
|
|
output_string += "\n";
|
|
output_index = 0;
|
|
}
|
|
}
|
|
}
|
|
return output_string;
|
|
}
|
|
|
|
|
|
// Get the custom arduino output variable name, if any
|
|
function getVariableName() {
|
|
var vn = document.getElementById('variableName');
|
|
return vn && vn.value.length ? vn.value : variableName;
|
|
}
|
|
|
|
|
|
// Output the image string to the textfield
|
|
function outputString(){
|
|
var output_string = "";
|
|
if (settings['drawMode'] == 'horizontal'){
|
|
output_string = imageToStringHorizontal();
|
|
}else{
|
|
output_string = imageToStringVertical();
|
|
}
|
|
|
|
if(settings['addArduinoCode']){
|
|
output_string = "const unsigned char " + getVariableName() + " [] PROGMEM = {\n" + output_string + "\n};";
|
|
}
|
|
document.getElementById('outputField').value = output_string;
|
|
}
|
|
|
|
|
|
// Use the horizontally oriented list to draw the image
|
|
function listToImageHorizontal(list){
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
var imgData = ctx.createImageData(canvas.width, canvas.height);
|
|
|
|
var index = 0;
|
|
|
|
var page = 0;
|
|
var x = 0;
|
|
var y = 7;
|
|
|
|
// Move the list into the imageData object
|
|
for (var i=0;i<list.length;i++){
|
|
var binString = hexToBinary(list[i]).result;
|
|
if (binString.length == 4){
|
|
binString = binString + "0000";
|
|
}
|
|
|
|
// Check if pixel is white or black
|
|
for(var k=0; k<binString.length; k++){
|
|
var 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);
|
|
img.src = canvas.toDataURL('image/png');
|
|
}
|
|
|
|
|
|
// Use the vertically oriented list to draw the image
|
|
function listToImageVertical(list){
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Create a new imageData object, but 90 degrees rotates (height x width)
|
|
// var imgData = ctx.createImageData(canvas.width, canvas.height);
|
|
// var imgData = ctx.getImageData(0, 0, canvas.height, canvas.width);
|
|
//var imgData = ctx.createImageData(canvas.width, canvas.height);
|
|
// var data = imgData.data;
|
|
|
|
var index = 0;
|
|
|
|
var page = 0;
|
|
var x = 0;
|
|
var y = 7;
|
|
|
|
// Move the list into the imageData object
|
|
for (var i=0;i<list.length;i++){
|
|
|
|
var binString = hexToBinary(list[i]).result;
|
|
if (binString.length == 4){
|
|
binString = binString + "0000";
|
|
}
|
|
|
|
// Check if pixel is white or black
|
|
for(var k=0; k<binString.length; k++){
|
|
var color = 0;
|
|
if(binString.charAt(k) == '1'){
|
|
color = 255;
|
|
}
|
|
// color = 0;
|
|
drawPixel(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.
|
|
img.src = canvas.toDataURL('image/png');
|
|
}
|
|
|
|
|
|
// Convert hex to binary
|
|
function hexToBinary(s) {
|
|
|
|
var i, k, part, ret = '';
|
|
// lookup table for easier conversion. '0' characters are padded for '1' to '7'
|
|
var 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) {
|
|
if (lookupTable.hasOwnProperty(s[i])) {
|
|
ret += lookupTable[s[i]];
|
|
} else {
|
|
return { valid: false };
|
|
}
|
|
}
|
|
return { valid: true, result: ret };
|
|
}
|
|
|
|
|
|
// Quick and effective way to draw single pixels onto the canvas
|
|
// using a global 1x1px large canvas
|
|
var single_pixel = ctx.createImageData(1,1);
|
|
var d = single_pixel.data;
|
|
|
|
function drawPixel(x, y, color){
|
|
d[0] = color;
|
|
d[1] = color;
|
|
d[2] = color;
|
|
d[3] = 255;
|
|
ctx.putImageData(single_pixel, x, y);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|