2009年10月31日 星期六

漸層字


#include <windows.h>

enum {W=800, H=600};
HBITMAP hBmp;
HWND    hwnd;
HDC     hdc, hdcMem;
UCHAR*  pic;


//將 c1 與 c2 以 nc1:nc-nc1 比例混色, 傳回 BGR 格式

inline int mixColor (int c1, int c2, int nc1, int nc)
{
   int a = nc - nc1; typedef UCHAR U;
   int r = (U(c1>>16)*nc1 + U(c2>>16)*a)/ nc;
   int g = (U(c1>>8) *nc1 + U(c2>>8) *a)/ nc;
   int b = (U(c1)    *nc1 + U(c2)    *a)/ nc;
   return (b<<16)|(g<<8)|r;
}

//在 (tx, ty) 處秀出字型=face, 大小=size 的文字 s, 顏色由 c1 過渡到 c2,

void show_text (int tx, int ty, wchar_t* s, int c1, int c2,
                int size=24, wchar_t* face = L"標楷體")                           
{   
    static int bits = GetDeviceCaps (hdc, BITSPIXEL) *
                      GetDeviceCaps (hdc, PLANES);
    HFONT   font    = CreateFont (size,0,0,0,FW_BOLD,0,0,0,0,0,0,0,0,face);
    HGDIOBJ oldFont = SelectObject (hdcMem, font);
    SetBkColor (hdcMem,0);
    SetTextColor (hdcMem, 93);
    TextOut (hdcMem, 0,0, s, wcslen(s));
    SelectObject (hdcMem, oldFont);     
    DeleteObject (font);
   
    int p,x,y, h = size, w = size * wcslen(s);
    if (w > W) w = W;
    if (h > H) h = H;

    BITMAPINFO info0 = {{40, w, h, 1, bits, 0,0,0,0,0,0}, {{0}}};
    GetDIBits (hdcMem, hBmp, 0, h, pic, &info0, DIB_RGB_COLORS);

    if (bits == 32) {
        int *o = (int*) pic;
        for (p=y=0; y<h; ++y, p+=w)
            for (x=0; x<w; ++x)
                if (RGB(0,0,93) == o[p+x])
                    o[p+x] = mixColor (c1,c2, y,h);
                else o[p+x] = 0;            
    }
    BITMAPINFO info = {{40, w, h, 1, bits, 0,0,0,0,0,0}, {{0}}};
    SetDIBitsToDevice (hdc, tx,ty,w,h, 0,0,0,h, pic, &info, DIB_RGB_COLORS);
}

void init()
{
    SetConsoleTitle (L"Conso");
    SMALL_RECT size = {0, 0, W-1, H-1};      
    SetConsoleWindowInfo (GetStdHandle (STD_OUTPUT_HANDLE), TRUE, &size);  
   
    hdc    = GetDC (hwnd = FindWindowW (0, L"Conso"));
    hdcMem = CreateCompatibleDC (hdc);
    hBmp   = CreateCompatibleBitmap (hdc, W, H);     
    pic    = new UCHAR[W*H];
    SelectObject (hdcMem, hBmp);   
}
void free()
{
    delete[] pic;
    ReleaseDC (hwnd, hdc);
    DeleteObject (hBmp);
    DeleteDC (hdcMem);   
}

int main (/* daviddr 081104 */)
{
    init();   
    show_text (30,80, L"漸層字!", RGB(255,10,10), RGB(255,250,50));
    show_text (30,120, L"這是48號字體", RGB(55,10,220), RGB(255,90,90), 48);
    system ("pause");
    free();
    return 0;
}

反編譯

 
  「由執行檔轉回 C++ 碼!?」

欲取 VC 資源可用 eXeScope 一類的東西,
欲得知原始碼大略寫法的話:
Dcc,EXE2C,REC,Boomerang,都是 for C...
IDA pro 的 plugin Hex-Rays 可轉出類似 C 的語法。
或許 SourceForge 上有其他更瘋狂的東西 XD

原理是先將 binary code 反組譯 (形如 sourcer、dumpbin、
IDA pro、w32dasm),再將 asm 解釋成 C。

但...

假設無 platform, endianness 等問題,反編譯仍是困難重重。
由於符號表可能不存在,code 可能因 optimization 或 (C++的)
inline 而變更佈局,且反編譯為一對多映射,C++ 另有 mangle,
vtbl, 模版代換 等問題,產生的結果將比 Perl2C 還難懂 ==;

