We were fiddling with another project and realized that, because we were using sspr() to resize images, we didn't actually know where all the pixels were - and we wanted to know, because we wanted to cast a shadow that was the colors of what was below but in shadow. And we realized that PICO-8 lets you look at the screen in the code, and then the concept of green screen popped into our head, and then we got to work.
It's not minimized and it's not optimized - if you tell it to chromakey the whole screen, it'll chew through the entire CPU budget with change (ask us how we know!) - but I think it's readable enough that people can hack on it. We haven't tested it extensively, but we made sure it respected the current clipping rectangle and restored it before it exited, because that seemed like the correct thing to do.
function chromakey(drawfunc,x,y,w,h,c) -- replaces pixels of c in rectangle x,y,w,h with output of drawfunc -- c defaults to 3 if type(c) == "nil" then c = 3 end -- load prior clip state local clip_x0=peek(0x5f20) local clip_y0=peek(0x5f21) local clip_x1=peek(0x5f22) local clip_y1=peek(0x5f23) -- squeeze chromakey rectangle into clipping region x = max(x,clip_x0) y = max(y,clip_y0) w = min(w,clip_x1-x) h = min(h,clip_y1-y) clip() for xs = x,x+w-1 do local ys = y local y0 = y local chroma = false while ys < y+h do if not chroma and pget(xs,ys) == c then -- start detecting chroma = true y0 = ys end if chroma and pget(xs,ys) ~= c then -- stop detecting and draw rectangle clip(xs,y0,1,ys-y0) drawfunc() chroma = false clip() end ys += 1 end if chroma then -- c extended to bottom of column clip(xs,y0,1,y+h-y0) drawfunc() clip() end end -- reset clipping region clip(clip_x0,clip_y0,clip_x1-clip_x0,clip_y1-clip_y0) end |
You're calling drawfunc() for every horizontally adjacent set of chroma pixels, and letting it do its entire draw?
I would propose requiring drawfunc() to take x,y,w,h parameters so you can tell it where it's drawing and it can just draw those pixels.
Here's a version with x2/y2 instead of w/h, plus my proposal above, and some token optimization.
function chromakey(drawfunc,x1,y1,x2,y2,c) -- replaces pixels of c in rectangle x1,y1,x2,y2 with output of drawfunc -- c defaults to 3 c = c or 3 -- load prior clip state local clip_x1, clip_y1, clip_x2, clip_y2 = peek(0x5f20), peek(0x5f21), peek(0x5f22), peek(0x5f23) -- squeeze chromakey rectangle into clipping region x1, y1, x2, y2 = max(x1, clip_x1), max(y1, clip_y1), min(x2, clip_x2 + x1), min(y2, clip_y2 + y1) for ys = y1, y2 do local x0, chroma = x1 for xs = x1, x2 do if pget(xs, ys) == c then if not chroma then -- start detecting chroma = 1 x0 = xs end elseif chroma then -- stop detecting and draw rectangle clip(x0, ys, xs - x0, 1) drawfunc(x0, ys, xs - 1, ys) chroma = nil clip() end end if chroma then -- c extended to end of row clip(x0, ys, x2 - x1 - x0, 1) drawfunc(x0, ys, x2, ys) clip() end end -- reset clipping region clip(clip_x1, clip_y1, clip_x2 - clip_x1, clip_y2 - clip_y1) end |
Ooh, that's really clean - thanks! Quick test suggests it's ~40% faster, which is terrific.
And yeah, that's a good change - I'd tried passing the clipping window info to the drawfunc at first, but it made no perceptible difference for a single sspr() or spr() command so I cut it out - could definitely be a big difference for some draw functions, though. It should definitely be in the library version of the function.
Edit: do you want the cart in the OP updated with your function?
No preference. Do you mind if I put my version in https://github.com/sparr/pico8lib ?
demo effect is nice but per-pixel operations for masking is really a cpu killer. suggest to look at peek/poke and bitmasking instead...
peek/poke and bitmasking seem like they would be good for predetermined screening patterns, while the approach here is for arbitrary shapes.
@sparr Yes, please - we actually originally posted it with the thought that a chroma key function would be a good addition to the library.
@freds72 Yeah, this is a CPU-expensive general case for CPU-unconstrained carts and times when general-case is the best way to do it. A lot of cases, including the one that inspired me to write it, have leaner solutions.
@sparr: just noticed that the line to squeeze chromakey rectangle into clipping region has min(y2, clip_y2 + x1)
should be min(y2, clip_y2 + y1)
[Please log in to post a comment]