|  | /* | 
|  | * rotate an image 180° in O(log Dx + log Dy) /dev/draw writes, | 
|  | * using an extra buffer same size as the image. | 
|  | * | 
|  | * the basic concept is that you can invert an array by inverting | 
|  | * the top half, inverting the bottom half, and then swapping them. | 
|  | * the code does this slightly backwards to ensure O(log n) runtime. | 
|  | * (If you do it wrong, you can get O(log² n) runtime.) | 
|  | * | 
|  | * This is usually overkill, but it speeds up slow remote | 
|  | * connections quite a bit. | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <bio.h> | 
|  | #include <draw.h> | 
|  | #include <thread.h> | 
|  | #include <cursor.h> | 
|  | #include "page.h" | 
|  |  | 
|  | int ndraw = 0; | 
|  | enum { | 
|  | Xaxis = 0, | 
|  | Yaxis = 1, | 
|  | }; | 
|  |  | 
|  | Image *mtmp; | 
|  |  | 
|  | void | 
|  | writefile(char *name, Image *im, int gran) | 
|  | { | 
|  | static int c = 100; | 
|  | int fd; | 
|  | char buf[200]; | 
|  |  | 
|  | snprint(buf, sizeof buf, "%d%s%d", c++, name, gran); | 
|  | fd = create(buf, OWRITE, 0666); | 
|  | if(fd < 0) | 
|  | return; | 
|  | writeimage(fd, im, 0); | 
|  | close(fd); | 
|  | } | 
|  |  | 
|  | void | 
|  | moveup(Image *im, Image *tmp, int a, int b, int c, int axis) | 
|  | { | 
|  | Rectangle range; | 
|  | Rectangle dr0, dr1; | 
|  | Point p0, p1; | 
|  |  | 
|  | if(a == b || b == c) | 
|  | return; | 
|  |  | 
|  | drawop(tmp, tmp->r, im, nil, im->r.min, S); | 
|  |  | 
|  | switch(axis){ | 
|  | case Xaxis: | 
|  | range = Rect(a, im->r.min.y,  c, im->r.max.y); | 
|  | dr0 = range; | 
|  | dr0.max.x = dr0.min.x+(c-b); | 
|  | p0 = Pt(b, im->r.min.y); | 
|  |  | 
|  | dr1 = range; | 
|  | dr1.min.x = dr1.max.x-(b-a); | 
|  | p1 = Pt(a, im->r.min.y); | 
|  | break; | 
|  | case Yaxis: | 
|  | default: | 
|  | range = Rect(im->r.min.x, a,  im->r.max.x, c); | 
|  | dr0 = range; | 
|  | dr0.max.y = dr0.min.y+(c-b); | 
|  | p0 = Pt(im->r.min.x, b); | 
|  |  | 
|  | dr1 = range; | 
|  | dr1.min.y = dr1.max.y-(b-a); | 
|  | p1 = Pt(im->r.min.x, a); | 
|  | break; | 
|  | } | 
|  | drawop(im, dr0, tmp, nil, p0, S); | 
|  | drawop(im, dr1, tmp, nil, p1, S); | 
|  | } | 
|  |  | 
|  | void | 
|  | interlace(Image *im, Image *tmp, int axis, int n, Image *mask, int gran) | 
|  | { | 
|  | Point p0, p1; | 
|  | Rectangle r0; | 
|  |  | 
|  | r0 = im->r; | 
|  | switch(axis) { | 
|  | case Xaxis: | 
|  | r0.max.x = n; | 
|  | p0 = (Point){gran, 0}; | 
|  | p1 = (Point){-gran, 0}; | 
|  | break; | 
|  | case Yaxis: | 
|  | default: | 
|  | r0.max.y = n; | 
|  | p0 = (Point){0, gran}; | 
|  | p1 = (Point){0, -gran}; | 
|  | break; | 
|  | } | 
|  |  | 
|  | drawop(tmp, im->r, im, display->opaque, im->r.min, S); | 
|  | gendrawop(im, r0, tmp, p0, mask, mask->r.min, S); | 
|  | gendrawop(im, r0, tmp, p1, mask, p1, S); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Halve the grating period in the mask. | 
|  | * The grating currently looks like | 
|  | * ####____####____####____####____ | 
|  | * where #### is opacity. | 
|  | * | 
|  | * We want | 
|  | * ##__##__##__##__##__##__##__##__ | 
|  | * which is achieved by shifting the mask | 
|  | * and drawing on itself through itself. | 
|  | * Draw doesn't actually allow this, so | 
|  | * we have to copy it first. | 
|  | * | 
|  | *     ####____####____####____####____ (dst) | 
|  | * +   ____####____####____####____#### (src) | 
|  | * in  __####____####____####____####__ (mask) | 
|  | * =========================================== | 
|  | *     ##__##__##__##__##__##__##__##__ | 
|  | */ | 
|  | int | 
|  | nextmask(Image *mask, int axis, int maskdim) | 
|  | { | 
|  | Point o; | 
|  |  | 
|  | o = axis==Xaxis ? Pt(maskdim,0) : Pt(0,maskdim); | 
|  | drawop(mtmp, mtmp->r, mask, nil, mask->r.min, S); | 
|  | gendrawop(mask, mask->r, mtmp, o, mtmp, divpt(o,-2), S); | 
|  | //	writefile("mask", mask, maskdim/2); | 
|  | return maskdim/2; | 
|  | } | 
|  |  | 
|  | void | 
|  | shuffle(Image *im, Image *tmp, int axis, int n, Image *mask, int gran, | 
|  | int lastnn) | 
|  | { | 
|  | int nn, left; | 
|  |  | 
|  | if(gran == 0) | 
|  | return; | 
|  | left = n%(2*gran); | 
|  | nn = n - left; | 
|  |  | 
|  | interlace(im, tmp, axis, nn, mask, gran); | 
|  | //	writefile("interlace", im, gran); | 
|  |  | 
|  | gran = nextmask(mask, axis, gran); | 
|  | shuffle(im, tmp, axis, n, mask, gran, nn); | 
|  | //	writefile("shuffle", im, gran); | 
|  | moveup(im, tmp, lastnn, nn, n, axis); | 
|  | //	writefile("move", im, gran); | 
|  | } | 
|  |  | 
|  | void | 
|  | rot180(Image *im) | 
|  | { | 
|  | Image *tmp, *tmp0; | 
|  | Image *mask; | 
|  | Rectangle rmask; | 
|  | int gran; | 
|  |  | 
|  | if(chantodepth(im->chan) < 8){ | 
|  | /* this speeds things up dramatically; draw is too slow on sub-byte pixel sizes */ | 
|  | tmp0 = xallocimage(display, im->r, CMAP8, 0, DNofill); | 
|  | drawop(tmp0, tmp0->r, im, nil, im->r.min, S); | 
|  | }else | 
|  | tmp0 = im; | 
|  |  | 
|  | tmp = xallocimage(display, tmp0->r, tmp0->chan, 0, DNofill); | 
|  | if(tmp == nil){ | 
|  | if(tmp0 != im) | 
|  | freeimage(tmp0); | 
|  | return; | 
|  | } | 
|  | for(gran=1; gran<Dx(im->r); gran *= 2) | 
|  | ; | 
|  | gran /= 4; | 
|  |  | 
|  | rmask.min = ZP; | 
|  | rmask.max = (Point){2*gran, 100}; | 
|  |  | 
|  | mask = xallocimage(display, rmask, GREY1, 1, DTransparent); | 
|  | mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent); | 
|  | if(mask == nil || mtmp == nil) { | 
|  | fprint(2, "out of memory during rot180: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | rmask.max.x = gran; | 
|  | drawop(mask, rmask, display->opaque, nil, ZP, S); | 
|  | //	writefile("mask", mask, gran); | 
|  | shuffle(im, tmp, Xaxis, Dx(im->r), mask, gran, 0); | 
|  | freeimage(mask); | 
|  | freeimage(mtmp); | 
|  |  | 
|  | for(gran=1; gran<Dy(im->r); gran *= 2) | 
|  | ; | 
|  | gran /= 4; | 
|  | rmask.max = (Point){100, 2*gran}; | 
|  | mask = xallocimage(display, rmask, GREY1, 1, DTransparent); | 
|  | mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent); | 
|  | if(mask == nil || mtmp == nil) { | 
|  | fprint(2, "out of memory during rot180: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | rmask.max.y = gran; | 
|  | drawop(mask, rmask, display->opaque, nil, ZP, S); | 
|  | shuffle(im, tmp, Yaxis, Dy(im->r), mask, gran, 0); | 
|  | freeimage(mask); | 
|  | freeimage(mtmp); | 
|  | freeimage(tmp); | 
|  | if(tmp0 != im) | 
|  | freeimage(tmp0); | 
|  | } | 
|  |  | 
|  | /* rotates an image 90 degrees clockwise */ | 
|  | Image * | 
|  | rot90(Image *im) | 
|  | { | 
|  | Image *tmp; | 
|  | int i, j, dx, dy; | 
|  |  | 
|  | dx = Dx(im->r); | 
|  | dy = Dy(im->r); | 
|  | tmp = xallocimage(display, Rect(0, 0, dy, dx), im->chan, 0, DCyan); | 
|  | if(tmp == nil) { | 
|  | fprint(2, "out of memory during rot90: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  |  | 
|  | for(j = 0; j < dx; j++) { | 
|  | for(i = 0; i < dy; i++) { | 
|  | drawop(tmp, Rect(i, j, i+1, j+1), im, nil, Pt(j, dy-(i+1)), S); | 
|  | } | 
|  | } | 
|  | freeimage(im); | 
|  |  | 
|  | return(tmp); | 
|  | } | 
|  |  | 
|  | /* rotates an image 270 degrees clockwise */ | 
|  | Image * | 
|  | rot270(Image *im) | 
|  | { | 
|  | Image *tmp; | 
|  | int i, j, dx, dy; | 
|  |  | 
|  | dx = Dx(im->r); | 
|  | dy = Dy(im->r); | 
|  | tmp = xallocimage(display, Rect(0, 0, dy, dx), im->chan, 0, DCyan); | 
|  | if(tmp == nil) { | 
|  | fprint(2, "out of memory during rot270: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  |  | 
|  | for(i = 0; i < dy; i++) { | 
|  | for(j = 0; j < dx; j++) { | 
|  | drawop(tmp, Rect(i, j, i+1, j+1), im, nil, Pt(dx-(j+1), i), S); | 
|  | } | 
|  | } | 
|  | freeimage(im); | 
|  |  | 
|  | return(tmp); | 
|  | } | 
|  |  | 
|  | /* from resample.c -- resize from → to using interpolation */ | 
|  |  | 
|  |  | 
|  | #define K2 7	/* from -.7 to +.7 inclusive, meaning .2 into each adjacent pixel */ | 
|  | #define NK (2*K2+1) | 
|  | double K[NK]; | 
|  |  | 
|  | double | 
|  | fac(int L) | 
|  | { | 
|  | int i, f; | 
|  |  | 
|  | f = 1; | 
|  | for(i=L; i>1; --i) | 
|  | f *= i; | 
|  | return f; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * i0(x) is the modified Bessel function, Σ (x/2)^2L / (L!)² | 
|  | * There are faster ways to calculate this, but we precompute | 
|  | * into a table so let's keep it simple. | 
|  | */ | 
|  | double | 
|  | i0(double x) | 
|  | { | 
|  | double v; | 
|  | int L; | 
|  |  | 
|  | v = 1.0; | 
|  | for(L=1; L<10; L++) | 
|  | v += pow(x/2., 2*L)/pow(fac(L), 2); | 
|  | return v; | 
|  | } | 
|  |  | 
|  | double | 
|  | kaiser(double x, double t, double a) | 
|  | { | 
|  | if(fabs(x) > t) | 
|  | return 0.; | 
|  | return i0(a*sqrt(1-(x*x/(t*t))))/i0(a); | 
|  | } | 
|  |  | 
|  |  | 
|  | void | 
|  | resamplex(uchar *in, int off, int d, int inx, uchar *out, int outx) | 
|  | { | 
|  | int i, x, k; | 
|  | double X, xx, v, rat; | 
|  |  | 
|  |  | 
|  | rat = (double)inx/(double)outx; | 
|  | for(x=0; x<outx; x++){ | 
|  | if(inx == outx){ | 
|  | /* don't resample if size unchanged */ | 
|  | out[off+x*d] = in[off+x*d]; | 
|  | continue; | 
|  | } | 
|  | v = 0.0; | 
|  | X = x*rat; | 
|  | for(k=-K2; k<=K2; k++){ | 
|  | xx = X + rat*k/10.; | 
|  | i = xx; | 
|  | if(i < 0) | 
|  | i = 0; | 
|  | if(i >= inx) | 
|  | i = inx-1; | 
|  | v += in[off+i*d] * K[K2+k]; | 
|  | } | 
|  | out[off+x*d] = v; | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | resampley(uchar **in, int off, int iny, uchar **out, int outy) | 
|  | { | 
|  | int y, i, k; | 
|  | double Y, yy, v, rat; | 
|  |  | 
|  | rat = (double)iny/(double)outy; | 
|  | for(y=0; y<outy; y++){ | 
|  | if(iny == outy){ | 
|  | /* don't resample if size unchanged */ | 
|  | out[y][off] = in[y][off]; | 
|  | continue; | 
|  | } | 
|  | v = 0.0; | 
|  | Y = y*rat; | 
|  | for(k=-K2; k<=K2; k++){ | 
|  | yy = Y + rat*k/10.; | 
|  | i = yy; | 
|  | if(i < 0) | 
|  | i = 0; | 
|  | if(i >= iny) | 
|  | i = iny-1; | 
|  | v += in[i][off] * K[K2+k]; | 
|  | } | 
|  | out[y][off] = v; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | Image* | 
|  | resample(Image *from, Image *to) | 
|  | { | 
|  | int i, j, bpl, nchan; | 
|  | uchar **oscan, **nscan; | 
|  | char tmp[20]; | 
|  | int xsize, ysize; | 
|  | double v; | 
|  | Image *t1, *t2; | 
|  | ulong tchan; | 
|  |  | 
|  | for(i=-K2; i<=K2; i++){ | 
|  | K[K2+i] = kaiser(i/10., K2/10., 4.); | 
|  | } | 
|  |  | 
|  | /* normalize */ | 
|  | v = 0.0; | 
|  | for(i=0; i<NK; i++) | 
|  | v += K[i]; | 
|  | for(i=0; i<NK; i++) | 
|  | K[i] /= v; | 
|  |  | 
|  | switch(from->chan){ | 
|  | case GREY8: | 
|  | case RGB24: | 
|  | case RGBA32: | 
|  | case ARGB32: | 
|  | case XRGB32: | 
|  | break; | 
|  |  | 
|  | case CMAP8: | 
|  | case RGB15: | 
|  | case RGB16: | 
|  | tchan = RGB24; | 
|  | goto Convert; | 
|  |  | 
|  | case GREY1: | 
|  | case GREY2: | 
|  | case GREY4: | 
|  | tchan = GREY8; | 
|  | Convert: | 
|  | /* use library to convert to byte-per-chan form, then convert back */ | 
|  | t1 = xallocimage(display, Rect(0, 0, Dx(from->r), Dy(from->r)), tchan, 0, DNofill); | 
|  | if(t1 == nil) { | 
|  | fprint(2, "out of memory for temp image 1 in resample: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | drawop(t1, t1->r, from, nil, ZP, S); | 
|  | t2 = xallocimage(display, to->r, tchan, 0, DNofill); | 
|  | if(t2 == nil) { | 
|  | fprint(2, "out of memory temp image 2 in resample: %r\n"); | 
|  | wexits("memory"); | 
|  | } | 
|  | resample(t1, t2); | 
|  | drawop(to, to->r, t2, nil, ZP, S); | 
|  | freeimage(t1); | 
|  | freeimage(t2); | 
|  | return to; | 
|  |  | 
|  | default: | 
|  | sysfatal("can't handle channel type %s", chantostr(tmp, from->chan)); | 
|  | } | 
|  |  | 
|  | xsize = Dx(to->r); | 
|  | ysize = Dy(to->r); | 
|  | oscan = malloc(Dy(from->r)*sizeof(uchar*)); | 
|  | nscan = malloc(max(ysize, Dy(from->r))*sizeof(uchar*)); | 
|  | if(oscan == nil || nscan == nil) | 
|  | sysfatal("can't allocate: %r"); | 
|  |  | 
|  | /* unload original image into scan lines */ | 
|  | bpl = bytesperline(from->r, from->depth); | 
|  | for(i=0; i<Dy(from->r); i++){ | 
|  | oscan[i] = malloc(bpl); | 
|  | if(oscan[i] == nil) | 
|  | sysfatal("can't allocate: %r"); | 
|  | j = unloadimage(from, Rect(from->r.min.x, from->r.min.y+i, from->r.max.x, from->r.min.y+i+1), oscan[i], bpl); | 
|  | if(j != bpl) | 
|  | sysfatal("unloadimage"); | 
|  | } | 
|  |  | 
|  | /* allocate scan lines for destination. we do y first, so need at least Dy(from->r) lines */ | 
|  | bpl = bytesperline(Rect(0, 0, xsize, Dy(from->r)), from->depth); | 
|  | for(i=0; i<max(ysize, Dy(from->r)); i++){ | 
|  | nscan[i] = malloc(bpl); | 
|  | if(nscan[i] == nil) | 
|  | sysfatal("can't allocate: %r"); | 
|  | } | 
|  |  | 
|  | /* resample in X */ | 
|  | nchan = from->depth/8; | 
|  | for(i=0; i<Dy(from->r); i++){ | 
|  | for(j=0; j<nchan; j++){ | 
|  | if(j==0 && from->chan==XRGB32) | 
|  | continue; | 
|  | resamplex(oscan[i], j, nchan, Dx(from->r), nscan[i], xsize); | 
|  | } | 
|  | free(oscan[i]); | 
|  | oscan[i] = nscan[i]; | 
|  | nscan[i] = malloc(bpl); | 
|  | if(nscan[i] == nil) | 
|  | sysfatal("can't allocate: %r"); | 
|  | } | 
|  |  | 
|  | /* resample in Y */ | 
|  | for(i=0; i<xsize; i++) | 
|  | for(j=0; j<nchan; j++) | 
|  | resampley(oscan, nchan*i+j, Dy(from->r), nscan, ysize); | 
|  |  | 
|  | /* pack data into destination */ | 
|  | bpl = bytesperline(to->r, from->depth); | 
|  | for(i=0; i<ysize; i++){ | 
|  | j = loadimage(to, Rect(0, i, xsize, i+1), nscan[i], bpl); | 
|  | if(j != bpl) | 
|  | sysfatal("loadimage: %r"); | 
|  | } | 
|  |  | 
|  | for(i=0; i<Dy(from->r); i++){ | 
|  | free(oscan[i]); | 
|  | free(nscan[i]); | 
|  | } | 
|  | free(oscan); | 
|  | free(nscan); | 
|  |  | 
|  | return to; | 
|  | } |