對 C++ 而言,與其 decompile,不若 rewrite 之。

若是 C# 就容易多了,他的 MSIL 和 Java 的 b-code 一樣,
原始資訊盡在其中 (這樣才能被 interpreter 正確解譯)。
一堆工具如 Ildasm, Reflector, salamande 都可以反編譯出可讀性高的碼。

求根

 
以下程式輸出為:
0.962398
-0.040659
預測值已跨出區間!
0.000000

前兩者在 [0,1] 「附近」找過零點,並不保證不越區。
此函式在那附近正好有兩個: 0.962398 與 -0.040659
Secant 根據給出的初始域,往 -0.040659 靠。
至於 Newton-Raphson 就更不可靠了,因發生越區而傳回 0。
若將初始域放大, Newton-Raphson 便可找到根值。

typedef double DF;              //double float
typedef DF (*FP)(DF);

DF abs (DF f) {if (f<0) f=-f; return f;}

//以精確度 accuracy, 在 loop 次迭代內求 f 在 [x1,x2] 中的根,
//注意! 參數需滿足 x2>x1, 且 [x1,x2] 中至少包含一根

DF false_pos (FP f, DF x1, DF x2, DF accuracy, int loop=64)
{
    DF acc, x,y, y1 = f(x1), y2 = f(x2);

    for (int i=0; i<loop; ++i) {
        y = f (x = x1 + (x2-x1) * y1/(y1-y2));
        if (y < 0) acc=x1-x, x1=x, y1=y;
        else       acc=x2-x, x2=x, y2=y;
        if (abs(acc) < accuracy || 0==y) return x;
    }
    puts ("root not be found!");
    return 0;
}

DF secant (FP f, DF x1, DF x2, DF accuracy, int loop=64)
{
    DF acc, dx, x = x2, y1 = f(x1), y = f(x2);
    if (abs(y1) < abs(y)) x=x1, x1=x2, dx=y1, y1=y, y=dx;

    for (int i=0; i<loop; ++i) {        
        dx = (x1-x) * y/(y-y1);
        x1 = x;
        y1 = y;
        x += dx;
        y = f (x);
        if (abs(dx) < accuracy || 0==y) return x;
    }
    puts ("root not be found!");
    return 0;
}

//df 為 f 的導函數
DF newton (FP f, FP df, DF x1, DF x2, DF accuracy, int loop=64)
{
    DF acc, dx, x = (x1+x2)*.5;   
    for (int i=0; i<loop; ++i) {        
        x -= dx = f(x) / df(x);
        if ((x1-x)*(x-x2) < 0) {puts("\n預測值已跨出區間!"); return 0;}
        if (abs(dx) < accuracy) return x;
    }
    puts ("root not be found!");
    return 0;
}

DF f (DF x) { return (((230*x+18)*x+9)*x-221)*x-9; }
DF df(DF x) { return ((230*x+18)*x+9)*x-221; }

int main()
{
    printf ("\n%f", false_pos (f, 0,1, 1e-6));
    printf ("\n%f", secant (f, 0,1, 1e-6));
    printf ("\n%f", newton (f, df, 0,1, 1e-6));
    return 0;
}

Fractal

 
底下秀出 4 種圖:

1.Koch curve
2.Sierpinski’s Gasket
3.Tree curve
4.Julia set

其中,sierpinski 外緣線需自行接上,
Julia set 觀察窗需手動調整:

#include <windows.h>
#include <math.h>
#include <complex>

#define RGB(r,g,b) (b<<16)|(g<<8)|r
enum {W=800, H=600};
HDC hdc;
HANDLE  hwnd, hIn, hOut;

namespace Turtle
{
    //--------------------- 作圖 -----------------------

    const float rd = 3.1415927/180.0;
    int* pic, X, Y, A, ang, step, C;   //定義: 畫布, 座標, 角度, 步距, 顏色
    float scale;
    float branch;

