Compressing the tiny code

Koma Tebe
8 min readSep 1, 2022

--

Making of #つぶやきProcessing sketch (part 2)

There are many ways to compress p5.js code created in the first part of this tutorial. I’ll show you the easiest. But before we start, let me ask you to forget everything about the rules of good JS programming. Your code is about to become dirty and unreadable! Don’t worry about warnings as long as your sketch works.

Another great editor

There is a great online tool called つぶやきProcessing Editor.

It is created by a guy named Naoto Heida. He managed to create an excellent helper tool for #つぶやきProcessing sketches. In essence, it is a code editor with options to compress JavaScript code, format the code, record animated gifs, and tweet directly from the editor. The minify & run (JavaScript compression) is the only option we are going to use in this tutorial.

Let’s copy & paste the following code into the つぶやきProcessing Editor.

function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
}
function draw() {
background(0);
pointLight(255, 255, 255, 0, -400, 400);
pointLight(200, 200, 200, 0, 0, 400);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + frameCount / 50);
box(30, 2, 30);
pop();
}
}

Click minify & run to compress the code.

If everything goes as expected then you should get the following result:

The compressed code is 283 characters long. It is not bad having in mind that the original code length is 354 characters!

Please keep in mind that the resulting, compressed, code includes 17 characters long //#つぶやきProcessing hashtag too!

Compression

The JavaScript compressor does a very good job. It compresses the code following its pre-defined rules like optimizing variable names or reformating code blocks. These rules are universal for all JS scripts. But our script always has unique lines and blocks of code which can be refactored and optimized for even better compression results. Using the compressor alone, we can reduce code up to one level. But it’s not intelligent. We must help him achieve better compression results.

Here is a list of changes we can do to our code to improve compression results:

One variable to conquer the setup() function

We don’t need setup() function!

If we have a code block meant to execute only once, we can do it the other way with a little help from a variable. Let’s name it f.

p5js based #つぶやきProcessing variables are global in 99% of cases. Using var, let or const can increase the number of characters.

f=0;
function setup() {
createCanvas(400, 400, WEBGL);
noStroke();
}

Now let’s move the whole setup() code block grouped as (createCanvas(400, 400, WEBGL), noStroke()) into draw() function like this:

f=0;
function draw() {
f++||(createCanvas(400, 400, WEBGL), noStroke());
background(0);
pointLight(255, 255, 255, 0, -400, 400);
pointLight(200, 200, 200, 0, 0, 400);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + frameCount / 50);
box(30, 2, 30);
pop();
}
}

(createCanvas(400, 400, WEBGL), noStroke()) is a comma operator expression wrapped in grouping parentheses. In our case, it executes every function separated by a comma (from left to right) and that is all you have to know about it. It is great for grouping functions in one line which is important for our script.

We are using logical OR (||) to execute our grouped code only once. If the variable f on the left can be converted to false, then it executes code on the right; else, it returns the value of f.

In short, at the beginning f is equal to zero which executes the code. With every draw cycle it increases by 1 (1,2,3,…) and the code on the right never gets executed again.

(f = 0 is treated as false. f > 0 is treated as not false. f++ is the same as f=f+1)

Let’s copy the code into the つぶやきProcessing Editor and compress it again.

It is 277 characters! Great! But this is just the beginning.

No frameCount property

Because the f variable gets incremented every frame we can use it to replace the frameCount property too:

f=0;
function draw() {
f++||(createCanvas(400, 400, WEBGL), noStroke());
background(0);
pointLight(255, 255, 255, 0, -400, 400);
pointLight(200, 200, 200, 0, 0, 400);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + f / 50);
box(30, 2, 30);
pop();
}
}

268 Characters!

Compress pointLight() parameters

If we have grayscale lights (all three redgreenblue values are the same) we can write rgb values as array with one value: pointLight(255, 255, 255, 0, -400, 400) as pointLight([255], 0, -400, 400).

f=0;
function draw() {
f++||(createCanvas(400, 400, WEBGL), noStroke());
background(0);
pointLight([255], 0, -400, 400);
pointLight([200], 0, 0, 400);

for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + f / 50);
box(30, 2, 30);
pop();
}
}

Copy the code into the つぶやきProcessing Editor and compress it… 256 characters!!!

No noStroke()

This one is cool. Our box(30,3,30) has three parameters for width, height, and depth. But we can add the fourth parameter which defines the number of triangle subdivisions in x-dimension. (we can also add the fifth parameter for subdivisions in y-dimension but it is not necessary)

Funny thing is — if we pass a number bigger than 4 as a fourth parameter We’ll get the following warning “Cannot draw stroke on box objects with more than 4 detailX or 4 detailY” but the object will still be rendered — without outlines! Which gives the same effect as the noStroke() function.

