Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "vnc.h"
#include "vncs.h"

/*
 * rise and run length encoding, aka rre.
 *
 * the pixel contained in r are subdivided into
 * rectangles of uniform color, each of which
 * is encoded by <color, x, y, w, h>.
 *
 * use raw encoding if it's shorter.
 *
 * for compact rre, use limited size rectangles,
 * which are shorter to encode and therefor give better compression.
 *
 * hextile encoding uses rre encoding on at most 16x16 rectangles tiled
 * across and then down the screen.
 */
static int      encrre(uchar *raw, int stride, int w, int h, int back, int pixb, uchar *buf, int maxr, uchar *done, int (*eqpix)(uchar*, int, int), uchar *(putr)(uchar*, uchar*, int, int, int, int, int, int));
static int      eqpix16(uchar *raw, int p1, int p2);
static int      eqpix32(uchar *raw, int p1, int p2);
static int      eqpix8(uchar *raw, int p1, int p2);
static int      findback(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int));
static uchar*   putcorre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h);
static uchar*   putrre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h);
static void     putpix(Vnc *v, uchar *raw, int p, int pixb);
static int      hexcolors(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int), int back, int *fore);
static uchar    *puthexfore(uchar *buf, uchar*, int, int, int x, int y, int w, int h);
static uchar    *puthexcol(uchar *buf, uchar*, int, int, int x, int y, int w, int h);
static void     sendtraw(Vnc *v, uchar *raw, int pixb, int stride, int w, int h);

/*
 * default routine, no compression, just the pixels
 */
int
sendraw(Vncs *v, Rectangle r)
{
        int pixb, stride;
        uchar *raw;

        if(!rectinrect(r, v->image->r))
                sysfatal("sending bad rectangle");

        pixb = v->bpp >> 3;
        if((pixb << 3) != v->bpp)
                sysfatal("bad pixel math in sendraw");
        stride = v->image->width*sizeof(ulong);
        if(((stride / pixb) * pixb) != stride)
                sysfatal("bad pixel math in sendraw");
        stride /= pixb;

        raw = byteaddr(v->image, r.min);

        vncwrrect(v, r);
        vncwrlong(v, EncRaw);
        sendtraw(v, raw, pixb, stride, Dx(r), Dy(r));
        return 1;
}

int
countraw(Vncs*, Rectangle)
{
        return 1;
}

/*
 * grab the image for the entire rectangle,
 * then encode each tile
 */
