Re: [9fans] Porter-Duff alpha blending
i submitted a patch to plan 9 with russ' fix: memdraw32bit. thanks russ, andrey! note: you will need to recompile your kernel, not just applications that use libmemdraw. i leave it as an exercize to the reader to port this to 9vx. ☺ - erik
Re: [9fans] Porter-Duff alpha blending
On Sun, Mar 8, 2009 at 6:33 PM, Jeff Sickel j...@corpus-callosum.com wrote: All this chatter about blending and Russ' recent updates to p9p encouraged me to finally fix a small thing that's annoyed me with devdraw's on OS X: that odd blank (all white) window when waking a machine from sleep, exiting a screen saver, or un-hiding a devdraw based application. Here's the patch all ready for an hg import (should also be applied to drawterm, and Inferno's win.c): I am confused as to why one would return eventNotHandledErr after handling the event? ian
Re: [9fans] Porter-Duff alpha blending
2009/3/9 erik quanstrom quans...@quanstro.net: i leave it as an exercize to the reader to port this to 9vx. ☺ There you are. Easy exercises are also good from time to time :) The patch also allows to use Shift+Button3 as Button2 (as documented in the man page). Regards, -- - yiyus || JGL . diff -r a18e9872164b src/9vx/libmemdraw/draw.c --- a/src/9vx/libmemdraw/draw.c Wed Dec 10 03:29:15 2008 -0800 +++ b/src/9vx/libmemdraw/draw.c Mon Mar 09 16:29:39 2009 +0100 @@ -10,22 +10,32 @@ #define RGB2K(r,g,b) ((156763*(r)+307758*(g)+59769*(b))19) /* - * for 0 ⤠x ⤠255*255, (x*0x0101+0x100)16 is a perfect approximation. - * for 0 ⤠x (116), x/255 = ((x+1)*0x0101)16 is a perfect approximation. - * the last one is perfect for all up to 116, avoids a multiply, but requires a rathole. + * For 16-bit values, x / 255 == (t = x+1, (t+(t8)) 8). + * We add another 127 to round to the nearest value rather + * than truncate. + * + * CALCxy does x bytewise calculations on y input images (x=1,4; y=1,2). + * CALC2x does two parallel 16-bit calculations on y input images (y=1,2). */ -/* #define DIV255(x) (((x)*257+256)16) */ -#define DIV255(x) x)+1)*257)16) -/* #define DIV255(x) (tmp=(x)+1, (tmp+(tmp8))8) */ +#define CALC11(a, v, tmp) \ + (tmp=(a)*(v)+128, (tmp+(tmp8))8) +#define CALC12(a1, v1, a2, v2, tmp) \ + (tmp=(a1)*(v1)+(a2)*(v2)+128, (tmp+(tmp8))8) +#define MASK 0xFF00FF -#define MUL(x, y, t) (t = (x)*(y)+128, (t+(t8))8) -#define MASK13 0xFF00FF00 -#define MASK02 0x00FF00FF -#define MUL13(a, x, t) (t = (a)*(((x)MASK13)8)+128, ((t+((t8)MASK02))8)MASK02) -#define MUL02(a, x, t) (t = (a)*(((x)MASK02)0)+128, ((t+((t8)MASK02))8)MASK02) -#define MUL0123(a, x, s, t) ((MUL13(a, x, s)8)|MUL02(a, x, t)) +#define CALC21(a, vvuu, tmp) \ + (tmp=(a)*(vvuu)+0x00800080, ((tmp+((tmp8)MASK))8)MASK) -#define MUL2(u, v, x, y) (t = (u)*(v)+(x)*(y)+256, (t+(t8))8) +#define CALC41(a, rgba, tmp1, tmp2) \ + (CALC21(a, rgba MASK, tmp1) | \ + (CALC21(a, (rgba8)MASK, tmp2)8)) + +#define CALC22(a1, vvuu1, a2, vvuu2, tmp) \ + (tmp=(a1)*(vvuu1)+(a2)*(vvuu2)+0x00800080, ((tmp+((tmp8)MASK))8)MASK) + +#define CALC42(a1, rgba1, a2, rgba2, tmp1, tmp2) \ + (CALC22(a1, rgba1 MASK, a2, rgba2 MASK, tmp1) | \ + (CALC22(a1, (rgba18) MASK, a2, (rgba28) MASK, tmp2)8)) static void mktables(void); typedef int Subdraw(Memdrawparam*); @@ -803,41 +813,85 @@ return bdst; } +/* + * Do the channels in the buffers match enough + * that we can do word-at-a-time operations + * on the pixels? + */ +static int +chanmatch(Buffer *bdst, Buffer *bsrc) +{ + uchar *drgb, *srgb; + + /* + * first, r, g, b must be in the same place + * in the rgba word. + */ + drgb = (uchar*)bdst-rgba; + srgb = (uchar*)bsrc-rgba; + if(bdst-red - drgb != bsrc-red - srgb + || bdst-blu - drgb != bsrc-blu - srgb + || bdst-grn - drgb != bsrc-grn - srgb) + return 0; + + /* + * that implies alpha is in the same place, + * if it is there at all (it might be == ones). + * if the destination is ones, we can scribble + * over the rgba slot just fine. + */ + if(bdst-alpha == ones) + return 1; + + /* + * if the destination is not ones but the src is, + * then the simultaneous calculation will use + * bogus bytes from the src's rgba. no good. + */ + if(bsrc-alpha == ones) + return 0; + + /* + * otherwise, alphas are in the same place. + */ + return 1; +} + static Buffer alphacalc14(Buffer bdst, Buffer bsrc, Buffer bmask, int dx, int grey, int op) { Buffer obdst; int fd, sadelta; int i, sa, ma, q; - uint32 s, t; + uint32 t, t1; obdst = bdst; sadelta = bsrc.alpha == ones ? 0 : bsrc.delta; - q = bsrc.delta == 4 bdst.delta == 4; + q = bsrc.delta == 4 bdst.delta == 4 chanmatch(bdst, bsrc); for(i=0; idx; i++){ sa = *bsrc.alpha; ma = *bmask.alpha; - fd = MUL(sa, ma, t); + fd = CALC11(sa, ma, t); if(op == DoutS) fd = 255-fd; if(grey){ - *bdst.grey = MUL(fd, *bdst.grey, t); + *bdst.grey = CALC11(fd, *bdst.grey, t); bsrc.grey += bsrc.delta; bdst.grey += bdst.delta; }else{ if(q){ -*bdst.rgba = MUL0123(fd, *bdst.rgba, s, t); +*bdst.rgba = CALC41(fd, *bdst.rgba, t, t1); bsrc.rgba++; bdst.rgba++; bsrc.alpha += sadelta; bmask.alpha += bmask.delta; continue; } - *bdst.red = MUL(fd, *bdst.red, t); - *bdst.grn = MUL(fd, *bdst.grn, t); - *bdst.blu = MUL(fd, *bdst.blu, t); + *bdst.red = CALC11(fd, *bdst.red, t); + *bdst.grn = CALC11(fd, *bdst.grn, t); + *bdst.blu = CALC11(fd, *bdst.blu, t); bsrc.red += bsrc.delta; bsrc.blu += bsrc.delta; bsrc.grn += bsrc.delta; @@ -846,7 +900,7 @@ bdst.grn += bdst.delta; } if(bdst.alpha != ones){ - *bdst.alpha = MUL(fd, *bdst.alpha, t); + *bdst.alpha = CALC11(fd, *bdst.alpha, t); bdst.alpha += bdst.delta; } bmask.alpha += bmask.delta; @@ -861,11 +915,11 @@ Buffer obdst; int fs, sadelta; int i, ma, da, q; - uint32 s, t; + uint32 t, t1; obdst = bdst; sadelta = bsrc.alpha == ones ? 0
Re: [9fans] Porter-Duff alpha blending
On Mar 9, 2009, at 10:28 AM, Michaelian Ennis wrote: I am confused as to why one would return eventNotHandledErr after handling the event? Because it's not really handling the event. It just happens to be the only hook available for monitoring the application event loop (in this case: the dance between the Carbon Event Manager and p9p) that I've found to force memscreen's image to flush. We still want OS X's application event loop to do whatever it does to get the window server and responder chain back--without messing up the buffering that could cause a flicker when moving the window later. -jas
Re: [9fans] Porter-Duff alpha blending
Thanks for tracking this down and pointing out where the error is. There are actually a handful of errors in that code. Assume a source value sv, source alpha sa, mask alpha ma, destination value dv, and destination alpha da. The values sv and dv are stored premultiplied by their corresponding alphas (the one true way). Given those values, the correct new values for the destination pixel in an S over D op are: dv = (sv*ma + dv*(255-sa*ma)) / 255 da = (sa*ma + da*(255-sa*ma)) / 255 Bug #1: The current draw.c does the division separately on the two halves: dv = (sv*ma)/255 + (dv*(255-sa*ma))/255 This can be off by one if the remainders from the two divisions sum to = 255. Bug #2: The MUL0123 macro assumes four values are packed into a 32-bit int and runs them in simultaneous pairs as 32-bit operations (MUL02 and MUL13) operating on 16-bit halves of the word. Those two don't use the right rounding for the bitwise op implementation of /255. On a single value, the implementation is x / 255 == (t = x+1, (t+(t8))8) (x+127) / 255 == (t = x+128, (t+(t8))8) These calculations only need 16 bits so you can run two of them in the two halves of a 32-bit word. The second implements round-to-nearest and is what the draw code tries to do in this case. But it only adds 128 (0x80), so it only rounds the bottom half correctly. It needs to add 0x00800080, which would round both of them. This explains: src rFF gFF bFF αFF mask kFF αFF dst r00 g00 b00 αFF dst after calc rFE gFE bFF αFF Bug #3: MUL0123 is enabled whenever the src and dst both have 32-bit pixel width, but there is no check that the sub-channels are in the same order. You don't say what the image chans were in your test, but this: src rFF g00 b00 αFF mask kFF αFF dst r00 g00 b00 αFF dst after calc rFE gFE b00 α00 would be explained by, say, src==ARGB and dst==RGBA. The A and R values in src became the R and G chans in dst. (In fact, since the dst R and G are FE not FF, that's almost certainly the scenario, modulo the little-endian draw names.) This one is similarly explained: src rCC g00 b00 αCC mask kFF αFF dst r00 g00 b00 αFF dst after calc rCB gCB b00 α33 The destination got scaled by 0x33/0xFF, leaving 00 00 00 33, and then the source, cc 00 00 cc, was added in the wrong place, using the incorrect rounding, to produce cb cb 00 33. I have corrected these bugs in the plan9port copy of src/libmemdraw/draw.c and finally get the right answer for Andrey's test (attached). I leave it as an exercise to the interested reader to port the changes to the other dozen copies of libmemdraw that are floating around, or to unify them all. Russ attachment: andrey.png
Re: [9fans] Porter-Duff alpha blending
Bug #3: MUL0123 is enabled whenever the src and dst both have 32-bit pixel width, but there is no check that the sub-channels are in the same order. You don't say what the image chans were in your test, but this: src rFF g00 b00 αFF mask kFF αFF dst r00 g00 b00 αFF dst after calc rFE gFE b00 α00 would be explained by, say, src==ARGB and dst==RGBA. The A and R values in src became the R and G chans in dst. (In fact, since the dst R and G are FE not FF, that's almost certainly the scenario, modulo the little-endian draw names.) it was indeed ARGB/RGBA (or the other way around) as I had focused testing only on those two. thanks for the explanation!
Re: [9fans] Porter-Duff alpha blending
ok, i think i am close, but i can go no further. here's what i did: - view all images through ppm instead of png. what that means is that i can do toppm $i $i.ppm in 9vx and then see them on the hosting Linux. since ppm is rather simple (doesn't do any draw ops but just dumps the bytes) it is good for viewing both inside Plan9 and outside (eog, in this case, which apparently stands for eye of gnome) - use libmemdraw with the default drawop (SoverD) with no libdraw conversions, i.e. don't display anything on screen. as soon as it was established that libmemdraw-ed images exhibit the same issue it was obvious that libmemdraw is the culprit - confirm that rgba2img and img2rgba are not the culprits (that's accomplished by testing that memimagefill(), which uses only rgba2img() works fine. then i dug deep into libmemdraw/draw.c, especially alphadraw(). it turned out after a lot of looking that the offending code is in alphacalc11(). with debugging turned on and a few extra print() statements it turned out that writebyte() was getting completely messed destination values. it turned out that a source 0x with mask 0xFF/0xFF will be turned by alphacalc11() into: 0xFEFE, or, in memdraw debugging terms (the last debugging statement is right underneath calc() in alphadraw()): src rFF gFF bFF αFF mask kFF αFF dst r00 g00 b00 αFF dst after calc rFE gFE bFF αFF what's worse, the error is much more pronounced when drawing single colors (here red is turned into yellow): src rFF g00 b00 αFF mask kFF αFF dst r00 g00 b00 αFF dst after calc rFE gFE b00 α00 things become weird down the road: src rCC g00 b00 αCC mask kFF αFF dst r00 g00 b00 αFF dst after calc rCB gCB b00 α33 the change below _seems_ to avoid the problem and results in correct images being generated, although those familiar with the code should be able to better figure out what breaks when q=1. % diff draw.c /n/sources/plan9/sys/src/libmemdraw/draw.c 996c996 q = 0;//bsrc.delta == 4 bdst.delta == 4; --- q = bsrc.delta == 4 bdst.delta == 4; %
Re: [9fans] Porter-Duff alpha blending
that's quite interesting, and i suspect you've discovered a bug. actually two bugs, maybe. one has to deal with drawing alpha-blended images, the other with drawing using RGBA32... the simplest way to trigger the second bug, which may or may not be related to the first, is to draw using black, RGBA32 channel and full alpha: the following draws fine on the screen: b = allocimage(display, Rect(0, 0, Dx(screen-r), Dy(screen-r)), ARGB32, 0, DBlack); draw(screen, screen-r, b, nil, ZP); but the following draws incorrectly as blue: allocimage(display, Rect(0, 0, Dx(screen-r), Dy(screen-r)), RGBA32, 0, DBlack); draw(screen, screen-r, b, nil, ZP); at this point i would suspect an endianness issue. setalpha doesn't care about endianness, but libmemdraw does. to add a bit more information i've taken the liberty of modifying your program. the code attached exhibits all sorts of issues with alpha blending and illustrates them more fully (i've also used a few of the tricks in /sys/src/libdraw/arith.c to show the correct usage of Pt(), ZP, color names and the like) in this example everything is drawn over a black screen image to avoid any effects of conversion to the real bpp of the screen. the base image's channel type is fixed at ABGR32 because this particular channel does not exhibit the behaviour of switching from black to blue described at the beginning of this email. i've included a screencap with the black image using RGBA32 so you can see the difference. the program shows 4 different columns of 256 lines with varying levels of alpha-blendedness (from 0xFF on top to 0x00 on bottom). the first column draws white, second red, third green and fourth blue. below the column a single line is drawn twice: once on the black screen and once on the display screen directly. in the two attached snapshots the line (created with ARGB23) is drawn correctly both on the black image and on the display when the black image is ARGB32, but if we switch to RGBA32 for the black image the first line gets mangled when the whole image is displayed. i'll leave the bit/byte swap analysis to the readers :) ps: are you using this on native terminal? it would be very interesting to test the program there for different values of ARGB32, RGBA32 and ABGR32 (there's no BGRA32) and possibly under display depth of 32bpp. i only have 9vx and drawterm to test with, unfortunately, although i will try with osx's drawterm when i get home: that one runs in 32bpp at all times. cheers! #include u.h #include libc.h #include draw.h #include cursor.h Image *col; Image *black; void main(void) { int i; ulong type = ARGB32; newwindow(-r 0 0 800 400); if(initdraw(nil, nil, tri) 0) exits(initdraw); black = allocimage(display, Rect(0, 0, Dx(screen-r), Dy(screen-r)), ARGB32, 0, DBlack); for(i = 0; i 0xff; i++) { col = allocimage(display, Rect(0,0,1,1), type, 1, setalpha(DWhite, 0xff-i)); draw(black, Rect(0,i,200,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), type, 1, setalpha(DRed, 0xff-i)); draw(black, Rect(200,i,400,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), type, 1, setalpha(DGreen, 0xff-i)); draw(black, Rect(400,i,600,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), type, 1, setalpha(DBlue, 0xff-i)); draw(black, Rect(600,i,800,i+1), col, nil, ZP); freeimage(col); } col = allocimage(display, Rect(0, 0, 1, 1), type, 1, DBlack); draw(black, Rect(0, 260, 800, 300), col, nil, ZP); draw(screen, screen-r, black, nil, ZP); draw(screen, Rect(0, 360, 800, 400), col, nil, ZP); flushimage(display, 1); sleep(1); closedisplay(display); } attachment: RGBA32.pngattachment: ARGB32.png
Re: [9fans] Porter-Duff alpha blending
it looks fine on a native plan 9 386 terminal. - erikattachment: α.png
Re: [9fans] Porter-Duff alpha blending
but it doesn't: the red is drawn as blue, the green switches to pink in the middle, and blue is drawn as red and cyan... only the white is alpha-blended correctly. On Wed, Mar 4, 2009 at 2:32 PM, erik quanstrom quans...@coraid.com wrote: it looks fine on a native plan 9 386 terminal. - erik
Re: [9fans] Porter-Duff alpha blending
to see how it should look when drawn correctly use type = ARGB32 and RGB24 for black's allocimage(). see attached. the alpha blending still works because when downgrading from ARGB32-RGB24 (for drawing onto black) the library still takes the source color from black without issues. the bugs appear when 32-bit channels are being used for source and destination. attachment: RGB24.png
Re: [9fans] Porter-Duff alpha blending
i didn't do a hex dump. it must be displayed differently outside of plan 9. - erik
Re: [9fans] Porter-Duff alpha blending
are you saying that you see the correct image but wee see the png differently? can you convert it to gif instead of png? On Wed, Mar 4, 2009 at 2:44 PM, erik quanstrom quans...@coraid.com wrote: i didn't do a hex dump. it must be displayed differently outside of plan 9. - erik
Re: [9fans] Porter-Duff alpha blending
this is a picture of what this looks like on my screen. your attached png is 4 stripes of white-blue blue-blue magenta-blue cyan-blue - erikattachment: αpic.jpg
Re: [9fans] Porter-Duff alpha blending
the jpg file you attached doesn't look like a jpg file :( On Wed, Mar 4, 2009 at 2:53 PM, erik quanstrom quans...@coraid.com wrote: this is a picture of what this looks like on my screen. your attached png is 4 stripes of white-blue blue-blue magenta-blue cyan-blue - erik
Re: [9fans] Porter-Duff alpha blending
ok, those all exhibit the incorrect blending behaviour, would you not agree? here's a .jpg that shows the correct behaviour and that should render properly on Plan9. I just tried RGB24.png and it indeed renders incorrectly in 9vx the way you're describing it. the jpg should be the benchmark for correct display. attachment: RGB24.jpg
Re: [9fans] Porter-Duff alpha blending
On Wed Mar 4 17:48:12 EST 2009, mirtchov...@gmail.com wrote: Erik, I think you're running in 32bpp mode, right? yes. iirc my nvidia card was not willing to do 24bpp. To have 'png' render RGB24.png correctly make the following change to /sys/src/cmd/jpg/png.c: however it won't fix the program that you originally sent. - erik
Re: [9fans] Porter-Duff alpha blending
however it won't fix the program that you originally sent. the fix (if you can call sidestepping the problem :a fix) for the original program is to use RGB24 as the channel for black's allocimage(): black = allocimage(display, Rect(0, 0, Dx(screen-r), Dy(screen-r)), RGB24, 0, DBlack);
Re: [9fans] Porter-Duff alpha blending
On Thu, Mar 5, 2009 at 10:31 AM, maht mattmob...@proweb.co.uk wrote: I decided to try some draw(2) exploration and I'm not getting what I expected from alpha blending. If I apply alpha to RGB24 images they change as expected but if I use RGBA32 images and try to apply alpha it's going weird on me (or I am weird on it). If I apply setalpha(DRed, 0x7F) and then draw() it to screen instead of being less red it goes blue! I've posted some sample code and the outputs at http://maht0x0r.blogspot.com/2009/03/o-that-way-madness-lies.html I can't tell from your post which images you think are wrong. You have three images after the words But when you apply alpha to RGBA32 images something unexpected happens:. The first one looks fine. The second one does not specify what your initial background is. It matters, because you are drawing a 1/2-translucent image and then a second one, so the result is 1/2-translucent in some places and 1/4-translucent in others. Whatever is underneath will shine through. Note the difference in the bottom right image in the white half vs the black half in the attached alpha.png. (View the PNG using something other than Plan 9, if necessary.) Andrey's bug feels like a variation on that theme, but I cannot pin it down just now. Russ attachment: alpha.png
Re: [9fans] Porter-Duff alpha blending
from 32-bit image to a 32-bit display. a 32-bit display? what's the result of cat /dev/draw/new?
Re: [9fans] Porter-Duff alpha blending
On Wed Mar 4 18:42:46 EST 2009, fors...@terzarima.net wrote: from 32-bit image to a 32-bit display. a 32-bit display? what's the result of cat /dev/draw/new? 1: !cat /dev/draw/new 9 0x8r8g8b8 0 0 0 16001200 0 016001200 cat: erro - erik
Re: [9fans] Porter-Duff alpha blending
in the case of 9vx running on Linux it's x8r8g8b8, drawterm on osx is also x8r8g8b8. here's how it breaks down drawing to display: sourcedestination error XRGB32 XRGB32 no XBGR32 XRGB32 no ARGB32 XRGB32 yes (see attached) RGBA32 XRGB32 yes i've done a bit more testing and will hopefully have a full table for 32-bit images later tonight: attachment: xrgb-XRGB32.png
Re: [9fans] Porter-Duff alpha blending
just for fun, the attached file should run the whole gamut of allocimage chan options in the 32-bit range (with RGB24 thrown in for good measure) and will create files in /tmp for each possible combination without involving drawing to the screen display (ideally this should be done in memdraw, i suppose). i think page will display them honestly, but i'm not sure. i'll leave to you to convert the resulting images for outside viewing :) #include u.h #include libc.h #include draw.h #include cursor.h Image *col; Image *back; ulong types[] = { RGBA32, ARGB32, XRGB32, XBGR32, RGB24, }; char *descs[] = { rgba32, argb32, xrgb32, xbgr32, rgb24, }; void main(void) { int fd, i, j, k; char *str; ulong type = RGBA32; if(initdraw(nil, nil, tri) 0) exits(initdraw); for(j = 0; j 5; j++) { for(k = 0; k 5; k++) { str= smprint(/tmp/%s-over-%s.1, descs[k], descs[j]); print(creating: %s\n, str); back = allocimage(display, Rect(0, 0, 800, 300), types[j], 0, DBlack); for(i = 0; i 0xff; i++) { col = allocimage(display, Rect(0,0,1,1), types[k], 1, setalpha(DWhite, 0xff-i)); draw(back, Rect(0,i,200,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), types[k], 1, setalpha(DRed, 0xff-i)); draw(back, Rect(200,i,400,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), types[k], 1, setalpha(DGreen, 0xff-i)); draw(back, Rect(400,i,600,i+1), col, nil, ZP); freeimage(col); col = allocimage(display, Rect(0,0,1,1), types[k], 1, setalpha(DBlue, 0xff-i)); draw(back, Rect(600,i,800,i+1), col, nil, ZP); freeimage(col); } fd = create(str, OWRITE, 0664); if(fd 0) sysfatal(create); writeimage(fd, back, 0); close(fd); } } }
Re: [9fans] Porter-Duff alpha blending
xgbr32 over xgbr32 or xrgb32 looks fine. but xgbr32 over agbr32, for example, results in a fade-to-blue and an extremely slow display time. seems like the channels are getting mixed up. oddly, xrgb32 over argb32 works (but slowly). - erik