テラByteの時代にキロByte

shader又はdemosceneに関係する事

JSの圧縮

JSの圧縮ツールにJsExeというのがある。ちょっとダウンロードしようとしたが出来なかった。
JsExeは持っていたが、どうせならばで作ってみた。
参考にした記事は yomotsu.net 正月早々に、俺は何でJSでバイナリなんて弄っているんだって、ぼやきながら、ずぶずぶにハマっていた。で、出来てみれば、大したことのない行数で済んでしまうんだな。こんな感じ。python3で書いてます。

from PIL import Image
import numpy
import base64
from io import BytesIO

def js2img(js):
    js = numpy.array(list(js.encode()), dtype='uint8').reshape((1, len(js)))
    img = Image.fromarray(js)
    buffer = BytesIO()
    img.save(buffer, "png")
    return buffer.getvalue()

def asciiHtml(js, filename='asciiDemo.html'):
    f = open(filename, 'w')
    f.write(
        "<canvas id=c><img src='data:image/png;base64," + base64.b64encode(js2img(js)).decode() + "' onload=C=c.getContext('2d');for($=_='';C.drawImage(this,-$,0),X=C.getImageData(0,0,1,1).data[0],$++<this.width;_+=String.fromCharCode(X));(1,eval)(_)>"
    )
    f.close()

def binaryHtml(js, filename='binaryDemo.html'):
    f = open(filename, 'bw')
    f.write(
        js2img(js) + b"<canvas id=c><img src=# onload=C=c.getContext('2d');for($=_='';C.drawImage(this,-$,0),X=C.getImageData(0,0,1,1).data[0],$++<this.width;_+=String.fromCharCode(X));(1,eval)(_)>"
    )
    f.close()
    
def stdHtml(js, filename='stdDemo.html'):
    f = open(filename, 'w')
    f.write(
        "<body><script>" + js + "</script></body>"
    )
    f.close()

js = '''
var canvas = document.createElement("canvas");
canvas.style.position = "fixed";
canvas.style.cursor = "none";
canvas.style.left = canvas.style.top = 0;

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

var gl = canvas.getContext("webgl2") || canvas.getContext("experimental-webgl2");
var compileShader = function(prog, src, type){
    var sh = gl.createShader(type);
    gl.shaderSource(sh, src);
    gl.compileShader(sh);
    gl.attachShader(prog, sh);
    gl.deleteShader(sh);
};

vs = `#version 300 es
void main()
{ 
    gl_Position = vec4(ivec2(gl_VertexID&1,gl_VertexID>>1)*2-1,0,1);
}
`
fs = `#version 300 es
precision mediump float;
uniform vec2  resolution;
uniform float time;
out vec4 O;

#define R(p,a,t) mix(a*dot(p,a),p,cos(t))+sin(t)*cross(p,a)
#define H(h) (cos((h)*6.3+vec3(0,23,21))*.5+.5)

void main(){
    vec3 p,c=vec3(0),
    d=normalize(vec3((gl_FragCoord.xy-.5*resolution.xy)/resolution.y,1));
    float i=0.,s,e,g=0.,t=time;
  for(;i++<99.;){
        p=g*d;;
        p.z-=3.;
        p=R(p,vec3(.577),t*.3);
        s=3.;
        for(int i=0;i++<8;p*=e)
            p=vec3(1,3.+sin(t)*.3,2)-abs(p-vec3(1,2,1.5+sin(t)*.2)),
            s*=e=9./clamp(dot(p,p),.8,9.);
        g+=e=abs(p.y/s-.001)+1e-3;
        c+=mix(vec3(1),H(length(p*.2+.5)),.6)*.0015/i/e;  
    }
    c*=c;
    O=vec4(c,1);
}
`
var p = gl.createProgram();
compileShader(p, vs, gl.VERTEX_SHADER);
compileShader(p, fs, gl.FRAGMENT_SHADER);
gl.linkProgram(p);
gl.useProgram(p);
gl.uniform2f(gl.getUniformLocation(p, "resolution"), canvas.width, canvas.height);

var zero = Date.now();
(function () { 
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.uniform1f(gl.getUniformLocation(p, "time"), (Date.now() - zero) * 0.001);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(arguments.callee);
})();
'''