int
sendhextile(Vncs *v, Rectangle r)
{
        uchar *(*putr)(uchar*, uchar*, int, int, int, int, int, int);
        int (*eq)(uchar*, int, int);
        uchar *raw, *buf, *done, *traw;
        int w, h, stride, pixb, pixlg, nr, bpr, back, fore;
        int sy, sx, th, tw, oback, ofore, k, nc;

        h = Dy(r);
        w = Dx(r);
        if(h == 0 || w == 0 || !rectinrect(r, v->image->r))
                sysfatal("bad rectangle %R in sendhextile %R", r, v->image->r);

        switch(v->bpp){
        case  8:        pixlg = 0;      eq = eqpix8;    break;
        case 16:        pixlg = 1;      eq = eqpix16;   break;
        case 32:        pixlg = 2;      eq = eqpix32;   break;
        default:
                sendraw(v, r);
                return 1;
        }
        pixb = 1 << pixlg;
        stride = v->image->width*sizeof(ulong);
        if(((stride >> pixlg) << pixlg) != stride){
                sendraw(v, r);
                return 1;
        }
        stride >>= pixlg;

        buf = malloc(HextileDim * HextileDim * pixb);
        done = malloc(HextileDim * HextileDim);
        if(buf == nil || done == nil){
                free(buf);
                free(done);
                sendraw(v, r);
                return 1;
        }
        raw = byteaddr(v->image, r.min);

        vncwrrect(v, r);
        vncwrlong(v, EncHextile);
        oback = -1;
        ofore = -1;
        for(sy = 0; sy < h; sy += HextileDim){
                th = h - sy;
                if(th > HextileDim)
                        th = HextileDim;
                for(sx = 0; sx < w; sx += HextileDim){
                        tw = w - sx;
                        if(tw > HextileDim)
                                tw = HextileDim;

                        traw = raw + ((sy * stride + sx) << pixlg);

                        back = findback(traw, stride, tw, th, eq);
                        nc = hexcolors(traw, stride, tw, th, eq, back, &fore);
                        k = 0;
                        if(oback < 0 || !(*eq)(raw, back + ((traw - raw) >> pixlg), oback))
                                k |= HextileBack;
                        if(nc == 1){
                                vncwrchar(v, k);
                                if(k & HextileBack){
                                        oback = back + ((traw - raw) >> pixlg);
                                        putpix(v, raw, oback, pixb);
                                }
                                continue;
                        }
                        k |= HextileRects;
                        if(nc == 2){
                                putr = puthexfore;
                                bpr = 2;
                                if(ofore < 0 || !(*eq)(raw, fore + ((traw - raw) >> pixlg), ofore))
                                        k |= HextileFore;
                        }else{
                                putr = puthexcol;
                                bpr = 2 + pixb;
                                k |= HextileCols;
                                /* stupid vnc clients smash foreground in this case */
                                ofore = -1;
                        }

                        nr = th * tw << pixlg;
                        if(k & HextileBack)
                                nr -= pixb;
                        if(k & HextileFore)
                                nr -= pixb;
                        nr /= bpr;
                        memset(done, 0, HextileDim * HextileDim);
                        nr = encrre(traw, stride, tw, th, back, pixb, buf, nr, done, eq, putr);
                        if(nr < 0){
                                vncwrchar(v, HextileRaw);
                                sendtraw(v, traw, pixb, stride, tw, th);
                                /* stupid vnc clients smash colors in this case */
                                ofore = -1;
                                oback = -1;
                        }else{
                                vncwrchar(v, k);
                                if(k & HextileBack){
                                        oback = back + ((traw - raw) >> pixlg);
                                        putpix(v, raw, oback, pixb);
                                }
                                if(k & HextileFore){
                                        ofore = fore + ((traw - raw) >> pixlg);
                                        putpix(v, raw, ofore, pixb);
                                }
                                vncwrchar(v, nr);
                                vncwrbytes(v, buf, nr * bpr);
                        }
                }
        }
        free(buf);
        free(done);
        return 1;
}

int
counthextile(Vncs*, Rectangle)
{
        return 1;
}

static int
hexcolors(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int), int back, int *rfore)
{
        int s, es, sx, esx, fore;

        *rfore = -1;
        fore = -1;
        es = stride * h;
        for(s = 0; s < es; s += stride){
                esx = s + w;
                for(sx = s; sx < esx; sx++){
                        if((*eqpix)(raw, back, sx))
                                continue;
                        if(fore < 0){
                                fore = sx;
                                *rfore = fore;
                        }else if(!(*eqpix)(raw, fore, sx))
                                return 3;
                }
        }

        if(fore < 0)
                return 1;
        return 2;
}