    void clear ()
    {
        int i = W*H; while (i--) pic[i] = 0;
    }
    void show ()                //注意: 顯示器色彩格式需為 32-bits
    {
        BITMAPINFO info = {{40, W, -H, 1,32,0,0,0,0,0,0}, {{0}}};
        SetDIBitsToDevice 
           (hdc, 0,0,W,H, 0,0,0,H, pic, &info, DIB_RGB_COLORS);
    }
    void set (int x, int y, int d=0, int a=0) {X=x; Y=y; ang=a; step=d;}
    void setxy (int x, int y) {set(x,y);}
    void pixel (int x, int y, int c)                //繪點
    {
        if (x<0 || y<0 || x>=W || y>=H) return;
        pic[y*W+x] = c;
    }  
    void line (int x, int y, int x2, int y2, int c=0xffffff)
    {
        int dx= x2-x, dy= y2-y, dirx=1, diry=1, b=0;
        if (dx < 0)  {dx = -dx; dirx= -1;}           
        if (dy < 0)  {dy = -dy; diry= -1;}           
        pixel (x,y,c);
        if (dx<=1 && dy<=1)  return;                 //兩點間隔太小,不予處理
        if (dx < dy) {                               //以Y軸為基準繪製線段
            int hy= dy>>1, dotn= dy-2, n=1;
            do {b += dx;   if (b > hy)  {b -= dy; x += dirx;}
                y += diry; pixel (x,y,c); n++;
            }while (dotn--);
        }else{                                       //以X軸為基準繪製線段
            int hx= dx>>1, dotn= dx-2, n=1;
            do {b += dy;   if (b > hx)  {b -= dx; y += diry;}
                x += dirx; pixel (x,y,c); n++;
            }while (dotn--);
        }
    }
    void move (int n, int c=0xffffff)
    {
        int x = n* cos (rd*A), y = n* sin (rd*A);
        line (X, Y, X+x, Y+y, c);
        X+=x; Y+=y;
    }
    void mov (int c=0xffffff) {move (step,c);}
    void turn (int a)
    {
        A+=a;
        if (A>360) A-=360;
        else if (A<360) A+=360;
    }

    //--------------------- 碎形 -----------------------

    void koch (int n)
    {
        if (0==n) {mov(); return;}
        koch (n-1);  turn (-60);
        koch (n-1);  turn (120);
        koch (n-1);  turn (-60);
        koch (n-1);
    }
    void sierpinski (int n)
    {
        if (0==n) {mov(); return;}
        sierpinski (n-1); turn(-120);
        sierpinski (n-1); turn(120);
        sierpinski (n-1); turn(120);
        sierpinski (n-1); turn(-120);
        sierpinski (n-1);
    }
    void tree (int n, int x, int y, float len, float ang)
    {
        if (0==n) return;
        setxy(x,y); A=-ang;  move(len); x=X; y=Y;
        tree (n-1, x,y, len/scale, ang-branch);
        tree (n-1, x,y, len/scale, ang+branch);
    }
    void julia (int n)
    {
        // z = x+iy,  F(z) = z^2+C,  |F(z)| = x^2+y^2
        int i, k;
        float v,xp,yp, x0,y0,x1,y1,x,y,z,cx,cy;
        cx=-0.5;  cy=0.5;
        for (x0=-2.0; x0<=2.0; x0+=0.005)
            for (y0=-2.0; y0<=2.0; y0+=0.005){
                for (x = x0, y = y0, k = 0;
                    x*x+y*y<4.0 && k<n; ++k){    //迭代 n 次,直到數列發散
                    x1 = x*x - y*y + cx;
                    y1 = 2*x*y + cy;
                    x = x1; y = y1;
                }
                xp = W/4*x0+W/2;
                yp = -H/4*y0+H/2;
                v = x*x + y*y;
                if (v < 4.0) {
                    i = v*64;
                    pixel (xp, yp, RGB (100,220,i));
                }else
                    pixel (xp, yp, RGB (30,100,200));
            }
    }
};
using namespace Turtle;


int main (/* daviddr 081020 */)
{
    SetConsoleTitle (L"Conso");
    hdc  = GetDC (FindWindowW (0, L"Conso"));
    hIn  = GetStdHandle (STD_INPUT_HANDLE);
    hOut = GetStdHandle (STD_OUTPUT_HANDLE);
    SMALL_RECT size = {0, 0, W-1, H-1};      
    SetConsoleWindowInfo (hOut, TRUE, &size);   //變更視窗大小
    Turtle::pic = new int[W*H];    //畫布
   
    clear ();
    set (20,380, 8);  koch (4);   
    show ();
    system ("pause");

    clear ();
    set (20,380, 8);  sierpinski (5);   
    show ();
    system ("pause");

    clear ();
    scale = 1.3;
    branch = 23;
    tree (8, 200,380, 64, 80);   
    show ();
    system ("pause");

    clear();
    julia(32);
    show ();
    system ("pause");

    delete [] Turtle::pic;
    DeleteDC (hdc);
    CloseHandle (hIn);
    CloseHandle (hOut);

    return 0;
}
sierpinski 外緣線由小而大逐次爬行的作法,
很難排除累計誤差,無法形成正三角。
可改為由外而內,用內插方式來算落點:

