Files
image2cpp/index.html
T
2016-11-02 15:09:28 +01:00

585 lines
18 KiB
HTML

<!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;
}
</style>
</head>
<body>
<div class="wrapper">
<h1>image2cpp</h1>
<p>
<a href="https://github.com/javl/image2cpp" target="_blank">Click here to visit the Github repo for this tool.</a>
</p>
<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="imageCanvas" style="border: 1px solid black;" 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('imageCanvas');
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>