static uchar*
puthexcol(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
{
        raw += p * pixb;
        while(pixb--)
                *buf++ = *raw++;
        *buf++ = (x << 4) | y;
        *buf++ = (w - 1) << 4 | (h - 1);
        return buf;
}

static uchar*
puthexfore(uchar *buf, uchar*, int, int, int x, int y, int w, int h)
{
        *buf++ = (x << 4) | y;
        *buf++ = (w - 1) << 4 | (h - 1);
        return buf;
}

static void
sendtraw(Vnc *v, uchar *raw, int pixb, int stride, int w, int h)
{
        int y;

        for(y = 0; y < h; y++)
                vncwrbytes(v, &raw[y * stride * pixb], w * pixb);
}

static int
rrerects(Rectangle r, int split)
{
        return ((Dy(r) + split - 1) / split) * ((Dx(r) + split - 1) / split);
}

enum
{
        MaxCorreDim     = 48,
        MaxRreDim       = 64,
};

int
countrre(Vncs*, Rectangle r)
{
        return rrerects(r, MaxRreDim);
}

int
countcorre(Vncs*, Rectangle r)
{
        return rrerects(r, MaxCorreDim);
}

static int
_sendrre(Vncs *v, Rectangle r, int split, int compact)
{
        uchar *raw, *buf, *done;
        int w, h, stride, pixb, pixlg, nraw, nr, bpr, back, totr;
        int (*eq)(uchar*, int, int);

        totr = 0;
        h = Dy(r);
        while(h > split){
                h = r.max.y;
                r.max.y = r.min.y + split;
                totr += _sendrre(v, r, split, compact);
                r.min.y = r.max.y;
                r.max.y = h;
                h = Dy(r);
        }
        w = Dx(r);
        while(w > split){
                w = r.max.x;
                r.max.x = r.min.x + split;
                totr += _sendrre(v, r, split, compact);
                r.min.x = r.max.x;
                r.max.x = w;
                w = Dx(r);
        }
        if(h == 0 || w == 0 || !rectinrect(r, v->image->r))
                sysfatal("bad rectangle in sendrre");

        switch(v->bpp){
        case  8:        pixlg = 0;      eq = eqpix8;    break;
        case 16:        pixlg = 1;      eq = eqpix16;   break;
        case 32:        pixlg = 2;      eq = eqpix32;   break;
        default:
                sendraw(v, r);
                return totr + 1;
        }
        pixb = 1 << pixlg;
        stride = v->image->width*sizeof(ulong);
        if(((stride >> pixlg) << pixlg) != stride){
                sendraw(v, r);
                return totr + 1;
        }
        stride >>= pixlg;

        nraw = w * pixb * h;
        buf = malloc(nraw);
        done = malloc(w * h);
        if(buf == nil || done == nil){
                free(buf);
                free(done);
                sendraw(v, r);
                return totr + 1;
        }
        memset(done, 0, w * h);

        raw = byteaddr(v->image, r.min);

        if(compact)
                bpr = 4 * 1 + pixb;
        else
                bpr = 4 * 2 + pixb;
        nr = (nraw - 4 - pixb) / bpr;
        back = findback(raw, stride, w, h, eq);
        if(compact)
                nr = encrre(raw, stride, w, h, back, pixb, buf, nr, done, eq, putcorre);
        else
                nr = encrre(raw, stride, w, h, back, pixb, buf, nr, done, eq, putrre);
        if(nr < 0){
                vncwrrect(v, r);
                vncwrlong(v, EncRaw);
                sendtraw(v, raw, pixb, stride, w, h);
        }else{
                vncwrrect(v, r);
                if(compact)
                        vncwrlong(v, EncCorre);
                else
                        vncwrlong(v, EncRre);
                vncwrlong(v, nr);
                putpix(v, raw, back, pixb);
                vncwrbytes(v, buf, nr * bpr);
        }
        free(buf);
        free(done);

        return totr + 1;
}

int
sendrre(Vncs *v, Rectangle r)
{
        return _sendrre(v, r, MaxRreDim, 0);
}

int
sendcorre(Vncs *v, Rectangle r)
{
        return _sendrre(v, r, MaxCorreDim, 1);
}

static int
encrre(uchar *raw, int stride, int w, int h, int back, int pixb, uchar *buf,
        int maxr, uchar *done, int (*eqpix)(uchar*, int, int),
        uchar *(*putr)(uchar*, uchar*, int, int, int, int, int, int))
{
        int s, es, sx, esx, sy, syx, esyx, rh, rw, y, nr, dsy, dp;

        es = stride * h;
        y = 0;
        nr = 0;
        dp = 0;
        for(s = 0; s < es; s += stride){
                esx = s + w;
                for(sx = s; sx < esx; ){
                        rw = done[dp];
                        if(rw){
                                sx += rw;
                                dp += rw;
                                continue;
                        }
                        if((*eqpix)(raw, back, sx)){
                                sx++;
                                dp++;
                                continue;
                        }

                        if(nr >= maxr)
                                return -1;

                        /*
                         * find the tallest maximally wide uniform colored rectangle
                         * with p at the upper left.
                         * this isn't an optimal parse, but it's pretty good for text
                         */
                        rw = esx - sx;
                        rh = 0;
                        for(sy = sx; sy < es; sy += stride){
                                if(!(*eqpix)(raw, sx, sy))
                                        break;
                                esyx = sy + rw;
                                for(syx = sy + 1; syx < esyx; syx++){
                                        if(!(*eqpix)(raw, sx, syx)){
                                                if(sy == sx)
                                                        break;
                                                goto breakout;
                                        }
                                }
                                if(sy == sx)
                                        rw = syx - sy;
                                rh++;
                        }
                breakout:;

                        nr++;
                        buf = (*putr)(buf, raw, sx, pixb, sx - s, y, rw, rh);

                        /*
                         * mark all pixels done
                         */
                        dsy = dp;
                        while(rh--){
                                esyx = dsy + rw;
                                for(syx = dsy; syx < esyx; syx++)
                                        done[syx] = esyx - syx;
                                dsy += w;
                        }

                        sx += rw;
                        dp += rw;
                }
                y++;
        }
        return nr;
}

/*
 * estimate the background color
 * by finding the most frequent character in a small sample
 */
static int
findback(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int))
{
        enum{
                NCol = 6,
                NExamine = 4
        };
        int ccount[NCol], col[NCol], i, wstep, hstep, x, y, pix, c, max, maxc;

        wstep = w / NExamine;
        if(wstep < 1)
                wstep = 1;
        hstep = h / NExamine;
        if(hstep < 1)
                hstep = 1;

        for(i = 0; i< NCol; i++)
                ccount[i] = 0;
        for(y = 0; y < h; y += hstep){
                for(x = 0; x < w; x += wstep){
                        pix = y * stride + x;
                        for(i = 0; i < NCol; i++){
                                if(ccount[i] == 0){
                                        ccount[i] = 1;
                                        col[i] = pix;
                                        break;
                                }
                                if((*eqpix)(raw, pix, col[i])){
                                        ccount[i]++;
                                        break;
                                }
                        }
                }
        }
        maxc = ccount[0];
        max = 0;
        for(i = 1; i < NCol; i++){
                c = ccount[i];
                if(!c)
                        break;
                if(c > maxc){
                        max = i;
                        maxc = c;
                }
        }
        return col[max];
}

static uchar*
putrre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
{
        raw += p * pixb;
        while(pixb--)
                *buf++ = *raw++;
        *buf++ = x >> 8;
        *buf++ = x;
        *buf++ = y >> 8;
        *buf++ = y;
        *buf++ = w >> 8;
        *buf++ = w;
        *buf++ = h >> 8;
        *buf++ = h;
        return buf;
}

static uchar*
putcorre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
{
        raw += p * pixb;
        while(pixb--)
                *buf++ = *raw++;
        *buf++ = x;
        *buf++ = y;
        *buf++ = w;
        *buf++ = h;
        return buf;
}

static int
eqpix8(uchar *raw, int p1, int p2)
{
        return raw[p1] == raw[p2];
}

static int
eqpix16(uchar *raw, int p1, int p2)
{
        return ((ushort*)raw)[p1] == ((ushort*)raw)[p2];
}

static int
eqpix32(uchar *raw, int p1, int p2)
{
        return ((ulong*)raw)[p1] == ((ulong*)raw)[p2];
}

static void
putpix(Vnc *v, uchar *raw, int p, int pixb)
{
        vncwrbytes(v, raw + p * pixb, pixb);
}