private:
    void sierpinski (int n, int x1, int y1, int x2, int y2, int x3, int y3)   
    {
        if (0 == n) {            
            setxy (x1, y1);
            mov(); turn (-120);
            mov(); turn (-120);
            mov(); turn (-120);
            return;
        }
        sierpinski (n-1, x1,y1, x3,y1, (x1+x3)/2, (y1+y3)/2);
        sierpinski (n-1, x3,y1, x2,y2, (x2+x3)/2, (y2+y3)/2);
        sierpinski (n-1, (x1+x3)/2, (y1+y3)/2, (x2+x3)/2, (y2+y3)/2, x3, y3);
    }

public:
    void sierpin2 (int n)                       //自頂至底
    {
        int d = step * (1<<n);
        sierpinski (n, X,Y, X+d,Y, X+d/2, int(float(Y)-1.72384*d/2));
    }

下面是呼叫 koch (4), sierpin2 (5), tree (8,..) 的結果:


1除以0

 
Y=1/X, X 趨近0時, Y 是多少?

if x -> +0 then 1/x = +∞
if x -> -0 then 1/x = -∞
but +∞ ≠ -∞ , paradox

If take it as unsigned infinity, 1/0 = ∞
但此處的「0」意指「無窮小量」,與 1 的「位階」不同。
由於「1」非無窮小,故結果為 ∞
這種定義有個好處是,在 plane of complex numbers z=x+iy
上加入無窮遠點 ∞ = 1/0,則 z 可在球面上表達出來。

也就是說,在 complex plane C 上,

令 S = C∪{∞}
f(z) = z, z ∈ {∞}
g(z) = 1 / z, z ∈ {0}

並定義 1/∞ = 0
則 { f, g } 為 S 之相容圖集,且 S 形成球面。
由於 C 是一種 Riemann surface (1D complex manifold)
此球面便稱為 Riemann sphere。

Riemann sphere 是緊致的,
相當於閉合且有界的歐幾裡德空間子集,
緊空間可以某種方式類似於有限集合,
且定義在緊集上的連續實值函數一致連續,
因而可施以微積分運算。


唉呀我到底在說啥..XD

JPEG編碼流程

 
若不牽扯到 Jpeg2000 或 Progressive JPEG,只針對 JFIF 基礎標準,
或是只採用 YCbCr + Baseline 模式 + Huffman 編碼的 TIFF 標準,
其作法可簡述如下:

將圖的長寬 zero padding 成 8 的倍數,
以 8x8 區塊為處理單位,理由是:
1. 做成晶片時省材料費 (開玩笑的?),
2. 程式師偏好 2 的冪次方以利記憶體對齊並加速運算 (半開玩笑的..),
3. 一般圖像的連續色調常侷限於 17x17 的區塊裡...

將 RGB 轉到均勻色彩空間 YCbCr (最常用的 YUV 一族),
由於綠光在可見光譜中所佔範圍較大,導致人視網膜上紅藍細胞較少,
加上柱狀細胞占多數,因此我們可保留 Y,而捨棄一些 Cb Cr 訊息,
Jpeg 可施行 4:4:4、4:2:2、4:2:0 3 種 downsampling (三選一)。

