Skip to content

Instantly share code, notes, and snippets.

@lewdev
Last active June 21, 2025 17:01
Show Gist options
  • Save lewdev/271783f04f57f2d8cfef8104ffb4db84 to your computer and use it in GitHub Desktop.
Save lewdev/271783f04f57f2d8cfef8104ffb4db84 to your computer and use it in GitHub Desktop.
🧪 JEX Part 3: Two-Color Pixel Editor Exports to JEX Data (each byte stores 3 values) in Sharable URL

🧪 JEX Part 3: Two-Color Pixel Editor Exports to JEX Data (each byte stores 3 values) in Sharable URL

JEX is a URL friendly Base 69 data storage method I created. JEX is a name that combines the names JavaScript and Hexidecimal.

All JEX characters: ()*+,-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~

NOTE: I was about to publish the format before realizing that twitter hates single quotes because they force it to be encoded. I really wanted to keep it at 70, I SWEAR!

These characters are all in order by charCodeAt and skips URL unsafe characters: &/:;=? @#<>[]{}|\^%\

I reserved ~ tilda as a reserved value to indicate that the data is compressed.

It's the value pair of storing how many times a value is repeated and what that value is.

🖌️ Mini 2-Color Pixel Editor is the editor I created to use JEX.

source code

Credit

I based the editor on this project miniPixelArt by xem

Methods for JEX data

const num2Jex=n=>String.fromCharCode(n+40+(n>7?1:0)+(n>17?7:0)+(n>43?6:0));

const jex2Num=s=>(n=s.charCodeAt()-40,n-(n>7?1:0)-(n>18?7:0)-(n>51?6:0));

Methods for Converting integer values into three 3-bit values

// three values 0-3 into one integer value
const threeIntoNum = (arr, i=0) => (
  0b0000000 + (arr[i] || 0) + ((arr[i+1] || 0) << 2) + (((arr[i+2] || 0) << 4))
);

const numToThree = n => [n & 3, (n >> 2) & 3, (n >> 4) & 3];

These methods are mostly just integrated into my editor code to use less text.

Put them together and you can get this to convert the data.

const extractData = str => {
  const output = [];
  [...str].map(c => output.push(...numToThree(jex2Num(c)));
  return output;
};

🖌️ Mini 2-Color Pixel Editor

The pixel editor features:

  • Changing the image dimensions.
  • Switching between two colors.
  • Changing 2-color palettes I got from lospec.
  • Right-click erases
  • The "export" button:
    • URL is updated with pallete and pixel data
    • Minimal code to draw this data
    • Bookmarklet that can be run in your addressbar
    • Readable and reusable code for drawing the pixel image

📄 PxJex2C.js

This will render the art into a canvas for faster drawing. This will be a more streamlined method for canvas drawing.

<!doctype html><html>
<head><title>JEX Part 3</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%221.045em%22 font-size=%2272%22>🧪</text></svg>">
<style>code { color: #dd00dd; background-color: #eeeeee; }</style>
</head>
<body>
<h2>JEX Part 3</h2>
<p>JEX a Base 69 number system I made up. The name is a combination of JavaScript and Hexadecimal.</p>
<p>I created the basic function of taking a raw number array data with values <code>0-68</code> and convert it to JEX.</p>
<p>I also created a basic compression system that stores the number of times a value is repeated using JEX values</p>
<p>All JEX characters: <code>'()*+,-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz</code></p>
<p>These characters are all in order by <code>charCodeAt</code> and skips URL unsafe characters: <code>&/:;=? @#&lt;&gt;[]{}|\^%`</code>.</p>
<p>It will use compressed or uncompressed versions depending on which is smaller.</p>
<p>The <code>~</code> at the start indicates that the value is compressed</p>
<script>
const jex = (() => {
const num2Jex=n=>String.fromCharCode(n+40+(n>7?1:0)+(n>17?7:0)+(n>43?6:0));
const jex2Num=s=>(n=s.charCodeAt()-40,n-(n>7?1:0)-(n>18?7:0)-(n>51?6:0));
const numArr2Jex=a=>a.map(num2Jex).join``;
return {
numArr2Jex,
jex2NumArr: s=>[...s].map(jex2Num),
numArr2MinJex: arr => {
const rawJex = numArr2Jex(arr);
const out=[curr=arr.shift()];
let c=0; //count
const add=_=>(out.push(c, curr),c=1);
for (n of arr)n===curr?(++c>68?add(c--):0):add(curr=n);
const minJex = "~"+[...out,c].map(num2Jex).join``;
return rawJex.length < minJex.length ? rawJex : minJex;
},
uncompressJex: s=>(d=[...s.substr(1)].map(jex2Num),d.reduce((p,n,i)=>(i%2?p:p+num2Jex(n).repeat(d[i+1])),"")),
minJex2NumArr: s=>[...(s[0]===`~`?uncompressJex(s):s)].map(jex2Num).join`, `,
};
})();
const DATA = [12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, 68, 68, 68, 68, 5, 6, 7, 8, 9];
// const DATA = [1, 2, 3, 4, 5];
onload =_=>{
const compressedData = jex.numArr2MinJex(DATA);
input.innerHTML = DATA.join`, `;
raw.innerHTML = jex.numArr2Jex(DATA);
rawNum.innerHTML = jex.jex2NumArr(jex.numArr2Jex(DATA)).join`, `;
compressed.innerHTML = compressedData;
uncompressed.innerHTML = jex.uncompressJex(compressedData);
};
</script>
<h3>Input:</h3>
<pre id=input></pre>
<h3>Raw JEX</h3>
<pre id=raw></pre>
<h3>Raw Data</h3>
<pre id=rawNum></pre>
<h3>Compressed JEX</h3>
<pre id=compressed></pre>
<h3>Uncompressed JEX</h3>
<pre id=uncompressed></pre>
</body></html>
// two-color palette using JEX (Base 70) data
class PxJex2C {
constructor(palette, jexStr, scale) {
const size = ~~Math.sqrt(jexStr.length * 3);
const ca = document.createElement`canvas`;
const cx = ca.getContext`2d`;
this.size = size;
this.c = ca;
ca.width = ca.height = size * scale;
const P = [];
[...jexStr].map(a=>{
let n = a.charCodeAt()-40;
n = n-(n>7?1:0)-(n>18?7:0)-(n>51?6:0);
P.push(n & 3,(n >> 2) & 3,(n >> 4) & 3);
});
for (let j = size * size, q; j--;) if (q = P[j]) {
cx.fillStyle=`#` + palette.substr(6*(q-1),6);
cx.fillRect(j % size * scale, ~~(j / size) * scale, scale, scale);
}
}
render(pos) { x.drawImage(this.c, pos.x, pos.y); }
}
// example: 1 pixel is 50px
let pj = new PxJex("2e3037ebe5ce", `DDDW2T-A1DD)'T)8PYEDD(`, 50);
pj.render({ x: 50, y: 50});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment