From cc9a887386aa35caa511fb791deba093a762694d Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 13 Jul 2021 18:33:15 +0300 Subject: [PATCH 1/6] optimize srgb -> linear preparation by using lookup table --- codecs/visdif/visdif.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp index 0825cdea..295c6f60 100644 --- a/codecs/visdif/visdif.cpp +++ b/codecs/visdif/visdif.cpp @@ -5,12 +5,21 @@ using namespace emscripten; using namespace butteraugli; +#define GAMMA 2.2 + +static double SrgbToLinear[256]; + +inline void gammaLookupTable() { + for (int i = 1; i < 256; ++i) { + SrgbToLinear[i] = pow(i / 255.0, GAMMA); + } +} + // Turns an interleaved RGBA buffer into 4 planes for each color channel void planarize(std::vector& img, const uint8_t* rgba, int width, - int height, - float gamma = 2.2) { + int height) { assert(img.size() == 0); img.push_back(ImageF(width, height)); img.push_back(ImageF(width, height)); @@ -22,10 +31,10 @@ void planarize(std::vector& img, float* const row_b = img[2].Row(y); float* const row_a = img[3].Row(y); for (int x = 0; x < width; x++) { - row_r[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 0] / 255.0, gamma); - row_g[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 1] / 255.0, gamma); - row_b[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 2] / 255.0, gamma); - row_a[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 3] / 255.0, gamma); + row_r[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 0]]); + row_g[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 1]]); + row_b[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 2]]); + row_a[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 3]]); } } } @@ -37,6 +46,7 @@ class VisDiff { public: VisDiff(std::string ref_img, int width, int height) { + gammaLookupTable(); planarize(this->ref_img, (uint8_t*)ref_img.c_str(), width, height); this->width = width; this->height = height; From 3b0b7dbdb1cd4852be6cd88ab5f91f5df085d9d3 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 13 Jul 2021 19:00:26 +0300 Subject: [PATCH 2/6] fix --- codecs/visdif/visdif.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp index 295c6f60..ba90ff83 100644 --- a/codecs/visdif/visdif.cpp +++ b/codecs/visdif/visdif.cpp @@ -10,6 +10,7 @@ using namespace butteraugli; static double SrgbToLinear[256]; inline void gammaLookupTable() { + SrgbToLinear[0] = 0; for (int i = 1; i < 256; ++i) { SrgbToLinear[i] = pow(i / 255.0, GAMMA); } From ef176d75654e29e86739543a29c2ecf3da458e21 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 13 Jul 2021 22:38:10 +0300 Subject: [PATCH 3/6] optimize more --- codecs/visdif/visdif.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp index ba90ff83..bc7f6305 100644 --- a/codecs/visdif/visdif.cpp +++ b/codecs/visdif/visdif.cpp @@ -12,7 +12,7 @@ static double SrgbToLinear[256]; inline void gammaLookupTable() { SrgbToLinear[0] = 0; for (int i = 1; i < 256; ++i) { - SrgbToLinear[i] = pow(i / 255.0, GAMMA); + SrgbToLinear[i] = static_cast(255.0 * pow(i / 255.0, GAMMA)); } } @@ -32,10 +32,10 @@ void planarize(std::vector& img, float* const row_b = img[2].Row(y); float* const row_a = img[3].Row(y); for (int x = 0; x < width; x++) { - row_r[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 0]]); - row_g[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 1]]); - row_b[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 2]]); - row_a[x] = static_cast(255.0 * SrgbToLinear[rgba[(y * width + x) * 4 + 3]]); + row_r[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 0]]; + row_g[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 1]]; + row_b[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 2]]; + row_a[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 3]]; } } } From 8262c79bb63393eb852fc760d901a9f8d6ff010b Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 13 Jul 2021 22:38:30 +0300 Subject: [PATCH 4/6] fix --- codecs/visdif/visdif.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp index bc7f6305..c48729ad 100644 --- a/codecs/visdif/visdif.cpp +++ b/codecs/visdif/visdif.cpp @@ -7,7 +7,7 @@ using namespace butteraugli; #define GAMMA 2.2 -static double SrgbToLinear[256]; +static float SrgbToLinear[256]; inline void gammaLookupTable() { SrgbToLinear[0] = 0; From cd20082e5d6888016e5e51b5cc2a70f5da178128 Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 19 Jul 2021 12:36:04 +0100 Subject: [PATCH 5/6] Build wasm --- codecs/visdif/visdif.cpp | 9 +++------ codecs/visdif/visdif.wasm | Bin 58337 -> 58394 bytes 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/codecs/visdif/visdif.cpp b/codecs/visdif/visdif.cpp index c48729ad..0fcb1d16 100644 --- a/codecs/visdif/visdif.cpp +++ b/codecs/visdif/visdif.cpp @@ -12,15 +12,12 @@ static float SrgbToLinear[256]; inline void gammaLookupTable() { SrgbToLinear[0] = 0; for (int i = 1; i < 256; ++i) { - SrgbToLinear[i] = static_cast(255.0 * pow(i / 255.0, GAMMA)); - } + SrgbToLinear[i] = static_cast(255.0 * pow(i / 255.0, GAMMA)); + } } // Turns an interleaved RGBA buffer into 4 planes for each color channel -void planarize(std::vector& img, - const uint8_t* rgba, - int width, - int height) { +void planarize(std::vector& img, const uint8_t* rgba, int width, int height) { assert(img.size() == 0); img.push_back(ImageF(width, height)); img.push_back(ImageF(width, height)); diff --git a/codecs/visdif/visdif.wasm b/codecs/visdif/visdif.wasm index ebb11eab9743cb24cac979ccae94a36f697499d3..96cbccb7c3ef0e5208168c184da9fed5c9eae4f3 100755 GIT binary patch delta 3647 zcmcIneQZ?65#Ms-B98* zfL%c&N2)5rn$)I5F(e`w0X^p(Nt)6m5F=)kaFBqO`w#cV`H+ zP5)@m^1Ioc*_qkj%>MS>)ie6uvwF{wT|>nNZKbWd-rrZ^OEdIfCd=p&Av|GXG`QqlsjowW99mF zYv=LD=kZyy9^;R$dgx)^*7^uv-Q2?0Jh-i#90t28D?U@{VwZ<>X3J!O4R7uPYzl=8ErkY#aG4UmXh3@Nc#8D5Xd zKw86SGUlDhY)V^*;78NCzL3%~-!oP+p64R&n?C{j2{Elx?E-*xqok2;io1f_;nDDGs@U$hX9M8e5O=U^=)GiY>3+c#Qxf!XlqomK+vnD3|q%~jxx`d~B4%8|%5B}vjyP|WZ6HZ6T zPQ(=4FdX)T09oQ}j23myf%Q8Ox(2%NG1L&IJRky&_)fd{?com&n@=j2@mR9u22w&7 zx59wf6lnX6C@)a=Hw81qJ}*MpK(OAJ+@4)HK1MSb6GnT`^!ftXsgmh1csV9x+4CtT zN3!PzzOb%xOj|~o!_B53L(cMrG2b*1cpxS-$36-ezcqH{;)v4}#6Eg4kAJx2CQ6`^ z?Mn=qLIyHdla`8&u1rSvdC_-|i*zQkVb}&X(p03H`I994mS7J$0`=!3SP1suqIWhX(W72{wR_trEOG$Skjdrig6Bsty1u zeC-NYv^AunFxwdRT2Rq~q8V?WkZ=aSTcsHsjp>L8B+-SG&d216s)xTc1#e$SxH5Xmj6PghA^kGhSaY=bIfTgjvcD(1qKst7{(d72}%%{YAA|7tkBm#6pIp571| z0x0@a2N5mdcT|rH?r_~+j)c6K)L33A*+gaT*+{8IWwJf8n#vP{a`)4ze>W)V{%z)E zjT+>c@_l$-DBmJKte8d%+_cJ_CRW^7U+tcrH-p^mHPt$-f4(?Fqxo{U_ANYj)cu62 z-J<%f2Hh+BmOFUrjcX`Jwl+FYoNfGbdN7J&1ULW-6xvJNw^y&zfMa;gAK;5xnsus@ zEv+SZcC{WYTrA?5_9Ai0vW2*6*)`(1fW1&04A}R{`nE=>{}O=uLJ;aBL8y0SLVY7> z-{&4}6DD?cWBr&iYLZ&-{+wkzz_UPLhfLrit{G0a*shmv^lqVLa^{{DRPS!z({E6% z%->r|wQl|1Sdf;wH(%MK;cNHdVm%aR>K>qWbXHfK*^_D^uuvN4G7WSoy2@ny=pl;B zr;n|hbdgQd6$f#@Vp1$9b1)H}z&ZE7z8~D#T_!(2HZL@C%V+n<-9EeHviWC&G$OU= zg2^K(pB_7#qC3u#PA75(r>5wM%k|Off8*3dqNYj|PTz_SmLpu)(zfz6b!Bw@SI+x~ zmV|n|+t5Ryr-xD~j5_Zbk)IC@mP6w?4=Cr|J#;%7$a5r>c>)eo^o_{hy;_OmI|^}M zbkw;Infp}ceLb?&9W00DR^~m?l=CDkeLZvsECW3inkTW^@!7 zcPIl1rS>nCnF*zShf==r?gq-26Nb0RzgB<4*xTRV@3zkG(8emMzn^LjaJGc8nu>}F z*S)sjc}@Ow_(3X_Bg4gbW}coy(`DW1Z8Y5-KE03dwf4+Ho#x2>?^WRW!F%r%%)lGG zI?~`Fh8rSmVh8x)V9%Au&Xv$ydF9-6njz_YKFx3o&o3ajZGJzVQ(4HFjoTJCOGGm) zKfdisx!?kRPPnlPB?Q{}@M0Cw3YmTB05!R>OV5(FLzDMjuI|K3GDtT>U`O2der_zn zErf#>N&K{+5YzEuo`etQ0~^HWqi@%|WUm~R54@%z_ugbo(WTWHo!n5rcUTzjI$WR5 zEcJr8v`B5oUI886Vr+a-RRc)O6rGKV^05U_itS7W@0*xSl#HAIIVMf^ c+T_uXi}PBLqJcNHDHahE?i9<+_y<1!1c2qI)c^nh delta 3578 zcmcInYiv}<6`pe+yKCQF`x>);f<3!nFfRi(HpVnz2e83_p=o$SNg$y#OK5N)l=7%u z1x1^v4JPBJCZwt%q}|0!)93RCP=30~^*i z@CO@sb@l!HzGW-c@O58b%a^ZQ%~vhmcyIRKUX;b!12jeM)xL&yKx;-@q9=yOsZwLj z0pg5PG{m?rK8eeB^l(vvqD>`S<9em$4^xv75r2sjzB11^NjeoMi)R)lKkjM&Oh^m7yRGzmF2sO$W*OEKVp^M81ttn>f;eptQ){EUGC9t7 zGTZ|f(jw}Eg}H79+_ddsuj@7#eae~CYgz6NuIm|>GXQfz;Wj!nVvnmU(Q%i~} zHHj%~&P0AxY?XoR;yP;O+M|NDg>|80YSD?9l$#-ZDz*qWDm7nN<0QV1o($=7ZFUpZ z_(Jv*BQpNV4H-+hxuhX4jn;!cwMehZ;LBDa#{ABzEdZOEVjFC_wE6><%Gz-ML!j)t z{_j!znZQneeiTdD+8XkyzI_2nJx)=Mm*B#4mjP)*2 z&Q2#4-|#%^8h)}wZ<1!WO^5sIFxtD@aT)3suG}yDpuAPQd+f?FbDP2(NTzG9J1OtU z<9-(=Yyhz{Dfi};j7ZWX#)Q!tFx~7U29+2c4o8#H$(u<@`9WU&XUEkHTW9sMC^uUI z43Wu0!yYy<52oS2j?gmf_}KOJ5*2EJZ^dDCFtA+Tr)EG;36gQ416hAJu% zs;UI=pm?Q)w{n8H%xFkc**hAnL-zg{Y_@KRR!%MmrtUR&Lb;@%XpSmFMNw)>7gb9y z!lQ^#i}34rs+Bt^tyaehmZ#N9=Xa=8QK6E~{=$rOzE}9OY_(-ISC{1avV4@yOGT&3 zloA;zgtQ9jP$8BPD{Romi=RYAKsEnYnMl*gz1$GfxslEVY-N>3#VjZt66qCow7C@8 zIjXJ^`P4)*N#is&U9(}M@Lb!cp5opYRb1X#mze^D3zq{W_kSJ(#d1^Wk6jiZE*Tb- zWZ%j{c*I{r>3zX{Y7Z(MVR!1t_$)oON4n#3&B(G+kQ-*IWDFcPs!&zUQ@}mUI!C_w zdGXZ9c-iB{Dl3}WpgilgQ0A6jx(TZeOTZcm)=0rt@z=OYw2{$Fm2r195QNMTWQA%W zk~~Y%c0KMa2;~_xO|GpNg|@w-9Bp?+C~pysKMaaHf1mueMhoP*>gUm3tllBtt(ib~JI1tqCPu7Z*WkQ4V-m?LXEqs( zv2G{wo1I#|vJM`k$Pv!w!p3#N>3ic@&AHLgpaXQm-Psy_Mb<^mp#3=VBbw>_K>|q~DH+pZe`t@|Bf~(EYw2-TeV{Cj;m{kb~|I0`@GY ze3dXk!1{H=rqE)!{prKOh1}0`p=ogwbcsPD8XjrSm*e;EpoQ}A{>3!kxv~GCK~3_h z&MInhUgWD=ArBPP0|gcBm2!4GPQCK| z_=ZvE*#uq5689-_#em8v9)!y{=e}PahKj^0<<`WE(BMtX*x;R*v0iy0k)T1j{n%Zj z2Q!$iV4EU_Qzr#l&KW>W(a|ff9BcRjP!oZgDo}t<=}A;$y0E3~;U0C0w0|jZD>*k5 zbB{=ug^n0ySQrBD7?fL+iE8w?;C=-ji_xuYAkM)|M8xR!h|to-n`jg%u-p4ltcpIz#kY*|txPn-P7*#ep@FQ1)G zlcfLLUYg`Qb?$k>&)D;`bgGw|F4Uksbm8^lD!kRJy&f`9^L8|BVlntLVb74;`zmOL ze6DYzQDs?E<;#6Rs&cOO-9=}w&aStfAgzO>_IATE=s1KuL^#Iqdf?;6Y#dHdVf8Nt@BCcn`b1{oy`0T2 zML^76f Date: Tue, 20 Jul 2021 19:14:23 +0100 Subject: [PATCH 6/6] Add build instructions --- codecs/visdif/BUILD.md | 14 ++++++++++++++ codecs/visdif/visdif.wasm | Bin 58394 -> 58504 bytes 2 files changed, 14 insertions(+) create mode 100644 codecs/visdif/BUILD.md diff --git a/codecs/visdif/BUILD.md b/codecs/visdif/BUILD.md new file mode 100644 index 00000000..2a77c28b --- /dev/null +++ b/codecs/visdif/BUILD.md @@ -0,0 +1,14 @@ +This codec currently needs monkey-patching of Emscripten + +``` +$ docker run --rm -it -v $(PWD):/src squoosh-cpp "/bin/bash" +# cat << EOF | patch /emsdk/upstream/emscripten/system/lib/dlmalloc.c +659c659 +< #define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void *))) +--- +> #define MALLOC_ALIGNMENT ((size_t)(16U)) +EOF +# emcc --clear-cache +# /emsdk/upstream/emscripten/embuilder build libdlmalloc --force +# emmake make +``` \ No newline at end of file diff --git a/codecs/visdif/visdif.wasm b/codecs/visdif/visdif.wasm index 96cbccb7c3ef0e5208168c184da9fed5c9eae4f3..2ac3ca8b31958d6c00d0fdf2a79a2771c5bf0e35 100755 GIT binary patch delta 12064 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 HY(M>9U*xU` delta 11642 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 V5dSH1h{>2Hazo5e4BsZc{vTxfaJv8i