mirror of
https://github.com/javl/image2cpp.git
synced 2026-09-11 09:03:04 +00:00
Create a small writer method to reuse with all conversion types instead of handling formatting separate for each type
This commit is contained in:
+109
-172
@@ -36,54 +36,65 @@ function bitswap(b) {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConversionFunctions = {
|
// Accumulates formatted bytes (prefix/value/separator, wrapped every
|
||||||
// Output the image as a string for horizontally drawing displays
|
// wrapWidth bytes) so our ConversionFunctions can use this formatting.
|
||||||
horizontal1bit(data, canvasWidth) {
|
// wrapWidth defaults to 16 bytes per line; horizontal888 wraps
|
||||||
let stringFromBytes = '';
|
// once per image row (canvasWidth) instead.
|
||||||
let outputIndex = 0;
|
function makeByteWriter(wrapWidth = 16) {
|
||||||
|
let out = '';
|
||||||
|
let count = 0;
|
||||||
|
return {
|
||||||
|
push(value, hexWidth) {
|
||||||
|
const byteSet = bitswap(value).toString(16).padStart(hexWidth, '0');
|
||||||
|
out += `${settings.prefix}${byteSet}${settings.separator}`;
|
||||||
|
count++;
|
||||||
|
if (count >= wrapWidth) {
|
||||||
|
out += '\n';
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result() { return out; },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared by horizontal1bit/horizontalAlpha: pack 8 samples (one per pixel,
|
||||||
|
// MSB first) into a byte, resetting early at the end of a row or the image
|
||||||
|
// so a partial final byte is still zero-padded instead of dropped.
|
||||||
|
function packBitsHorizontal(data, canvasWidth, writer, sampleAt) {
|
||||||
let byteIndex = 7;
|
let byteIndex = 7;
|
||||||
let number = 0;
|
let number = 0;
|
||||||
|
|
||||||
// format is RGBA, so move 4 steps per pixel
|
|
||||||
for (let index = 0; index < data.length; index += 4) {
|
for (let index = 0; index < data.length; index += 4) {
|
||||||
// Get the average of the RGB (we ignore A)
|
if (sampleAt(data, index) > settings.ditheringThreshold) {
|
||||||
const avg = (data[index] + data[index + 1] + data[index + 2]) / 3;
|
|
||||||
if (avg > settings.ditheringThreshold) {
|
|
||||||
number += 2 ** byteIndex;
|
number += 2 ** byteIndex;
|
||||||
}
|
}
|
||||||
byteIndex--;
|
byteIndex--;
|
||||||
|
|
||||||
// if this was the last pixel of a row or the last pixel of the
|
const isRowEnd = index !== 0 && (((index / 4) + 1) % canvasWidth) === 0;
|
||||||
// image, fill up the rest of our byte with zeros so it always contains 8 bits
|
const isImageEnd = index === data.length - 4;
|
||||||
if ((index !== 0 && (((index / 4) + 1) % (canvasWidth)) === 0) || (index === data.length - 4)) {
|
if (isRowEnd || isImageEnd) {
|
||||||
// for(var i=byteIndex;i>-1;i--){
|
|
||||||
// number += Math.pow(2, i);
|
|
||||||
// }
|
|
||||||
byteIndex = -1;
|
byteIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we have the complete 8 bits, combine them into a hex value
|
|
||||||
if (byteIndex < 0) {
|
if (byteIndex < 0) {
|
||||||
let byteSet = bitswap(number).toString(16);
|
writer.push(number, 2);
|
||||||
if (byteSet.length === 1) { byteSet = `0${byteSet}`; }
|
|
||||||
stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`;
|
|
||||||
outputIndex++;
|
|
||||||
if (outputIndex >= 16) {
|
|
||||||
stringFromBytes += '\n';
|
|
||||||
outputIndex = 0;
|
|
||||||
}
|
|
||||||
number = 0;
|
number = 0;
|
||||||
byteIndex = 7;
|
byteIndex = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stringFromBytes;
|
}
|
||||||
|
|
||||||
|
const ConversionFunctions = {
|
||||||
|
// Output the image as a string for horizontally drawing displays
|
||||||
|
horizontal1bit(data, canvasWidth) {
|
||||||
|
const writer = makeByteWriter();
|
||||||
|
const avgRgb = (d, i) => (d[i] + d[i + 1] + d[i + 2]) / 3;
|
||||||
|
packBitsHorizontal(data, canvasWidth, writer, avgRgb);
|
||||||
|
return writer.result();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Output the image as a string for vertically drawing displays
|
// Output the image as a string for vertically drawing displays
|
||||||
// eslint-disable-next-line no-unused-vars
|
vertical1bit(data) {
|
||||||
vertical1bit(data, canvasWidth) {
|
const writer = makeByteWriter();
|
||||||
let stringFromBytes = '';
|
|
||||||
let outputIndex = 0;
|
|
||||||
for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) {
|
for (let p = 0; p < Math.ceil(settings.screenHeight / 8); p++) {
|
||||||
for (let x = 0; x < settings.screenWidth; x++) {
|
for (let x = 0; x < settings.screenWidth; x++) {
|
||||||
let byteIndex = 7;
|
let byteIndex = 7;
|
||||||
@@ -97,118 +108,49 @@ const ConversionFunctions = {
|
|||||||
}
|
}
|
||||||
byteIndex--;
|
byteIndex--;
|
||||||
}
|
}
|
||||||
let byteSet = bitswap(number).toString(16);
|
writer.push(number, 2);
|
||||||
if (byteSet.length === 1) { byteSet = `0${byteSet}`; }
|
|
||||||
stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`;
|
|
||||||
outputIndex++;
|
|
||||||
if (outputIndex >= 16) {
|
|
||||||
stringFromBytes += '\n';
|
|
||||||
outputIndex = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return writer.result();
|
||||||
return stringFromBytes;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Output the image as a string for 565 displays (horizontally)
|
// Output the image as a string for 565 displays (horizontally)
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
horizontal565(data, canvasWidth) {
|
horizontal565(data, canvasWidth) {
|
||||||
let stringFromBytes = '';
|
const writer = makeByteWriter();
|
||||||
let outputIndex = 0;
|
|
||||||
|
|
||||||
// format is RGBA, so move 4 steps per pixel
|
// format is RGBA, so move 4 steps per pixel
|
||||||
for (let index = 0; index < data.length; index += 4) {
|
for (let index = 0; index < data.length; index += 4) {
|
||||||
// Get the RGB values
|
|
||||||
const r = data[index];
|
const r = data[index];
|
||||||
const g = data[index + 1];
|
const g = data[index + 1];
|
||||||
const b = data[index + 2];
|
const b = data[index + 2];
|
||||||
// calculate the 565 color value
|
// Calculate the 565 color value
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3);
|
const rgb = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3);
|
||||||
// Split up the color value in two bytes
|
writer.push(rgb, 4);
|
||||||
// const firstByte = (rgb >> 8) & 0xff;
|
|
||||||
// const secondByte = rgb & 0xff;
|
|
||||||
|
|
||||||
let byteSet = bitswap(rgb).toString(16);
|
|
||||||
while (byteSet.length < 4) { byteSet = `0${byteSet}`; }
|
|
||||||
stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`;
|
|
||||||
// add newlines every 16 bytes
|
|
||||||
outputIndex++;
|
|
||||||
if (outputIndex >= 16) {
|
|
||||||
stringFromBytes += '\n';
|
|
||||||
outputIndex = 0;
|
|
||||||
}
|
}
|
||||||
}
|
return writer.result();
|
||||||
return stringFromBytes;
|
|
||||||
},
|
},
|
||||||
// Output the image as a string for rgb888 displays (horizontally)
|
// Output the image as a string for rgb888 displays (horizontally), one
|
||||||
|
// image row per output line
|
||||||
horizontal888(data, canvasWidth) {
|
horizontal888(data, canvasWidth) {
|
||||||
let stringFromBytes = '';
|
const writer = makeByteWriter(canvasWidth);
|
||||||
let outputIndex = 0;
|
|
||||||
|
|
||||||
// format is RGBA, so move 4 steps per pixel
|
// format is RGBA, so move 4 steps per pixel
|
||||||
for (let index = 0; index < data.length; index += 4) {
|
for (let index = 0; index < data.length; index += 4) {
|
||||||
// Get the RGB values
|
// Split into RGB and pack into a single 24-bit value (MSB first)
|
||||||
const r = data[index];
|
const r = data[index];
|
||||||
const g = data[index + 1];
|
const g = data[index + 1];
|
||||||
const b = data[index + 2];
|
const b = data[index + 2];
|
||||||
// calculate the 565 color value
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
const rgb = (r << 16) | (g << 8) | (b);
|
const rgb = (r << 16) | (g << 8) | (b);
|
||||||
// Split up the color value in two bytes
|
writer.push(rgb, 8);
|
||||||
// const firstByte = (rgb >> 8) & 0xff;
|
|
||||||
// const secondByte = rgb & 0xff;
|
|
||||||
|
|
||||||
let byteSet = bitswap(rgb).toString(16);
|
|
||||||
while (byteSet.length < 8) { byteSet = `0${byteSet}`; }
|
|
||||||
stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`;
|
|
||||||
|
|
||||||
// add newlines every 16 bytes
|
|
||||||
outputIndex++;
|
|
||||||
if (outputIndex >= canvasWidth) {
|
|
||||||
stringFromBytes += '\n';
|
|
||||||
outputIndex = 0;
|
|
||||||
}
|
}
|
||||||
}
|
return writer.result();
|
||||||
return stringFromBytes;
|
|
||||||
},
|
},
|
||||||
// Output the alpha mask as a string for horizontally drawing displays
|
// Output the alpha mask as a string for horizontally drawing displays
|
||||||
horizontalAlpha(data, canvasWidth) {
|
horizontalAlpha(data, canvasWidth) {
|
||||||
let stringFromBytes = '';
|
const writer = makeByteWriter();
|
||||||
let outputIndex = 0;
|
packBitsHorizontal(data, canvasWidth, writer, (d, i) => d[i + 3]);
|
||||||
let byteIndex = 7;
|
return writer.result();
|
||||||
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}`; }
|
|
||||||
stringFromBytes += `${settings.prefix}${byteSet}${settings.separator}`;
|
|
||||||
outputIndex++;
|
|
||||||
if (outputIndex >= 16) {
|
|
||||||
stringFromBytes += '\n';
|
|
||||||
outputIndex = 0;
|
|
||||||
}
|
|
||||||
number = 0;
|
|
||||||
byteIndex = 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stringFromBytes;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
settings.conversionFunction = ConversionFunctions.horizontal1bit;
|
settings.conversionFunction = ConversionFunctions.horizontal1bit;
|
||||||
@@ -489,10 +431,50 @@ function getImageType() {
|
|||||||
return 'unsigned char';
|
return 'unsigned char';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the horizontally oriented list to draw the image
|
||||||
|
// Validates and decodes a comma-split list of pasted hex byte strings into
|
||||||
|
// one contiguous bit string (MSB first, each entry padded to a full byte).
|
||||||
|
// Returns { valid: true, bits } or { valid: false, s } naming the bad entry.
|
||||||
|
function hexListToBits(list) {
|
||||||
|
let bits = '';
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const binString = hexToBinary(list[i]);
|
||||||
|
if (!binString.valid) {
|
||||||
|
return binString;
|
||||||
|
}
|
||||||
|
bits += binString.result.length === 4 ? `${binString.result}0000` : binString.result;
|
||||||
|
}
|
||||||
|
return { valid: true, bits };
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportInvalidByteArray(s) {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
alert('Something went wrong converting the string. Make sure there are no comments in your input.');
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('invalid hexToBinary: ', s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the canvas contents inside the (first) image object so it can be
|
||||||
|
// reused when scaling/inverting/etc.
|
||||||
|
function commitCanvasToFirstImage(canvas) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
images.first().img = img;
|
||||||
|
};
|
||||||
|
img.src = canvas.toDataURL('image/png');
|
||||||
|
}
|
||||||
|
|
||||||
// Use the horizontally oriented list to draw the image
|
// Use the horizontally oriented list to draw the image
|
||||||
function listToImageHorizontal(list, canvas) {
|
function listToImageHorizontal(list, canvas) {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const decoded = hexListToBits(list);
|
||||||
|
if (!decoded.valid) {
|
||||||
|
reportInvalidByteArray(decoded.s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const imgData = ctx.createImageData(canvas.width, canvas.height);
|
const imgData = ctx.createImageData(canvas.width, canvas.height);
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
|
||||||
@@ -501,51 +483,24 @@ function listToImageHorizontal(list, canvas) {
|
|||||||
let widthCounter = 0;
|
let widthCounter = 0;
|
||||||
|
|
||||||
// Move the list into the imageData object
|
// Move the list into the imageData object
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let k = 0; k < decoded.bits.length; k++, widthCounter++) {
|
||||||
let binString = hexToBinary(list[i]);
|
|
||||||
if (!binString.valid) {
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
alert('Something went wrong converting the string. Make sure there are no comments in your input?');
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('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 we've counted enough bits, reset counter for next line
|
||||||
if (widthCounter >= widthRoundedUp) {
|
if (widthCounter >= widthRoundedUp) {
|
||||||
widthCounter = 0;
|
widthCounter = 0;
|
||||||
}
|
}
|
||||||
// skip 'artifact' pixels due to rounding up to a byte
|
// skip 'artifact' pixels due to rounding up to a byte
|
||||||
if (widthCounter < canvas.width) {
|
if (widthCounter < canvas.width) {
|
||||||
let color = 0;
|
const color = decoded.bits.charAt(k) === '1' ? 255 : 0;
|
||||||
if (binString.charAt(k) === '1') {
|
|
||||||
color = 255;
|
|
||||||
}
|
|
||||||
imgData.data[index] = color;
|
imgData.data[index] = color;
|
||||||
imgData.data[index + 1] = color;
|
imgData.data[index + 1] = color;
|
||||||
imgData.data[index + 2] = color;
|
imgData.data[index + 2] = color;
|
||||||
imgData.data[index + 3] = 255;
|
imgData.data[index + 3] = 255;
|
||||||
|
|
||||||
index += 4;
|
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);
|
ctx.putImageData(imgData, 0, 0);
|
||||||
const img = new Image();
|
commitCanvasToFirstImage(canvas);
|
||||||
img.onload = () => {
|
|
||||||
images.first().img = img;
|
|
||||||
};
|
|
||||||
img.src = canvas.toDataURL('image/png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick and effective way to draw single pixels onto the canvas
|
// Quick and effective way to draw single pixels onto the canvas
|
||||||
@@ -566,31 +521,19 @@ function listToImageVertical(list, canvas) {
|
|||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const decoded = hexListToBits(list);
|
||||||
|
if (!decoded.valid) {
|
||||||
|
reportInvalidByteArray(decoded.s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let page = 0;
|
let page = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = 7;
|
let y = 7;
|
||||||
|
|
||||||
// Move the list into the imageData object
|
// Move the list into the imageData object
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let k = 0; k < decoded.bits.length; k++) {
|
||||||
let binString = hexToBinary(list[i]);
|
const color = decoded.bits.charAt(k) === '1' ? 255 : 0;
|
||||||
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?');
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('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);
|
drawPixel(ctx, x, (page * 8) + y, color);
|
||||||
y--;
|
y--;
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
@@ -602,14 +545,8 @@ function listToImageVertical(list, canvas) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Save the canvas contents inside the img object. This way we can
|
commitCanvasToFirstImage(canvas);
|
||||||
// reuse the img object when we want to scale / invert, etc.
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
images.first().img = img;
|
|
||||||
};
|
|
||||||
img.src = canvas.toDataURL('image/png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set width/height preset for byte array text input
|
// Set width/height preset for byte array text input
|
||||||
|
|||||||
Reference in New Issue
Block a user