stdHtml(js)
asciiHtml(js)
binaryHtml(js)

これで出来たbinaryDemo.htmlが圧縮の対象。

stdDemo.html        1,986 バイト
asciiDemo.html      2,005  バイト
binaryDemo.html     1,530 バイト

こんな感じの圧縮状況。
binaryDemo.html はバイナリなので、通常では見れない。localhostを立てて見ます。完全にdemoparty仕様です。
python3を使ってlocalhostを立ててみるには

python -m http.server

これでOK。 http://localhost:8000/ にアクセスしてください。
asciiDemo.html はbase64を使った奴。サーバー上じゃなくても見れます。しかしJSのコードがbase64に変換されるので、まあ読めなくなりますね。
stdDemo.html は普通のパターンな奴。
ちょっと気がかりな点でperformance.now()を使うとバイナリhtmlが表示出来なかった。他にも落とし穴的な関数があるかもしれない。

簡単なトリミング

JSの空白とか改行をトリミングする関数。shader用に使っていたのを、ちょっと弄ったので、まだ実績が無い。とりあえず使えそうなので載せておく。想定外なエラーが出る可能性有りです。

import re

def trim(src):
    src = re.compile(r'/\*.*?\*/', re.DOTALL).sub("", src)
    src = re.sub(r"//.*",     "", src)
    src = re.sub(r"\t",      " ", src)
    src = re.sub(r" +",      " ", src)
    src = re.sub(r" *\n *", "\n", src)
    src = re.sub(r"\n+",    "\n", src)    
    src = re.sub(r"^\n",      "", src)
    #####
    line = src.split("\n")
    for i in range(len(line)):
        s = line[i]
        if re.search("#", s) != None:
            line[i] = "\n" + line[i] + "\n"
        else:
            s = re.sub(r" *\+ *" ,"+", s)
            s = re.sub(r" *\- *" ,"-", s)
            s = re.sub(r" *\* *" ,"*", s)
            s = re.sub(r" */ *"  ,"/", s)
            s = re.sub(r" *= *"  ,"=", s)
            s = re.sub(r" *< *"  ,"<", s)
            s = re.sub(r" *> *"  ,">", s)
            s = re.sub(r" *& *"  ,"&", s)
            s = re.sub(r" *\| *" ,"|", s)
            s = re.sub(r" *\( *" ,"(", s)
            s = re.sub(r" *\) *" ,")", s)
            s = re.sub(r" *\[ *" ,"[", s)
            s = re.sub(r" *\] *" ,"]", s)
            s = re.sub(r" *{ *"  ,"{", s)
            s = re.sub(r" *} *"  ,"}", s)
            s = re.sub(r" *; *"  ,";", s)
            s = re.sub(r" *, *"  ,",", s)
            line[i] = s
    src = "".join(line)
    src = re.sub(r"\n+","\n", src)
    return src

あとがき

JsExeはpngのバイナリを壊して更にminifyしてます。そこまではやってません。8bitグレースケールのpngを使っているので、大体良い線にはいっていると思います。
今年はコードを隠してみようかなってって考えてます。今まではコードを見て見て感が強くて、良いトリックを思いつくと、それで満足しちゃうところあって絵がおろそかになってました。そんな事もあって年の頭に、面倒な事をやってました。

追記

以前にJsExeを使って圧縮したファイルのサイズをみたところ、半分くらいに圧縮されているようでした。まだまだ奥がありそうです。pngの圧縮も出来るようなので、その辺りの技術を使っているのかもしれません。いづれ手を出すかも。まあ素直にJsExeを使わせて貰えって話もあるけど、そういうハックも面白かったりするので困ったものです。
後日の為に情報を残しておきます。 ハフマン符号化

darkcrowcorvus.hatenablog.jp

garakuta-toolbox.hatenablog.com