By Joe Crawford. December 2024.
This is a GIF images with ONLY 3 colors. The first two indexed colors are recolored. The third is transparent.
<?php
// This is an image with 3 colors. 3rd color is transparent.
// GIFs have an indexed color palette which is why this works.
// if you change out the image, and it is a GIF with 3 colors,
// it ought to work.
// This source code is provided without warranty tho.
// https://apps.artlung.com/logo-i-zer/create/
$reference_file = '20241112-header.gif';
// if there's no file to work from, don't move on
if (!file_exists($reference_file)) {
die("File not found: $reference_file");
}
// we expect 2 parameters and 2 only
$c0 = $_GET['c0'] ?? '000000';
$c1 = $_GET['c1'] ?? 'FFFFFF';
// if the colors are in the form #FFFFFF, strip the #
if (strlen($c0) === 7 || strlen($c1) === 7) {
$c0 = substr($c0, 1);
$c1 = substr($c1, 1);
}
// if colors are provided in the 3 letter form, expand them
if (strlen($c0) === 3) {
$c0 = str_repeat(substr($c0, 0, 1), 2)
. str_repeat(substr($c0, 1, 1), 2)
. str_repeat(substr($c0, 2, 1), 2);
}
if (strlen($c1) === 3) {
$c1 = str_repeat(substr($c1, 0, 1), 2)
. str_repeat(substr($c1, 1, 1), 2)
. str_repeat(substr($c1, 2, 1), 2);
}
// now we have 6 character hex colors, but if invalid hex numbers
// got through, we have to bail out
$regex = '/^[0-9a-fA-F]{6}$/';
if (!preg_match($regex, $c0) || !preg_match($regex, $c1)) {
http_response_code(400);
die("Invalid color");
}
// let's not put up with any mishagosh with extra parameters
if (count($_GET) > 2) {
http_response_code(400);
die("Invalid parameters");
}
// enforce uppercase hex colors.
$c0 = strtoupper($c0);
$c1 = strtoupper($c1);
$filename =sprintf('images/20241112-header-%s-%s.gif', str_replace('#', '', $c0), str_replace('#', '', $c1));
if (file_exists($filename)) {
header('Content-Type: image/gif');
readfile($filename);
exit;
}
$im = imagecreatefromgif($reference_file);
list($r0, $g0, $b0) = sscanf($c0, "%02x%02x%02x");
list($r1, $g1, $b1) = sscanf($c1, "%02x%02x%02x");
imagecolorallocate($im, $r0, $g0, $b0);
imagecolorallocate($im, $r1, $g1, $b1);
imagecolorset($im, 0, $r0, $g0, $b0);
imagecolorset($im, 1, $r1, $g1, $b1);
$written = imagegif($im, $filename);
if (!$written) {
http_response_code(500);
die("Failed to write image to images/ directory");
}
$latest_data = [
'filename' => $filename,
'timestamp' => time(),
];
$written = file_put_contents('latest.json', json_encode($latest_data));
if (!$written) {
http_response_code(500);
die("Failed to write latest.json");
}
header('Content-Type: image/gif');
readfile($filename);
exit;
<!DOCTYPE html>
<html>
<head>
<title>ARTLUNG LOGO-I-ZER</title>
<link rel="webmention" href="https://webmention.io/artlung.com/webmention" />
<meta property="og:image" content="https://apps.artlung.com/logo-i-zer/logo-I-zer.jpg" />
<meta property="og:description" content="Joe Crawford made this tool to make his ARTLUNG logo with any 2 colors" />
<style><?php print file_get_contents('create.css'); ?></style>
</head>
<body>
<form method="get" action="./">
<div>
<label for="c1">Inner Color</label> <input type="color" id="c1" value="#FFFFFF" name="c1">
<label for="c0">Outer Color</label> <input type="color" id="c0" value="#000000" name="c0">
<button id="flop-colors">Flop Colors</button>
</div>
<button id="create">Create</button>
<a href="./images/" target="_blank"><img src="images/20241112-header-000000-FFFFFF.gif" alt="ARTLUNG"></a>
</form>
<a href="source.php">Source Code</a>
<script src="create.js?<?php
echo '?' . filemtime('create.js');
?>"></script>
</body>
</html>
:root { --c0: #000000; --c1: #FFFFFF; } html { height: 100%; } body { height: 100%; font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono", "Droid Sans Mono", monospace, monospace; display: grid; grid-template-rows: 90vh 10vh; align-items: center; background-color: color-mix(in srgb, var(--c1) 5%, transparent 80%); background-image: linear-gradient( 45deg, var(--c0) 2rem, transparent 0 ), linear-gradient( -45deg, var(--c0) 2rem, transparent 0 ), linear-gradient( 135deg, var(--c0) 2rem, transparent 0 ), linear-gradient( -135deg, var(--c0) 2rem, transparent 0 ); overflow: hidden; > a { color: var(--c0); display: block; padding: 1rem; text-decoration: underline; text-decoration-style: wavy; width: min-content; white-space: nowrap; margin: 0 auto; } } img { width: 100%; height: auto; image-rendering: pixelated; } form { div { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; label { text-align: end; } #flop-colors { grid-column: 2; font-size: 0.66rem; padding: 0.33rem; font-weight: normal; width: min-content; white-space: nowrap; } } display: flex; flex-direction: column; width: clamp(220px, 50%, 500px); margin: 0 auto; gap: 1rem; button { font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono", "Droid Sans Mono", monospace, monospace; color: var(--c0); background-color: color-mix(in srgb, var(--c1) 5%, transparent 70%); border: 3px double var(--c0); border-radius: 1rem; padding: 1rem; font-size: 1rem; cursor: pointer; font-weight: bold; } a { text-decoration: none; border: none; } }
document.getElementById('create').addEventListener('click', function (evt) { console.log('create'); var c0 = document.getElementById('c0').value; var c1 = document.getElementById('c1').value; // set value of --c0 and --c1 in the :root style document.documentElement.style.setProperty('--c0', c0); document.documentElement.style.setProperty('--c1', c1); var img = document.querySelector('img'); c0 = c0.toUpperCase().replace('#', ''); c1 = c1.toUpperCase().replace('#', ''); img.src = './?c0=' + c0 + '&c1=' + c1; evt.preventDefault(); }); document.getElementById('flop-colors').addEventListener('click', function (evt) { console.log('flop colors'); var c0 = document.getElementById('c0').value; var c1 = document.getElementById('c1').value; document.getElementById('c0').value = c1; document.getElementById('c1').value = c0; saveColorsToLocal(); evt.preventDefault(); }); function saveColorsToLocal() { var c0 = document.getElementById('c0').value; var c1 = document.getElementById('c1').value; localStorage.setItem('c0', c0); localStorage.setItem('c1', c1); } setInterval(saveColorsToLocal, 1000); if (localStorage.getItem('c0') && localStorage.getItem('c1')) { document.getElementById('c0').value = localStorage.getItem('c0'); document.getElementById('c1').value = localStorage.getItem('c1'); document.getElementById('create').click(); }
<?php
require 'logo-i-zer.php';
<!DOCTYPE html>
<html>
<head>
<title>ARTLUNG Logo-i-zer Images</title>
<link rel="webmention" href="https://webmention.io/artlung.com/webmention" />
<style><?php print file_get_contents('images.css'); ?></style>
<style>section img { aspect-ratio: 211 / 41; }</style>
<script>let latest = <?php print file_get_contents('../latest.json'); ?></script>
<script src="images.js<?php
echo '?' . filemtime('images.js');
?>"></script>
</head>
<body><section><?php
require 'glob.php';
?></section></body></html>
<?php
$gifs = glob('*.gif');
rsort($gifs);
foreach ($gifs as $gif) {
printf('<img src="%s" alt="%s" />', $gif, $gif);
}
<?php
require 'images.php';
if (latest && latest.timestamp) { let lastSuccess = new Date().getTime(); interval = setInterval(() => { fetch('../latest.json') .then(response => response.json()) .then(data => { console.log(data) if (latest.timestamp !== data.timestamp) { latest = data; const img = document.createElement('img'); img.src = latest.filename.replace('images/', ''); img.alt = latest.filename.replace('images/', ''); img.className = 'fresh'; document.querySelector('section').appendChild(img); lastSuccess = new Date().getTime(); } }); // don't do this forever if (new Date().getTime() - lastSuccess > 600000) { console.log('stopping because it has been 10 minutes since last success'); clearInterval(interval); } }, 4000); } // attach a a mouseover to any image that might get added to the body with img.fresh class // and when mouseover, remove the class document.addEventListener('mouseover', (e) => { if (e.target.classList.contains('fresh')) { e.target.classList.remove('fresh'); } });
html, body { height: 100%; } body { background: conic-gradient( #eee 25%, #fff 0 50%, #eee 0 75%, #fff 0 100% ) 0 0 / 50px 50px; margin: 0; padding: 10px; display: grid; justify-content: center; align-items: center; section { display: flex; gap: 10px; flex-wrap: wrap; justify-content: start; align-items: center; height: min-content; img { &.fresh { outline: var(--outline-width) solid var(--outline-color); animation: pulse 1.2s infinite linear; } } } } :root { --outline-color: pink; --outline-width: 0.5rem; } @keyframes pulse { 0% { outline-color: var(--outline-color); outline-width: var(--outline-width); } 50% { outline-color: transparent; outline-width: 0; } 100% { outline-color: var(--outline-color); outline-width: var(--outline-width); } }
<?php
header('Location: /logo-i-zer/create.php');