之後,將 Y、Cb、Cr 值正規化至 -128~127 (一般,Y 要扣掉 128),
再分別進行 Forward DCT 變換該矩陣的底基向量
(這部份有許多加速法,例如將 8x8 DCT 分解成 x, y 方向的兩個 1D DCT,
或是用 AA&N 並搭配他們的量化表),然後將各值除以quantization table
(如 CCIR 601)中對應的值,再取 round。
採用 DCT 而不用 FFT,是因為他能以較少的係數來逼近連續性資料。
(圖釋: http://www.cs.cf.ac.uk/Dave/Multimedia/node231.html)

為了將盡可能多的 0 靠在一起,以利壓縮資料,
需將量化後表中的 AC 值以 Zig-Zag 順序重新編排,
(相當於從低頻排到高頻) 以 EOB 標籤 (1010) 取代尾端那些 0。
至於中間的 0,可用 RLE 壓縮掉,
RLE 中每組值分成 high 4-bits 與 low n-bits 兩部分,
high 4-bits 表示 0 的連續個數,(這和 Jpeg 標籤採用 4-bits 有關)
low bits 表示接在連續 0 後的值,以 Huffman 編碼表之。
low bits 表示的值有可能是 0,因為 high 4-bits 最多只能紀錄
15 個連續的 0,若有 15 個以上的連續 0,low bits 便是 0 了。

因為相鄰兩方塊的平均色彩 (也就是 DC 值) 一般很接近,
也就是說他們的差值很小,可用少量 bits 表示,
便以 DPCM 對區塊間的 DC 值進行 Predictive coding。

最後在各資料前頭貼標籤,存檔。
處理 jpg 檔中的眾多標籤也是件麻煩工作。

如何正確做 Jpeg Canonical Huffman Code 對應的說法很雜,
你可以用 ISO/IEC 10918-1 中給出的 DC, AC 預設編碼表試試看。
Jpeg codec 要寫的完善是件苦工喔...


簡單說來,對 8x8 區塊裡的 63 個 AC 值,以及區塊間 DC 的差值,
分別使用不同的 Huffman 表去編碼。而且 Y 與 CbCr 各有其編碼表,
所以總共有 4 份碼表。

以 Y 成分的 DC 差值為例,將每組差值表示成如下形式:
(Huffman識別碼, 差值的 binary code)

底下用「HI」簡稱「Huffman識別碼」,
用「DL」簡稱「差值 binary code 的長度」。
其碼表為:
DL HI長度 HI ----------------------- 0 2 00 1 3 010 2 3 011 3 3 100 4 3 101 5 3 110 6 4 1110 7 5 11110
若現在 Y 成分有 3 個區塊,其 DC 值分別是 58, 50, 53
安插 0 到最前端,便利差值運算,形成 0, 58, 50, 53。
將串列中元素,後者減前者,得差值為:

58 -8 3

差值為負時,先取絕對值,再將位元反相,故得:

111010 0111 11

111010 長度是 7,對照碼表得 HI 為 11110,故編碼成 11110 111010
0111 長度是 4,對照碼表得 HI 為 101,故編碼成 101 0111
11 長度是 2,對照碼表得 HI 為 00,故編碼成 00 11
最後寫入:

11110 111010 + 63個AC編碼
101 0111 + 63個AC編碼
00 11 + 63個AC編碼

為何要安插1個0到最前端做運算?
因為解碼時需要知道第一個 DC 值,才能將之後的 DC 值還原回來,
否則只憑 -8, 3,並無法還原回 58, 50, 53,之後的 IDCT 亦會出錯。

進制轉換

 
10進位小數轉2進位:
//這種版本是刻意為之的 :-)

char* float_to_binary (float f)             //進制轉換
{
    const int N = 128;
    static char s[N];     
    char *p = s;
    int b=0, i=N, n=f;                      //b 將指向整數部分
                                            //最右端的位置
    while (i--) s[i] = '0';

    if (f < 0)  {++b; *p++='-'; n= f= -f;}  //處理負數
    if (n == 0) {++b; p++;}                 //若是 0
   
    if (n) {                                //算整數部分
        i = n; while (i>>=1) ++b;            
        p = s + b;
        i = n; do *p--+= i&1; while (i>>=1); 
        p = s + b + 1;
    }   
    *p++ = f-n ?'.': 0;                     //是否加小數點
    while (f -= n) *p+++= n = f*=2;         //算小數部分
    *p = 0;
    return s;
}

int main()
{
    cout << float_to_binary (-20.375);
}
若不處理小數:
#include <stdio.h>
int main () 
{
    for (int m,i=0; i<257 && printf("\n%3d\t",i); ++i)
        for (m=512; m=m>>1; putchar(i&m?49:48));
}
或者這樣:(注意: 在不同環境裡會有 Big/Little Endian 與 bit align 問題)
#include <stdio.h>
int main() 
{
    union {
        unsigned n;
        struct {unsigned a:1,b:1,c:1,d:1,e:1,f:1,g:1,h:1,i:1;};
        void pnt() {
            printf ("\n%3d\t%d%d%d%d%d%d%d%d%d", n,i,h,g,f,e,d,c,b,a);
        }
    } b = {0}; 
    while (++b.n<257) b.pnt();
}