So, let’s delete noStroke() function and add the fourth parameter to our box(30,3,30). Another outcome of this action is that we don’t need parentheses for createCanvas(400, 400, WEBGL) because it is the only function to be executed in the case when f=0.

f=0;
function draw() {
f++||createCanvas(400, 400, WEBGL);
background(0);
pointLight([255], 0, -400, 400);
pointLight([200], 0, 0, 400);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + f / 50);
box(30, 2, 30, 5);
pop();
}
}

If you copy & paste in つぶやきProcessing Editor you’ll see that the compressed code is 245 characters long now!

No background()

Instead of using background(), we can use a big box() that can do the same job. It will cover the remains of the previous draw() cycle and fill the scene with black color because it is big and light can not light it properly.

Values bigger than 1000 will be represented by e-notation.
Example: 1000 = 1e3 (1 character gained).

The scene enclosed with the box on the left. The same scene zoomed out of the enclosing box on the right.
f=0;
function draw() {
f++||createCanvas(400, 400, WEBGL);
pointLight([255], 0, -400, 400);
pointLight([200], 0, 0, 400);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + f / 50);
box(30, 2, 30, 5);
pop();
}
box(1000);
}

240 characters!!!

Put the same values into a variable

Value 400 appears in 5 places in the code! Let’s move it into variable W.

f=0;
function draw() {
f++||createCanvas(W=400, W, WEBGL);
pointLight([255], 0, -W, W);
pointLight([200], 0, 0, W);
for (let i = 0; i < TAU; i += PI / 256) {
push();
rotateZ(i);
translate(100, 0, 0);
rotateY(i + f / 50);
box(30, 2, 30, 5);
pop();
}
box(1000);
}

234 characters!!!

One character here… one character there…

f=0;
function draw() {
f++||createCanvas(W=400, W, WEBGL);
pointLight([255], 0, -W, W);
pointLight([200], 0, 0, W);
for (i = 0; i < TAU; i += PI / 256) {
push();
rotate(i);
translate(99, 0);
rotateY(i + f / 50);
pop(box(30, 2, 30, 5));
}
box(1000);
}

Copy & paste the code in つぶやきProcessing Editor and compress it. It is 225 characters long now!

Arrow function

Finally, we can use a new arrow function instead of the standard one. It has a shorter syntax which is great for code compression.

Instead of function draw() we can use draw=_=>.

f=0;
draw =_=> {
f++||createCanvas(W=400, W, WEBGL);
pointLight([255], 0, -W, W);
pointLight([200], 0, 0, W);
for (i = 0; i < TAU; i += PI / 256) {
push();
rotate(i);
translate(99, 0);
rotateY(i + f / 50);
pop(box(30, 2, 30, 5));
}
box(1000);
}

This reduces the code to 219 characters!

Compromise

Our lights are set using the following values:

pointLight([255], 0, -W, W);
pointLight([200], 0, 0, W);

These values give the following result:

Looks like we can use the same light two times with only one different value:

pointLight([255], 0, -W, W);
pointLight([255], 0, W, W);

And get the slightly brighter result:

Banding is the result of gif compression

If the resulting animation looks good then we can do another optimization using the array map function. It iterates an array and passes the iterated value to our function. Like this:

pointLight([255], 0, -W, W);
pointLight([255], 0, W, W);

… changes into:

[-1,1].map(i=>pointLight([255], 0, W * i, W));

Each array value [-1,1] is passed as i to pointLight() function. And then used to position the light on the y-axis W * i.

Finish line

Our final, optimized, code looks like this:

f=0;
draw =_=> {
f++||createCanvas(W=400, W, WEBGL);
[-1,1].map(i=>pointLight([255], 0, W * i, W));
for (i = 0; i < TAU; i += PI / 256) {
push();
rotate(i);
translate(99, 0);
rotateY(i + f / 50);
pop(box(30, 2, 30, 5));
}
box(1000);
}

Compressed code looks like this:

f=0,draw=a=>{for(f++||createCanvas(W=400,W,WEBGL),[-1,1].map(a=>pointLight([255],0,W*a,W)),i=0;i<TAU;i+=PI/256)push(),rotate(i),translate(99,0),rotateY(i+f/50),pop(box(30,2,30,5));box(1e3)};//#つぶやきProcessing

The final code is 211 characters long (#つぶやきProcessing hashtag included!) which means that you have a whole 69 characters left to add your tweaks and changes to the code.

Be creative! Post your results on Twitter! And don’t forget to keep the #つぶやきProcessing hashtag in the compressed code. This will help people find you.

Cheers,
KT

--

--

Koma Tebe
Koma Tebe

No responses yet