mirror of
https://github.com/javl/image2cpp.git
synced 2026-09-11 09:03:04 +00:00
380 lines
31 KiB
HTML
380 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>image2cpp</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<script src="./js/dithering.js" defer></script>
|
|
<script src="./js/script.js" defer></script>
|
|
<link rel="stylesheet" href="./css/style.css">
|
|
</head>
|
|
<body>
|
|
<header class="topnav">
|
|
<div class="topnav-inner">
|
|
<div class="topnav-left">
|
|
<span class="brand">image2cpp</span>
|
|
<nav class="topnav-links">
|
|
<a href="https://github.com/javl/image2cpp" target="_blank">view GitHub Repository</a>
|
|
</nav>
|
|
</div>
|
|
<div class="topnav-right">
|
|
<button class="icon-btn" id="theme-toggle" title="Toggle dark mode" aria-label="Toggle dark mode">
|
|
<span class="theme-icon-dark">☽</span>
|
|
<span class="theme-icon-light">☼</span>
|
|
</button>
|
|
<button class="btn-reset" onclick="const fileInput = document.getElementById('file-input'); if (fileInput) fileInput.value = ''; location.reload();">Reset page</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="layout">
|
|
<aside class="sidebar">
|
|
<nav class="sidebar-nav">
|
|
<a href="#step-0" class="sidebar-link" data-step="step-0">About and support</a>
|
|
<a href="#step-1" class="sidebar-link" data-step="step-1"><span class="sidebar-num">1</span>Select Image</a>
|
|
<a href="#step-2" class="sidebar-link" data-step="step-2"><span class="sidebar-num">2</span>Settings</a>
|
|
<a href="#step-3" class="sidebar-link" data-step="step-3"><span class="sidebar-num">3</span>Preview</a>
|
|
<a href="#step-4" class="sidebar-link" data-step="step-4"><span class="sidebar-num">4</span>Output</a>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="wrapper">
|
|
<section class="section step" id="step-0">
|
|
<h1>image2cpp</h1>
|
|
<p><strong>image2cpp</strong> is a simple tool to convert images into byte arrays (and vice versa) for use with (monochrome) displays such as OLEDs on your Arduino or Raspberry Pi.<br>
|
|
It was originally made to work with the Adafruit OLED library (for which your can find an example Arduino sketch <a href="https://github.com/javl/image2cpp/blob/master/oled_example/oled_example.ino" target="_blank">here</a>) but
|
|
has been expanded by the community to be useful in all kind of (embedded) projects.</p>
|
|
<p>More info (and credits) can be found in the <a href="https://github.com/javl/image2cpp" target="_blank">Github repository</a>. This is also where you can report any <a href="https://github.com/javl/image2cpp/issues" target="_blank">issues</a> you might come across.</p>
|
|
<p class="sp-block">
|
|
Did you find this tool useful? Feel free to support my open source software on Github, especially if used commercially.
|
|
</p>
|
|
<p>
|
|
<a href="https://github.com/sponsors/javl"><img src="https://camo.githubusercontent.com/7fcc34127227d7a0293435c20a74bce4bf3eb26d86f6c5000aa620da147223dd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5f2d73706f6e736f725f6f6e5f4769746875622d626c75653f6c6f676f3d676974687562" alt="GitHub Sponsor" data-canonical-src="https://img.shields.io/badge/_-sponsor_on_Github-blue?logo=github" style="max-width: 100%;"></a> <a href="https://www.buymeacoffee.com/javl" rel="nofollow"><img src="https://camo.githubusercontent.com/0eeabd01753f37f754419d1b3f1b9fd755b1653c23283c8c8b17b1fbb18191e4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275795f4d655f615f436f666665652d6f72616e67653f6c6f676f3d6275796d6561636f66666565" alt="BMC" data-canonical-src="https://img.shields.io/badge/Buy_Me_a_Coffee-orange?logo=buymeacoffee" style="max-width: 100%;"></a>
|
|
</p>
|
|
</section>
|
|
|
|
<section class="section step bottom-divider" id="step-1">
|
|
<div class="step-num">1</div>
|
|
<section class="sub-section input-methods-row">
|
|
<div class="column" id="file-input-column">
|
|
<h2 class="sub-section-title">Select image</h2>
|
|
<div class="note upload-note">You can import multiple images at once.<br>All processing is done locally in your browser: your images are not uploaded or stored anywhere online.</div>
|
|
<input type="file" id="file-input" name="file-input" multiple/><br />
|
|
<div id="file-input-column-items"></div>
|
|
<div class="note" id="continue-note">Add more images or <a href="#step-2">continue to step 2</a></div>
|
|
</div>
|
|
<div class="column column-right">
|
|
<h2 class="sub-section-title">or paste existing byte array</h2>
|
|
<textarea id="byte-input" class="byte-input" placeholder="i.e. 0xff, 0x81, 0x0d, 0x8d, 0xb1, 0xb1, 0x80, 0xfc"></textarea><br />
|
|
<div class="text-input-size">
|
|
<input type="number" min="1" id="text-input-width" class="size-input" value="128" /> x
|
|
<input type="number" min="1" id="text-input-height" class="size-input" value="64" /> px
|
|
</div>
|
|
<div class="note">Set this to the size of your image.<br>Common sizes: <a href="#" class="size-preset" onclick="setTextInputSize(16, 16); return false;"><code>16x16</code></a> <a href="#" class="size-preset" onclick="setTextInputSize(32, 32); return false;"><code>32x32</code></a> <a href="#" class="size-preset" onclick="setTextInputSize(64, 48); return false;"><code>64x48</code></a> <a href="#" class="size-preset" onclick="setTextInputSize(128, 32); return false;"><code>128x32</code></a> <a href="#" class="size-preset" onclick="setTextInputSize(128, 64); return false;"><code>128x64</code></a>.</div>
|
|
<div>
|
|
<button onclick="handleTextInput('horizontal')">Read as horizontal</button>
|
|
<button onclick="handleTextInput('vertical')">Read as vertical</button>
|
|
</div>
|
|
<canvas id="text-input-preview-canvas" class="text-input-preview-canvas"></canvas>
|
|
<div id="text-input-error" class="text-input-error">Byte array is invalid (or empty).</div>
|
|
<div class="note" id="read-images-note">If your image looks glitchy try changing the dimensions above and press the read button again.<br>Read images also appear at step 3 below for further processing.</div>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="section step bottom-divider" id="step-2">
|
|
<div class="step-num">2</div>
|
|
<h2>Image Settings</h2>
|
|
<section class="sub-section">
|
|
<div class="table settings-grid">
|
|
|
|
<div class="table-row full-row">
|
|
<div class="table-cell"><label>Canvas size(s): </label></div>
|
|
<div class="table-cell">
|
|
<ul id="image-size-settings"></ul>
|
|
<div id="only-images-file-error" class="msg error-msg">Only images file type are allowed</div>
|
|
<div class="no-file-selected msg">Select one or more images at <a href="#step-1">step 1</a> first.</div>
|
|
<button id="all-same-size" onclick="allSameSize()">Apply first image size to all images</button>
|
|
<div class="note" id="glyph-name-note">
|
|
<i>Glyph name is used to identify the image in the code.</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Canvas background color:</label></div>
|
|
<div class="table-cell">
|
|
<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>
|
|
<input id="backgroundColorTransparent" type="radio" name="backgroundColor" value="transparent" onchange="updateRadio('backgroundColor')"/>
|
|
<label for="backgroundColorTransparent" class="smallLabel">Transparent</label>
|
|
<div class="note">
|
|
<i>Used for parts of the canvas that are not covered by your image.</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="invertColors">Invert image colors</label></div>
|
|
<div class="table-cell">
|
|
<input id="invertColors" type="checkbox" onchange="updateBoolean('invertColors')" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="ditheringMode">Dithering: </label></div>
|
|
<div class="table-cell">
|
|
<select id="ditheringMode" onchange="updateInteger('ditheringMode')">
|
|
<option value="0">Binary</option>
|
|
<option value="1">Bayer</option>
|
|
<option value="2">Floyd-Steinberg</option>
|
|
<option value="3">Atkinson</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="ditheringThreshold">Brightness / alpha threshold: </label></div>
|
|
<div class="table-cell">
|
|
<input id="ditheringThreshold" class="size-input" type="number" min="0" max="255" name="ditheringThreshold" oninput="updateInteger('ditheringThreshold')" value="128"/>
|
|
<div class="note">
|
|
<i>0 - 255; if the brightness of a pixel is above the given level the pixel becomes white, otherwise they become black. When using alpha, opaque and transparent are used instead. Default is 128.</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="scale">Scaling:</label></div>
|
|
<div class="table-cell">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Center image:</label></div>
|
|
<div class="table-cell">
|
|
<input id="centerHorizontally" type="checkbox" onchange="updateBoolean('centerHorizontally')" />
|
|
<label for="centerHorizontally">horizontally</label>
|
|
<input id="centerVertically" type="checkbox" onchange="updateBoolean('centerVertically')" />
|
|
<label for="centerVertically">vertically</label>
|
|
<div class="note">
|
|
<i>Centering the image only works when using a canvas larger than the original image.</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="rotation">Rotate image:</label></div>
|
|
<div class="table-cell">
|
|
<select id="rotation" name="rotation" onchange="updateInteger('rotation')">
|
|
<option value="0">0</option>
|
|
<option value="90">90</option>
|
|
<option value="180">180</option>
|
|
<option value="270">270</option>
|
|
</select>
|
|
<label for="rotation">degrees CW</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Flip image:</label></div>
|
|
<div class="table-cell">
|
|
<input id="flipHorizontally" type="checkbox" onchange="updateBoolean('flipHorizontally')" />
|
|
<label for="flipHorizontally">horizontally</label>
|
|
<input id="flipVertically" type="checkbox" onchange="updateBoolean('flipVertically')" />
|
|
<label for="flipVertically">vertically</label>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="section step bottom-divider" id="step-3">
|
|
<div class="step-num">3</div>
|
|
<h2>Preview</h2>
|
|
<section class="sub-section">
|
|
<div class="no-file-selected msg">No files selected</div>
|
|
<div id="images-canvas-container"></div>
|
|
</section>
|
|
</section>
|
|
|
|
<section class="section step" id="step-4">
|
|
<div class="step-num">4</div>
|
|
<h2>Output</h2>
|
|
<section class="sub-section">
|
|
<div class="table">
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Draw mode:</label></div>
|
|
<div class="table-cell">
|
|
<select id="drawMode" name="drawMode" onchange="updateDrawMode(this)">
|
|
<option value="horizontal1bit">Horizontal - 1 bit per pixel</option>
|
|
<option value="vertical1bit">Vertical - 1 bit per pixel</option>
|
|
<option value="horizontal565">Horizontal - 2 bytes per pixel (565)</option>
|
|
<option value="horizontalAlpha">Horizontal - 1 bit per pixel alpha map</option>
|
|
<option value="horizontal888">Horizontal - 3 bytes per pixel (rgb888)</option>
|
|
</select>
|
|
|
|
<div class="note">
|
|
<i>If your image looks all messed up on your display, like the image below, try using a different mode.</i>
|
|
</div>
|
|
<img class="inlineImg"
|
|
src="data:image/jpeg;base64,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"
|
|
width="150" height="64" alt="" />
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Code output format</label></div>
|
|
<div class="table-cell">
|
|
<input id="outputFormatPlain" type="radio" name="outputFormat" value="plain" onchange="updateOutputFormat(this)"/>
|
|
<label for="outputFormatPlain" class="smallLabel">Plain bytes</label>
|
|
<input id="outputFormatArduino" type="radio" name="outputFormat" value="arduino" checked="checked" onchange="updateOutputFormat(this)"/>
|
|
<label for="outputFormatArduino" class="smallLabel">Arduino code</label>
|
|
<input id="outputFormatArduinoSingle" type="radio" name="outputFormat" value="arduino_single" onchange="updateOutputFormat(this)"/>
|
|
<label for="outputFormatArduinoSingle" class="smallLabel">Arduino code, single bitmap</label>
|
|
<input id="outputFormatAdafruitGfx" type="radio" name="outputFormat" value="adafruit_gfx" onchange="updateOutputFormat(this)"/>
|
|
<label for="outputFormatAdafruitGfx" class="smallLabel">Adafruit GFXbitmapFont</label>
|
|
<div id="format-caption-container">
|
|
<div data-caption="plain">
|
|
Outputs just the raw bytes of the image(s).
|
|
</div>
|
|
<div data-caption="arduino">
|
|
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>.
|
|
If multiple images are loaded, generates a byte array for each and appends a counter to the identifier.
|
|
</div>
|
|
<div data-caption="arduino_single">
|
|
Adds some extra Arduino code around the output for easy copy-paste.
|
|
If multiple images are loaded, generates a single byte array.
|
|
</div>
|
|
<div data-caption="adafruit_gfx">
|
|
Creates a <code>GFXbitmapFont</code> formatted ouput. Used by a modified version of the Adafruit GFX library.
|
|
GitHub project and example <a href="https://github.com/wiredolphin/Adafruit-GFX-Library/tree/bitmap-font" target="_blank">here</a>.
|
|
<br />
|
|
<i>First ASCII character</i> value is used only if a glyph identifier of length equal to 1 is not provided for each image. The value itself will be incremented by 1 for each glyph.
|
|
</div>
|
|
</div>
|
|
<div id="extra-settings-container">
|
|
<div id="adafruit-gfx-settings" class="table nested-table">
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>First ASCII character (dec):</label></div>
|
|
<div class="table-cell">
|
|
<input id="first-ascii-char" class="text-input" type="text" name="first-ascii-char" value="48"/>
|
|
</div>
|
|
</div>
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>x advance:</label></div>
|
|
<div class="table-cell">
|
|
<input id="x-advance" class="text-input" type="text" name="x-advance" value="0"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="arduino-identifier" class="table nested-table">
|
|
<div class="table-row">
|
|
<div class="table-cell"><label>Identifier/Prefix:</label></div>
|
|
<div class="table-cell">
|
|
<input id="identifier" class="text-input" type="text" name="identifier" value="epd_bitmap_"/>
|
|
</div>
|
|
</div>
|
|
<div class="table-row">
|
|
<div class="table-cell"><label for="esp32Format">ESP32 formatting:</label></div>
|
|
<div class="table-cell">
|
|
<input id="esp32Format" type="checkbox" onchange="updateBoolean('esp32Format')" />
|
|
<i class="note">Uses <code>uint8_t</code> instead of <code>unsigned char</code> and drops <code>PROGMEM</code>, which is a no-op on ESP32.</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-row" id="prefix-separator-container">
|
|
<div class="table-cell"><label>Extra formatting options:</label></div>
|
|
<div class="table-cell">
|
|
<div class="sub-field">
|
|
<input id="outputComments" type="checkbox" checked onchange="updateBoolean('outputComments')">
|
|
<label for="outputComments">Add comments to code output</label>
|
|
</div>
|
|
<div class="sub-field">
|
|
<input id="bitswap" type="checkbox" onchange="updateBoolean('bitswap')">
|
|
<label for="bitswap">Swap bits in bytes</label>
|
|
<i class="note">MSB -> LSB. Useful when working with the u8g2 library.</i>
|
|
</div>
|
|
|
|
<div class="sub-field">
|
|
<label for="prefix" class="sub-field-label">Byte prefix:</label>
|
|
<input id="prefix" class="size-input" type="text" value="0x" oninput="updateString('prefix')"/>
|
|
</div>
|
|
<div class="sub-field">
|
|
<label for="separator" class="sub-field-label">Separator between bytes:</label>
|
|
<input id="separator" class="size-input" type="text" value=", " oninput="updateString('separator')"/>
|
|
</div>
|
|
<div class="sub-field">
|
|
<label for="bytesPerRow" class="sub-field-label">Bytes per row:</label>
|
|
<input id="bytesPerRow" class="size-input" type="number" min="0" value="16" oninput="updateInteger('bytesPerRow')"/>
|
|
<i class="note">Set to 0 to wrap using the image's width instead, e.g. an 8px wide image adds a newline every 8th byte.</i>
|
|
</div>
|
|
<div class="sub-field">
|
|
<input id="showAsciiPreview" type="checkbox" onchange="updateBoolean('showAsciiPreview')">
|
|
<label for="showAsciiPreview">Show ascii preview behind output</label>
|
|
<i class="note">Adds a dot/hash ascii-art comment after each row of bytes. Forces one image row per output line, regardless of "Bytes per row".</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<section class="sub-section">
|
|
<button type="button" class="generate-button" onclick="generateOutputString()">Generate code</button>
|
|
<button type="button" id = "copy-button" onclick="copyOutput()">Copy Output to clipboard</button>
|
|
<button type="button" id = "download-button" onclick="downloadBinFile()">Download as binary file (.bin)</button>
|
|
<div id="no-images-error" class="msg error-msg">Select one or more images at <a href="#step-1">step 1</a> first.</div>
|
|
<textarea id="code-output" class="code-output"></textarea>
|
|
</section>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
// Dark mode toggle (persisted)
|
|
(function () {
|
|
var root = document.documentElement;
|
|
var stored = localStorage.getItem('theme');
|
|
if (stored) root.setAttribute('data-theme', stored);
|
|
var btn = document.getElementById('theme-toggle');
|
|
if (btn) btn.addEventListener('click', function () {
|
|
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
root.setAttribute('data-theme', next);
|
|
localStorage.setItem('theme', next);
|
|
});
|
|
|
|
// Sidebar scrollspy
|
|
var links = Array.prototype.slice.call(document.querySelectorAll('.sidebar-link'));
|
|
var steps = links.map(function (l) { return document.getElementById(l.dataset.step); });
|
|
function onScroll() {
|
|
var pos = window.scrollY + 120;
|
|
var active = 0;
|
|
steps.forEach(function (s, i) { if (s && s.offsetTop <= pos) active = i; });
|
|
links.forEach(function (l, i) { l.classList.toggle('active', i === active); });
|
|
steps.forEach(function (s, i) { if (s) s.classList.toggle('active', i === active); });
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
onScroll();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|