Skip to content

Instantly share code, notes, and snippets.

@x4fx77x4f
Last active April 14, 2021 04:16
Show Gist options
  • Save x4fx77x4f/384f6f7a09f8a3bc50b8bbcb42a63a33 to your computer and use it in GitHub Desktop.
Save x4fx77x4f/384f6f7a09f8a3bc50b8bbcb42a63a33 to your computer and use it in GitHub Desktop.
Utility to easily create pixel perfect captures of StarfallEx chips
-- The below line is the *only* one you need to change.
--@include chopper.txt
-- The following are optional.
local OVERRIDE_WIDTH = false and 320
local OVERRIDE_HEIGHT = false and 240
local OVERRIDE_RT = false and 'out' -- Name of rendertarget to save, or none to save the screen
local OVERRIDE_DELAY = false and 2 -- How many seconds to wait until capturing
local THRESHOLD = quotaMax()*0.9
-- End configuration.
--@client
-- I only *need* to cache the ones I'm detouring, but of course, I couldn't stop there...
local _coroutine = coroutine
local _coroutine_resume = _coroutine.resume
local _coroutine_yield = _coroutine.yield
local _file = file
local _file_exists = _file.exists
local _hasPermission = hasPermission
local _hook = hook
local _hook_add = _hook.add
local _hook_remove = _hook.remove
local _hook_run = _hook.run
local _math = math
local _math_floor = _math.floor
local _math_max = _math.max
local _pairs = pairs
local _quotaAverage = quotaAverage
local _quotaTotalAverage = quotaTotalAverage
local _quotaTotalUsed = quotaTotalUsed
local _quotaUsed = quotaUsed
local _render = render
local _render_capturePixels = _render.capturePixels
local _render_clear = _render.clear
local _render_drawRect = _render.drawRect
local _render_drawText = _render.drawText
local _render_drawTexturedRectUV = _render.drawTexturedRectUV
local _render_getTextSize = _render.getTextSize
local _render_readPixel = _render.readPixel
local _render_selectRenderTarget = _render.selectRenderTarget
local _render_setBackgroundColor = _render.setBackgroundColor
local _render_setFilterMag = _render.setFilterMag
local _render_setFilterMin = _render.setFilterMin
local _render_setFont = _render.setFont
local _render_setRenderTargetTexture = _render.setRenderTargetTexture
local _render_setRGBA = _render.setRGBA
local _setupPermissionRequest = setupPermissionRequest
local _string = string
local _string_find = _string.find
local _string_sub = _string.sub
local _TEXFILTER_POINT = TEXFILTER.POINT
local _timer = timer
local _timer_create = _timer.create
local _timer_remove = _timer.remove
local _timer_simple = _timer.simple
local _timer_systime = _timer.systime
local message
local permissions = {
'file.exists',
'file.open',
'render.offscreen',
'render.screen'
}
local function init()
for _, permission in _pairs(permissions) do
if not _hasPermission(permission) then
_setupPermissionRequest(permissions, "a", true)
return
end
end
_hook_remove('permissionrequest', '')
_hook_remove('starfallused', '')
message = "Waiting..."
local function patch_hookname(name)
return _string_sub(name, 1, 1) == '_' and '_'..name or name == 'render' and '_render' or name
end
local hooks = {}
function _hook.add(hookname, name, func)
hookname = patch_hookname(hookname)
local hooks_hookname = hooks[hookname]
if not hooks_hookname then
hooks_hookname = {}
hooks[hookname] = hooks_hookname
end
hooks_hookname[name] = true
return _hook_add(hookname, name, func)
end
function _hook.remove(hookname, name)
hookname = patch_hookname(hookname)
local hooks_hookname = hooks[hookname]
if hooks_hookname then
hooks_hookname[name] = nil
end
return _hook_remove(patch_hookname(hookname), name)
end
function _hook.run(hookname, ...)
return _hook_run(patch_hookname(hookname), ...)
end
local function patch_rt_name(name)
return _string_sub(name, 1, 1) == '_' and '_'..name
end
function _render.selectRenderTarget(name)
return _render_selectRenderTarget(name == nil and '_screengrab' or name)
end
local bg = Color(0, 0, 0, 255)
function _render.setBackgroundColor(color)
bg = color
end
local timers = {}
local timers_enabled = true
function _timer.create(name, delay, reps, func)
timers[name] = true
return _timer_create(name, delay, reps, func)
end
function _timer.remove(name)
timers[name] = nil
return _timer_remove(name)
end
function _timer.simple(delay, func)
return _timer_simple(delay, function()
return timers_enabled and func()
end)
end
local i, path = 1
repeat
--path = 'screengrab/'..i..'.ppm.dat'
path = 'screengrab/'..i..'.pam.dat'
i = i+1
until not _file_exists(path)
local handle = assert(_file.open(path, 'wb'), "failed to open "..path)
local w, h = OVERRIDE_WIDTH or 512, OVERRIDE_HEIGHT or 512
--handle:write('P6 '..w..' '..h..' 255\n')
handle:write('P7\nWIDTH '..w..'\nHEIGHT '..h..'\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n')
local i, j = 0, w*h
local thread = _coroutine.create(function()
for y=0, h-1 do
for x=0, w-1 do
i = i+1
local pixel = _render_readPixel(x, y)
handle:writeByte(pixel[1])
handle:writeByte(pixel[2])
handle:writeByte(pixel[3])
handle:writeByte(pixel[4]) -- PAM only
_coroutine_yield()
end
end
return true
end)
local duedate = OVERRIDE_DELAY and _timer_systime()+OVERRIDE_DELAY or 0
_hook_add('render', '', function(screen)
_render_selectRenderTarget('_screengrab')
_render_clear(bg, false)
_hook_run('_render', screen)
if _timer_systime() < duedate then
return
end
_hook_remove('render', '')
_render_selectRenderTarget(OVERRIDE_RT or '_screengrab')
_render_capturePixels()
for hookname, hooks in _pairs(hooks) do
for name in _pairs(hooks) do
_hook_remove(hookname, name)
end
end
for name in _pairs(timers) do
_timer_remove(name)
end
timers_enabled = false
_hook_add('renderoffscreen', '', function()
while _math_max(_quotaUsed(), _quotaAverage(), _quotaTotalUsed(), _quotaTotalAverage()) < THRESHOLD do
_render_selectRenderTarget(OVERRIDE_RT or '_screengrab')
if _coroutine_resume(thread) then
handle:close()
_hook_remove('renderoffscreen', '')
message = "Complete"
return
end
end
message = "Capturing... (".._math_floor(i/j*100).."%)"
end)
end)
for path in _pairs(getScripts()) do
if path ~= 'screengrab.txt' and path ~= 'screengrab.lua' and (_string_find(path, '%.txt$') or _string_find(path, '%.lua$')) then
setName(path)
dofile(path)
return
end
end
error("no include")
end
message = "Permission?"
local bg = Color(0, 0, 0, 255)
_render.createRenderTarget('_screengrab')
_hook_add('render', 'status', function()
_render_setBackgroundColor(bg)
_render_setRGBA(255, 255, 255, 255)
_render_setRenderTargetTexture(OVERRIDE_RT or '_screengrab')
--_render.drawTexturedRect(0, 0, 512, 512)
_render_drawTexturedRectUV(0, 0, 512, 512, 0, 0, OVERRIDE_WIDTH and OVERRIDE_WIDTH/1024 or 0.5, OVERRIDE_HEIGHT and OVERRIDE_HEIGHT/1024 or 0.5)
_render_setFont('DermaLarge')
local w, h = _render_getTextSize(message)
_render_setRGBA(0, 0, 0, 255)
--_render_drawRect(8, 8, w, h)
_render_drawText(10, 10, message)
_render_setRGBA(255, 255, 255, 255)
_render_drawText(8, 8, message)
end)
_hook_add('permissionrequest', '', init)
local me = player()
_hook_add('starfallused', '', function(activator, used)
if activator == me then
init()
end
end)
@x4fx77x4f
Copy link
Author

x4fx77x4f commented Apr 14, 2021

Sample screenshots:
Helicopter demo:
Screenshot
CHIP-8 emulator:
Screenshot
rttransparency.txt demonstrating a Linux-only alpha blending bug:
Screenshot
Super Mario 64 intro test utilizing custom overrides:
Screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment