From 116575fb5fab2effcb645d2dcd730d956aa677f8 Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 14:19:16 +0100 Subject: [PATCH 01/46] Replaced the old shell with LSH --- sdcard/3ds/ctruLua/libs/filepicker.lua | 199 +++++++++++++++++++++++++ sdcard/3ds/ctruLua/main.lua | 71 +++++---- 2 files changed, 243 insertions(+), 27 deletions(-) create mode 100644 sdcard/3ds/ctruLua/libs/filepicker.lua diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua new file mode 100644 index 0000000..61a3dfb --- /dev/null +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -0,0 +1,199 @@ +-- LSH version 0.1 +-- ctrµLua official shell + +local ctr = require("ctr") +local gfx = require("ctr.gfx") + +local function saveGraphicsState() + local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), + gfx.font.getDefault()} + + local mono = gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf") + + gfx.set3D(false) + gfx.color.setDefault(0xFFFDFDFD) + gfx.color.setBackground(0xFF333333) + gfx.font.setDefault(mono) + + return old +end + +local function restoreGraphicsState(state) + gfx.set3D(state[1]) + gfx.color.setDefault(state[2]) + gfx.color.setBackground(state[3]) + gfx.font.setDefault(state[4]) +end + +local function getExtension(sel, bindings) + for _, ext in ipairs(bindings) do + if ext.ext == sel:match("%..+$") then + return ext + end + end +end + +local function getFilelist(cur) + local files = ctr.fs.list(cur) + + if cur ~= "/" and cur ~= "sdmc:/" then + table.insert(files, {name = "..", isDirectory = true}) + end + + -- Stealy stealing code from original openfile.lua + table.sort(files, function(i, j) + if i.isDirectory and not j.isDirectory then + return true + elseif i.isDirectory == j.isDirectory then + return string.lower(i.name) < string.lower(j.name) + end + end) + + return files +end + +local function drawBottom(cur, selFile, bindings) + local ext = getExtension(selFile.name, bindings) + + gfx.start(gfx.BOTTOM) + gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3) + gfx.text(1, 0, cur, 12) + gfx.text(1, 15, selFile.name, 12) + if not selFile.isDirectory then + gfx.text(1, 45, selFile.fileSize, 12) + end + + local keys = {"X: Quit/Cancel"} + if selFile.isDirectory then + gfx.text(1, 30, "Directory", 12, 0xFF727272) + gfx.text(1, gfx.BOTTOM_HEIGHT - 30, "A: Open", 12) + gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) + elseif ext then + local lines = 1 + + -- Keys + if ext.y then + lines = lines + 1 + table.insert(keys, "Y: " .. ext.y) + end + if ext.a then + lines = lines + 1 + table.insert(keys, "A: " .. ext.a) + end + + -- Drawing + for i=lines, 1, -1 do + gfx.text(1, gfx.BOTTOM_HEIGHT - 15*i, keys[i], 12) + end + gfx.text(1, 30, ext.name, 12, 0xFF727272) + gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) + else + gfx.text(1, 30, "File", 12, 0xFF727272) + gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) + gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) + end + gfx.stop() +end + +local function drawTop(files, sel, scr) + gfx.start(gfx.TOP) + gfx.rectangle(0, (sel-scr-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3) + local over = #files - scr >= 16 and 16 or #files - scr + for i=scr+1, scr+over do + local color = files[i].isDirectory and 0xFF727272 or 0xFFFDFDFD + gfx.text(1, (i-scr-1)*15+1, files[i].name or "", 12, color) + end + gfx.stop() +end + +local function runA(cur, selFile, bindings) + if not selFile.isDirectory then + local ext = getExtension(selFile.name, bindings) + if not ext then return end + if ext.a then return cur .. selFile.name, ext.ext end + end +end + +local function runY(cur, selFile, bindings) + if not selFile.isDirectory then + local ext = getExtension(selFile.name, bindings) + if not ext then return end + if ext.y then return cur .. selFile.name, ext.ext end + end +end + +--- Open a file browser to allow the user to select a file. +-- It will save current graphical settings and set them back after ending. Press up or down to move one element at a time, and press left or right to go at the beginning or at the end of the list. This function is the return result of requiring filepicker.lua +-- @name filePicker +-- @param bindings A table of the extensions the user can select in the format {{name, ext, a, y},...}, name will show up instead of "File" or "Directory" on the bottom screen, a and y set the action names for those keys on the bottom screen (and also enable them, so if there's neither a or y, the file will have a custom type name but won't be effectively selectable), and ext is the extension to search for, dot included. Everything must be strings. +-- @param workdir Optional, current working directory will be used if not specified, otherwise, sets the path at which the file browser first shows up, a string. +-- @returns The absolute path to the file, nil in case no file was picked. +-- @returns The extension of the file, this might be helpful in cases were multiple file types could be expected, nil in case no file was picked. +-- @returns The "mode", which indicates which key was used to select the file, "A" or "Y". "X" in case no file was picked. +return function(bindings, workdir) + -- Initialization + local old = saveGraphicsState() + local cur = workdir or ctr.fs.getDirectory() + if cur:sub(-1) ~= "/" then + cur = cur .. "/" + end + local bindings = bindings or {} + + local files = getFilelist(cur) or {{name = "- Empty -"}} + local sel = 1 + local scr = 0 + + while ctr.run() do + drawBottom(cur, files[sel], bindings) + drawTop(files, sel, scr) + gfx.render() + + ctr.hid.read() + local state = ctr.hid.keys() + if (state.down.dDown or state.down.cpadDown) and sel < #files then + sel = sel + 1 + if sel - scr >= 16 then + scr = scr + 1 + end + elseif (state.down.dUp or state.down.cpadUp) and sel > 1 then + sel = sel - 1 + if sel == scr then + scr = scr - 1 + end + elseif state.down.dLeft or state.down.cpadLeft then + sel = 1 + scr = 0 + elseif state.down.dRight or state.down.cpadRight then + sel = #files + if #files > 15 then + scr = #files - 16 + end + + elseif state.down.a then + local selFile = files[sel] + if selFile.isDirectory then + if selFile.name == ".." then + cur = cur:gsub("[^/]+/$", "") + else + cur = cur .. selFile.name .. "/" + end + files, sel, scr = getFilelist(cur), 1, 0 + else + local file, ext = runA(cur, selFile, bindings) + if file then + restoreGraphicsState(old) + return file, ext, "A" + end + end + elseif state.down.y then + local file, ext = runY(cur, files[sel], bindings) + if file then + restoreGraphicsState(old) + return file, ext, "Y" + end + elseif state.down.x then + restoreGraphicsState(old) + return nil, nil, "X" + end + end +end \ No newline at end of file diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 4b9540d..66ef48d 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -1,33 +1,50 @@ +local ctr = require("ctr") +local fs = require("ctr.fs") local gfx = require("ctr.gfx") -local fs = require("ctr.fs") + +-- Initializing "constants" +ctruLua = {} +ctruLua.root = fs.getDirectory() -- Set up path local ldir = fs.getDirectory().."libs/" package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua" -repeat - gfx.set3D(false) - gfx.color.setDefault(0xFFFFFFFF) - gfx.color.setBackground(0xFF000000) - gfx.font.setDefault() - local file = require("openfile")("Choose a Lua file to execute", nil, ".lua", "exist") - if file then - fs.setDirectory(file:match("^(.-)[^/]*$")) - local success, err = pcall(dofile, file) - if not success then - local hid = require("ctr.hid") - gfx.set3D(false) - gfx.color.setDefault(0xFFFFFFFF) - gfx.color.setBackground(0xFF000000) - gfx.font.setDefault() - while true do - hid.read() - if hid.keys().down.start then break end - gfx.start(gfx.TOP) - gfx.wrappedText(0, 0, err, gfx.TOP_WIDTH) - gfx.stop() - gfx.render() - end - end - end -until not file \ No newline at end of file +-- Erroring +local function displayError(err) + gfx.color.setBackground(0xFF0000B3) + gfx.color.setDefault(0xFFFDFDFD) + gfx.font.setDefault(gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf")) + + while ctr.run() do + gfx.start(gfx.TOP) + gfx.text(1, 1, "An error has occured.", 12) + gfx.wrappedText(1, 30, err, gfx.TOP_WIDTH-2, 12) + gfx.text(1, gfx.TOP_HEIGHT-15, "Press Start to continue.", 12) + gfx.stop() + gfx.start(gfx.BOTTOM) + gfx.stop() + + gfx.render() + ctr.hid.read() + if ctr.hid.keys().down.start then break end + end +end + +-- Main loop +while ctr.run() do + gfx.set3D(false) + gfx.font.setDefault() + gfx.color.setDefault(0xFFFDFDFD) + gfx.color.setBackground(0xFF333333) + local file, ext, mode = require("filepicker")({{name="Lua Script", ext=".lua", a="Execute"}}) + if file and mode == "A" then + fs.setDirectory(file:match("^(.-)[^/]*$")) + local ok, err = pcall(dofile, file) + if not ok then displayError(err) end + else + break + end +end + +error("Main process has exited.\nPlease reboot.\nPressing Start does not work yet.") \ No newline at end of file From 3eb41b5062d2b204d4e73f6c6613ed269eb9caf1 Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 14:23:36 +0100 Subject: [PATCH 02/46] Tidbits of documentation --- sdcard/3ds/ctruLua/main.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 66ef48d..196a8c9 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -4,6 +4,8 @@ local gfx = require("ctr.gfx") -- Initializing "constants" ctruLua = {} + +--- The ctruLua root directory's absolute path ctruLua.root = fs.getDirectory() -- Set up path From a380f09a348f81a6da17535da10c8c82822af031 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Wed, 24 Feb 2016 14:46:11 +0100 Subject: [PATCH 03/46] Updated httpc.c to the latest ctrulib, fixed the cfgu example. The name is still buggy, can't find why. --- sdcard/3ds/ctruLua/examples/cfgu/cfgu.lua | 3 +- source/httpc.c | 59 ++++++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/sdcard/3ds/ctruLua/examples/cfgu/cfgu.lua b/sdcard/3ds/ctruLua/examples/cfgu/cfgu.lua index 90db095..3ca851a 100644 --- a/sdcard/3ds/ctruLua/examples/cfgu/cfgu.lua +++ b/sdcard/3ds/ctruLua/examples/cfgu/cfgu.lua @@ -36,6 +36,7 @@ local models = { [cfgu.MODEL_N3DSXL] = "New 3DS XL" } +cfgu.init() while ctr.run() do hid.read() keys = hid.keys() @@ -45,7 +46,7 @@ while ctr.run() do gfx.text(2, 2, "CFGU example") gfx.text(2, 20, "Region: "..regions[cfgu.getRegion()]) gfx.text(2, 30, "Model: "..models[cfgu.getModel()]) - gfx.text(2, 40, "Language: "..models[cfgu.getLanguage()]) + gfx.text(2, 40, "Language: "..languages[cfgu.getLanguage()]) gfx.text(2, 50, "Username: "..cfgu.getUsername()) local m,d = cfgu.getBirthday() gfx.text(2, 60, "Birthday: "..d.."/"..m) diff --git a/source/httpc.c b/source/httpc.c index eed4590..1386880 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -22,12 +22,6 @@ Create a HTTP Context. */ static int httpc_context(lua_State *L) { httpcContext context; - Result ret = httpcOpenContext(&context, "http://google.com/", 0); // Initialization only. - if (ret != 0) { - lua_pushnil(L); - lua_pushinteger(L, ret); - return 2; - } lua_newuserdata(L, sizeof(&context)); luaL_getmetatable(L, "LHTTPC"); lua_setmetatable(L, -2); @@ -44,13 +38,25 @@ context object Open an url in the context. @function :open @tparam string url the url to open +@tparam[opt="GET"] string method method to use; can be `"GET"`, `"POST"`, `"HEAD"`, `"PUT"` or `"DELETE"` */ static int httpc_open(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); char *url = (char*)luaL_checkstring(L, 2); + char *smethod = (char*)luaL_optstring(L, 3, "GET"); + HTTPC_RequestMethod method = HTTPC_METHOD_GET; // default to GET + if (strcmp(smethod, "POST")) { + method = HTTPC_METHOD_POST; + } else if (strcmp(smethod, "HEAD")) { + method = HTTPC_METHOD_HEAD; + } else if (strcmp(smethod, "PUT")) { + method = HTTPC_METHOD_PUT; + } else if (strcmp(smethod, "DELETE")) { + method = HTTPC_METHOD_DELETE; + } Result ret = 0; - ret = httpcOpenContext(context, url, 0); + ret = httpcOpenContext(context, method, url, 0); if (ret != 0) { lua_pushnil(L); lua_pushinteger(L, ret); @@ -177,6 +183,41 @@ static int httpc_close(lua_State *L) { return 0; } +/*** +Add a POST form field to a HTTP context. +@function :addPostData +@tparam string name name of the field +@tparam string value value of the field +*/ +static int httpc_addPostData(lua_State *L) { + httpcContext *context = lua_touserdata(L, 1); + char *name = (char*)luaL_checkstring(L, 2); + char *value = (char*)luaL_checkstring(L, 3); + + httpcAddPostDataAscii(context, name, value); + + return 0; +} + +/*** +Get a header field from a response. +@function :getResponseHeader +@tparam string name name of the header field to get +@tparam[opt=2048] number maximum size of the value to get +@treturn string field value +*/ +static int httpc_getResponseHeader(lua_State *L) { + httpcContext *context = lua_touserdata(L, 1); + char *name = (char*)luaL_checkstring(L, 2); + u32 maxSize = luaL_checkinteger(L, 3); + char* value = 0; + + httpcGetResponseHeader(context, name, value, maxSize); + + lua_pushstring(L, value); + return 1; +} + // object static const struct luaL_Reg httpc_methods[] = { {"open", httpc_open }, @@ -186,6 +227,8 @@ static const struct luaL_Reg httpc_methods[] = { {"getDownloadSize", httpc_getDownloadSize }, {"downloadData", httpc_downloadData }, {"close", httpc_close }, + {"addPostData", httpc_addPostData }, + {"getResponseHeader", httpc_getResponseHeader }, {NULL, NULL} }; @@ -208,7 +251,7 @@ int luaopen_httpc_lib(lua_State *L) { void load_httpc_lib(lua_State *L) { if (!isHttpcInitialized) { - httpcInit(); + httpcInit(0x1000); isHttpcInitialized = true; } From b6beaddf66c71362136714057e087b478d966db9 Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 17:55:12 +0100 Subject: [PATCH 04/46] Tabulation correction --- sdcard/3ds/ctruLua/libs/filepicker.lua | 290 ++++++++++++------------- sdcard/3ds/ctruLua/main.lua | 56 ++--- 2 files changed, 173 insertions(+), 173 deletions(-) diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua index 61a3dfb..7a017cf 100644 --- a/sdcard/3ds/ctruLua/libs/filepicker.lua +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -5,121 +5,121 @@ local ctr = require("ctr") local gfx = require("ctr.gfx") local function saveGraphicsState() - local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), - gfx.font.getDefault()} + local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), + gfx.font.getDefault()} - local mono = gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf") + local mono = gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf") - gfx.set3D(false) - gfx.color.setDefault(0xFFFDFDFD) - gfx.color.setBackground(0xFF333333) - gfx.font.setDefault(mono) + gfx.set3D(false) + gfx.color.setDefault(0xFFFDFDFD) + gfx.color.setBackground(0xFF333333) + gfx.font.setDefault(mono) - return old + return old end local function restoreGraphicsState(state) - gfx.set3D(state[1]) - gfx.color.setDefault(state[2]) - gfx.color.setBackground(state[3]) - gfx.font.setDefault(state[4]) + gfx.set3D(state[1]) + gfx.color.setDefault(state[2]) + gfx.color.setBackground(state[3]) + gfx.font.setDefault(state[4]) end local function getExtension(sel, bindings) - for _, ext in ipairs(bindings) do - if ext.ext == sel:match("%..+$") then - return ext - end - end + for _, ext in ipairs(bindings) do + if ext.ext == sel:match("%..+$") then + return ext + end + end end local function getFilelist(cur) - local files = ctr.fs.list(cur) + local files = ctr.fs.list(cur) - if cur ~= "/" and cur ~= "sdmc:/" then - table.insert(files, {name = "..", isDirectory = true}) - end + if cur ~= "/" and cur ~= "sdmc:/" then + table.insert(files, {name = "..", isDirectory = true}) + end - -- Stealy stealing code from original openfile.lua - table.sort(files, function(i, j) - if i.isDirectory and not j.isDirectory then - return true - elseif i.isDirectory == j.isDirectory then - return string.lower(i.name) < string.lower(j.name) - end - end) + -- Stealy stealing code from original openfile.lua + table.sort(files, function(i, j) + if i.isDirectory and not j.isDirectory then + return true + elseif i.isDirectory == j.isDirectory then + return string.lower(i.name) < string.lower(j.name) + end + end) - return files + return files end local function drawBottom(cur, selFile, bindings) - local ext = getExtension(selFile.name, bindings) + local ext = getExtension(selFile.name, bindings) - gfx.start(gfx.BOTTOM) - gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3) - gfx.text(1, 0, cur, 12) - gfx.text(1, 15, selFile.name, 12) - if not selFile.isDirectory then - gfx.text(1, 45, selFile.fileSize, 12) - end + gfx.start(gfx.BOTTOM) + gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3) + gfx.text(1, 0, cur, 12) + gfx.text(1, 15, selFile.name, 12) + if not selFile.isDirectory then + gfx.text(1, 45, selFile.fileSize, 12) + end - local keys = {"X: Quit/Cancel"} - if selFile.isDirectory then - gfx.text(1, 30, "Directory", 12, 0xFF727272) - gfx.text(1, gfx.BOTTOM_HEIGHT - 30, "A: Open", 12) - gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) - elseif ext then - local lines = 1 + local keys = {"X: Quit/Cancel"} + if selFile.isDirectory then + gfx.text(1, 30, "Directory", 12, 0xFF727272) + gfx.text(1, gfx.BOTTOM_HEIGHT - 30, "A: Open", 12) + gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) + elseif ext then + local lines = 1 - -- Keys - if ext.y then - lines = lines + 1 - table.insert(keys, "Y: " .. ext.y) - end - if ext.a then - lines = lines + 1 - table.insert(keys, "A: " .. ext.a) - end + -- Keys + if ext.y then + lines = lines + 1 + table.insert(keys, "Y: " .. ext.y) + end + if ext.a then + lines = lines + 1 + table.insert(keys, "A: " .. ext.a) + end - -- Drawing - for i=lines, 1, -1 do - gfx.text(1, gfx.BOTTOM_HEIGHT - 15*i, keys[i], 12) - end - gfx.text(1, 30, ext.name, 12, 0xFF727272) - gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) - else - gfx.text(1, 30, "File", 12, 0xFF727272) - gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) - gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) - end - gfx.stop() + -- Drawing + for i=lines, 1, -1 do + gfx.text(1, gfx.BOTTOM_HEIGHT - 15*i, keys[i], 12) + end + gfx.text(1, 30, ext.name, 12, 0xFF727272) + gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) + else + gfx.text(1, 30, "File", 12, 0xFF727272) + gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) + gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) + end + gfx.stop() end local function drawTop(files, sel, scr) - gfx.start(gfx.TOP) - gfx.rectangle(0, (sel-scr-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3) - local over = #files - scr >= 16 and 16 or #files - scr - for i=scr+1, scr+over do - local color = files[i].isDirectory and 0xFF727272 or 0xFFFDFDFD - gfx.text(1, (i-scr-1)*15+1, files[i].name or "", 12, color) - end - gfx.stop() + gfx.start(gfx.TOP) + gfx.rectangle(0, (sel-scr-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3) + local over = #files - scr >= 16 and 16 or #files - scr + for i=scr+1, scr+over do + local color = files[i].isDirectory and 0xFF727272 or 0xFFFDFDFD + gfx.text(1, (i-scr-1)*15+1, files[i].name or "", 12, color) + end + gfx.stop() end local function runA(cur, selFile, bindings) - if not selFile.isDirectory then - local ext = getExtension(selFile.name, bindings) - if not ext then return end - if ext.a then return cur .. selFile.name, ext.ext end - end + if not selFile.isDirectory then + local ext = getExtension(selFile.name, bindings) + if not ext then return end + if ext.a then return cur .. selFile.name, ext.ext end + end end local function runY(cur, selFile, bindings) - if not selFile.isDirectory then - local ext = getExtension(selFile.name, bindings) - if not ext then return end - if ext.y then return cur .. selFile.name, ext.ext end - end + if not selFile.isDirectory then + local ext = getExtension(selFile.name, bindings) + if not ext then return end + if ext.y then return cur .. selFile.name, ext.ext end + end end --- Open a file browser to allow the user to select a file. @@ -131,69 +131,69 @@ end -- @returns The extension of the file, this might be helpful in cases were multiple file types could be expected, nil in case no file was picked. -- @returns The "mode", which indicates which key was used to select the file, "A" or "Y". "X" in case no file was picked. return function(bindings, workdir) - -- Initialization - local old = saveGraphicsState() - local cur = workdir or ctr.fs.getDirectory() - if cur:sub(-1) ~= "/" then - cur = cur .. "/" - end - local bindings = bindings or {} + -- Initialization + local old = saveGraphicsState() + local cur = workdir or ctr.fs.getDirectory() + if cur:sub(-1) ~= "/" then + cur = cur .. "/" + end + local bindings = bindings or {} - local files = getFilelist(cur) or {{name = "- Empty -"}} - local sel = 1 - local scr = 0 + local files = getFilelist(cur) or {{name = "- Empty -"}} + local sel = 1 + local scr = 0 - while ctr.run() do - drawBottom(cur, files[sel], bindings) - drawTop(files, sel, scr) - gfx.render() + while ctr.run() do + drawBottom(cur, files[sel], bindings) + drawTop(files, sel, scr) + gfx.render() - ctr.hid.read() - local state = ctr.hid.keys() - if (state.down.dDown or state.down.cpadDown) and sel < #files then - sel = sel + 1 - if sel - scr >= 16 then - scr = scr + 1 - end - elseif (state.down.dUp or state.down.cpadUp) and sel > 1 then - sel = sel - 1 - if sel == scr then - scr = scr - 1 - end - elseif state.down.dLeft or state.down.cpadLeft then - sel = 1 - scr = 0 - elseif state.down.dRight or state.down.cpadRight then - sel = #files - if #files > 15 then - scr = #files - 16 - end + ctr.hid.read() + local state = ctr.hid.keys() + if (state.down.dDown or state.down.cpadDown) and sel < #files then + sel = sel + 1 + if sel - scr >= 16 then + scr = scr + 1 + end + elseif (state.down.dUp or state.down.cpadUp) and sel > 1 then + sel = sel - 1 + if sel == scr then + scr = scr - 1 + end + elseif state.down.dLeft or state.down.cpadLeft then + sel = 1 + scr = 0 + elseif state.down.dRight or state.down.cpadRight then + sel = #files + if #files > 15 then + scr = #files - 16 + end - elseif state.down.a then - local selFile = files[sel] - if selFile.isDirectory then - if selFile.name == ".." then - cur = cur:gsub("[^/]+/$", "") - else - cur = cur .. selFile.name .. "/" - end - files, sel, scr = getFilelist(cur), 1, 0 - else - local file, ext = runA(cur, selFile, bindings) - if file then - restoreGraphicsState(old) - return file, ext, "A" - end - end - elseif state.down.y then - local file, ext = runY(cur, files[sel], bindings) - if file then - restoreGraphicsState(old) - return file, ext, "Y" - end - elseif state.down.x then - restoreGraphicsState(old) - return nil, nil, "X" - end - end + elseif state.down.a then + local selFile = files[sel] + if selFile.isDirectory then + if selFile.name == ".." then + cur = cur:gsub("[^/]+/$", "") + else + cur = cur .. selFile.name .. "/" + end + files, sel, scr = getFilelist(cur), 1, 0 + else + local file, ext = runA(cur, selFile, bindings) + if file then + restoreGraphicsState(old) + return file, ext, "A" + end + end + elseif state.down.y then + local file, ext = runY(cur, files[sel], bindings) + if file then + restoreGraphicsState(old) + return file, ext, "Y" + end + elseif state.down.x then + restoreGraphicsState(old) + return nil, nil, "X" + end + end end \ No newline at end of file diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 196a8c9..16b214c 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -1,4 +1,4 @@ -local ctr = require("ctr") +²local ctr = require("ctr") local fs = require("ctr.fs") local gfx = require("ctr.gfx") @@ -14,39 +14,39 @@ package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua" -- Erroring local function displayError(err) - gfx.color.setBackground(0xFF0000B3) - gfx.color.setDefault(0xFFFDFDFD) - gfx.font.setDefault(gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf")) + gfx.color.setBackground(0xFF0000B3) + gfx.color.setDefault(0xFFFDFDFD) + gfx.font.setDefault(gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf")) - while ctr.run() do - gfx.start(gfx.TOP) - gfx.text(1, 1, "An error has occured.", 12) - gfx.wrappedText(1, 30, err, gfx.TOP_WIDTH-2, 12) - gfx.text(1, gfx.TOP_HEIGHT-15, "Press Start to continue.", 12) - gfx.stop() - gfx.start(gfx.BOTTOM) - gfx.stop() + while ctr.run() do + gfx.start(gfx.TOP) + gfx.text(1, 1, "An error has occured.", 12) + gfx.wrappedText(1, 30, err, gfx.TOP_WIDTH-2, 12) + gfx.text(1, gfx.TOP_HEIGHT-15, "Press Start to continue.", 12) + gfx.stop() + gfx.start(gfx.BOTTOM) + gfx.stop() - gfx.render() - ctr.hid.read() - if ctr.hid.keys().down.start then break end - end + gfx.render() + ctr.hid.read() + if ctr.hid.keys().down.start then break end + end end -- Main loop while ctr.run() do - gfx.set3D(false) - gfx.font.setDefault() - gfx.color.setDefault(0xFFFDFDFD) - gfx.color.setBackground(0xFF333333) - local file, ext, mode = require("filepicker")({{name="Lua Script", ext=".lua", a="Execute"}}) - if file and mode == "A" then - fs.setDirectory(file:match("^(.-)[^/]*$")) - local ok, err = pcall(dofile, file) - if not ok then displayError(err) end - else - break - end + gfx.set3D(false) + gfx.font.setDefault() + gfx.color.setDefault(0xFFFDFDFD) + gfx.color.setBackground(0xFF333333) + local file, ext, mode = require("filepicker")({{name="Lua Script", ext=".lua", a="Execute"}}) + if file and mode == "A" then + fs.setDirectory(file:match("^(.-)[^/]*$")) + local ok, err = pcall(dofile, file) + if not ok then displayError(err) end + else + break + end end error("Main process has exited.\nPlease reboot.\nPressing Start does not work yet.") \ No newline at end of file From cbfc7bfaca9fd349b84d839aa0f936ba2a261aa2 Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 18:01:37 +0100 Subject: [PATCH 05/46] Oopsie. --- sdcard/3ds/ctruLua/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 16b214c..53bd3c1 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -1,4 +1,4 @@ -²local ctr = require("ctr") +local ctr = require("ctr") local fs = require("ctr.fs") local gfx = require("ctr.gfx") From 60b304b2d31ade4ce5c90e28e83f00375ee1f4ea Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 19:00:47 +0100 Subject: [PATCH 06/46] Forgot the font --- sdcard/3ds/ctruLua/main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 53bd3c1..705397a 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -14,6 +14,7 @@ package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua" -- Erroring local function displayError(err) + gfx.set3D(false) gfx.color.setBackground(0xFF0000B3) gfx.color.setDefault(0xFFFDFDFD) gfx.font.setDefault(gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf")) From 34d12eae0f73370550e2a218a7e641b54121e2fa Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Wed, 24 Feb 2016 19:04:17 +0100 Subject: [PATCH 07/46] Added VeraMono.ttf, for real this time --- sdcard/3ds/ctruLua/resources/VeraMono.ttf | Bin 0 -> 49224 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sdcard/3ds/ctruLua/resources/VeraMono.ttf diff --git a/sdcard/3ds/ctruLua/resources/VeraMono.ttf b/sdcard/3ds/ctruLua/resources/VeraMono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..139f0b4311ad2e0369a347b3be6c46e6c2b730d5 GIT binary patch literal 49224 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 literal 0 HcmV?d00001 From e84ab0e3b2ca041d29dbfde6766bcb81a885a9a6 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Wed, 24 Feb 2016 21:48:28 +0100 Subject: [PATCH 08/46] Added functions to set/get the default text size, closes #4 --- source/gfx.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/source/gfx.c b/source/gfx.c index 18a110b..a7e1345 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -18,6 +18,7 @@ The `gfx` module. bool isGfxInitialized = false; bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib. +u32 textSize = 9; /*** The `ctr.gfx.color` module. @@ -255,7 +256,7 @@ Draw a text on the current screen. @tparam integer x text drawing origin horizontal coordinate, in pixels @tparam integer y text drawing origin vertical coordinate, in pixels @tparam string text the text to draw -@tparam[opt=9] integer size drawing size, in pixels +@tparam[opt=default size] integer size drawing size, in pixels @tparam[opt=default color] integer color drawing color @tparam[opt=default font] font font to use */ @@ -265,7 +266,7 @@ static int gfx_text(lua_State *L) { size_t len; const char *text = luaL_checklstring(L, 3, &len); - int size = luaL_optinteger(L, 4, 9); + int size = luaL_optinteger(L, 4, textSize); u32 color = luaL_optinteger(L, 5, color_default); font_userdata *font = luaL_testudata(L, 6, "LFont"); if (font == NULL) { @@ -293,7 +294,7 @@ Warning: No UTF32 support. @tparam integer y text drawing origin vertical coordinate, in pixels @tparam string text the text to draw @tparam integer width width of a line, in pixels -@tparam[opt=9] integer size drawing size, in pixels +@tparam[opt=default Size] integer size drawing size, in pixels @tparam[opt=default color] integer color drawing color @tparam[opt=default font] font font to use */ @@ -304,7 +305,7 @@ static int gfx_wrappedText(lua_State *L) { const char *text = luaL_checklstring(L, 3, &len); unsigned int lineWidth = luaL_checkinteger(L, 4); - int size = luaL_optinteger(L, 5, 9); + int size = luaL_optinteger(L, 5, textSize); u32 color = luaL_optinteger(L, 6, color_default); font_userdata *font = luaL_testudata(L, 7, "LFont"); if (font == NULL) { @@ -330,7 +331,7 @@ Calculate the size of a text draw with `wrappedText`. @function calcBoundingBox @tparam string text The text to check @tparam integer lineWidth width of a line, in pixels -@tparam[opt=9] integer size drawing size, in pixels +@tparam[opt=default size] integer size drawing size, in pixels @tparam[opt=default font] font font to use @treturn integer width of the text, in pixels @treturn integer height of the text, in pixels @@ -339,7 +340,7 @@ static int gfx_calcBoundingBox(lua_State *L) { size_t len; const char *text = luaL_checklstring(L, 1, &len); unsigned int lineWidth = luaL_checkinteger(L, 2); - int size = luaL_optinteger(L, 3, 9); + int size = luaL_optinteger(L, 3, textSize); font_userdata *font = luaL_testudata(L, 4, "LFont"); if (font == NULL) { lua_getfield(L, LUA_REGISTRYINDEX, "LFontDefault"); @@ -356,6 +357,26 @@ static int gfx_calcBoundingBox(lua_State *L) { return 2; } +/*** +Set the default text size. +@function setTextSize +@tparam number size new default text size +*/ +static int gfx_setTextSize(lua_State *L) { + textSize = luaL_checkinteger(L, 1); + return 0; +} + +/*** +Return the default text size. +@function getTextSize +@treturn number the default text size +*/ +static int gfx_getTextSize(lua_State *L) { + lua_pushinteger(L, textSize); + return 1; +} + // Functions static const struct luaL_Reg gfx_lib[] = { { "start", gfx_start }, @@ -374,6 +395,8 @@ static const struct luaL_Reg gfx_lib[] = { { "text", gfx_text }, { "wrappedText", gfx_wrappedText }, { "calcBoundingBox", gfx_calcBoundingBox }, + { "setTextSize", gfx_setTextSize }, + { "getTextSize", gfx_getTextSize }, { NULL, NULL } }; From 34c48f360e8d3413caed829727fc3bbc36448744 Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 29 Feb 2016 19:42:52 +0100 Subject: [PATCH 09/46] Added a way to build the documentation as .sublime-completions files (make build-doc-st) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also replaced make build-doc with make build-doc-html so we can easily add new documentation formats in the future. To add ctrµLua API autocompletion to Sublime Text, simply copy the output directory (doc/sublimetext) to your ST's package directory. --- .gitignore | 3 +- Makefile | 18 ++++++++ doc/ldoc.ltp | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 doc/ldoc.ltp diff --git a/.gitignore b/.gitignore index 9208658..59e812b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build/* /ctruLua.* -/doc/html/* \ No newline at end of file +/doc/html/* +/doc/sublimetext/* diff --git a/Makefile b/Makefile index 671e455..fede3ea 100644 --- a/Makefile +++ b/Makefile @@ -166,8 +166,17 @@ build-all: @make build build-doc: + @echo Building HTML documentation... + @make build-doc-html + @echo Building SublimeText documentation... + @make build-doc-st + +build-doc-html: @cd doc/ && ldoc . && cd .. +build-doc-st: + @cd doc/ && ldoc . --template ./ --ext sublime-completions --dir ./sublimetext/ && cd .. + #--------------------------------------------------------------------------------- clean: @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf @@ -197,8 +206,17 @@ clean-all: @make clean clean-doc: + @echo Cleaning HTML documentation... + @make clean-doc-html + @echo Cleaning SublimeText documentation... + @make clean-doc-st + +clean-doc-html: @rm -rf doc/html +clean-doc-st: + @rm -rf doc/sublimetext + #--------------------------------------------------------------------------------- else diff --git a/doc/ldoc.ltp b/doc/ldoc.ltp new file mode 100644 index 0000000..600da3e --- /dev/null +++ b/doc/ldoc.ltp @@ -0,0 +1,118 @@ +# -- LDoc template by Reuh. +# -- Generates sublime-completions files which can be used in Sublime Text 3 for autocompletion. +# -- Based on the HTML template, so generated files will contain lots of comments with additionnal data not handled by ST. +# -- I tried to make the generated files human-readable, so they may be used instead of the HTML documentation. +# -- Typical usage: ldoc . --template ./ --ext sublime-completions --dir ./sublimetext/ +# +# local scope = "source.lua" +# local function e(str) return str:gsub("\"", "\\\"") end -- escape json string ("str") +# local function indent(indentation, str) -- indent str (except first line) with indentation +# return str:gsub("(.-)\n", indentation.."%1\n"):gsub("\n([^\n]*)$", "\n"..indentation.."%1"):gsub("^"..indentation, "") +# end +# local function displayName(item) return item.type == "function" and item.name..item.args or item.name end -- nice name +# local function autocompleteName(item) -- ST-autocomplete name +# if item.type == "function" then +# local i = 1 +# local args = "(" +# for arg in (item.args:match("^%((.*)%)$")..","):gmatch("%s*([^,]+)%,") do +# args = args.."${"..i..":"..arg.."}, " +# i = i +1 +# end +# return item.name..args:gsub("%, $", "")..")" +# else return item.name end +# end +/* +Title: $(ldoc.title) +Project: $(ldoc.project) +Description: $(ldoc.description) +# if ldoc.single then +(Single module-project) +# end +# if not module then + +Project contents: +# for kind, mods in ldoc.kinds() do + $(kind) +# for m in mods() do + $(m.name): $(m.summary) +# end +# end +*/ +# else -- if not module +*/ + +/* +Module: $(module.name) +Summary: $(module.summary) +Description: $(module.description) + +Module contents: +# for kind, items in module.kinds() do + $(kind) +# for item in items() do + $(item.type) $(displayName(item)) +# end +# end +*/ + +/* Completions */ +{ + "scope": "$(e(scope))", + + "completions": [ +# for kind, items in module.kinds() do + /* $(kind) */ +# for item in items() do + /* + $(item.type) $(displayName(item)) + Summary: $(item.summary) + Description: $(indent("\t\t\t", item.description)) +# if item.type == "function" then + Parameters: +# for p in item.params:iter() do +# local default = item:default_of_param(p) +# if default == true then default = "(optional)" +# elseif default then default = "(defaults to "..default..")" end + ($(item:type_of_param(p))) $(item:display_name_of(p)):$(item.params.map[p]) $(default) +# end +# local retgroups = item.retgroups or {} + Returns: +# for i, group in ldoc.ipairs(retgroups) do +# for ret in group:iter() do + ($(ret.type)) $(indent("\t\t\t", ret.text)) +# end +# if i < #retgroups then + ---or--- +# end +# end +# end +# if item.usage then + Usage: +# for i, usage in ldoc.ipairs(item.usage) do + $(sep)$(indent("\t\t\t", usage:gsub("^\n", ""))) +# if i < #item.usage then + -------- +# end +# end +# end +# if item.see then + See also: +# for see in item.see:iter() do + $(see.mod.name): $(see.name) +# end +# end + */ +# for pos in (module.name.."."):gmatch("()[^.]+%.") do +# local prefix = e(module.name:sub(pos) .. (item.name:match("^[%.%:]") and "" or ".")) + { + "trigger": "$(prefix)$(e(item.name))\t$(e(item.summary))", + "contents": "$(prefix)$(e(autocompleteName(item)))" + }, +# end +# end +# end + ] +} +# end -- if not module + +/* Generated by LDoc; sublime-completions template by Reuh. Last updated $(ldoc.updatetime).*/ From acd41db80546404e004c192aa42c7e8771eeb025 Mon Sep 17 00:00:00 2001 From: Reuh Date: Wed, 9 Mar 2016 13:30:15 +0100 Subject: [PATCH 10/46] Update README.md links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6232241..c8e96c4 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ Everything is in the Wiki. Warning: the 'u' in the repo's name is a 'µ', not a 'u'. -#### Builds ![build status](http://thomas99.no-ip.org:3000/ctrulua.png) +#### Builds ![build status](http://ci.reuh.tk/ctrulua.png) -* Most recent working build: [here](http://thomas99.no-ip.org:3000/ctrulua/builds/latest/artifacts/ctruLua.3dsx) (warning: only tested with Citra, sometimes on real hardware). -* See http://thomas99.no-ip.org:3000/ctrulua for all the builds. +* Most recent working build: [here](http://ci.reuh.tk/ctrulua/builds/latest/artifacts/ctruLua.3dsx) (warning: only tested with Citra, sometimes on real hardware). +* See http://ci.reuh.tk/ctrulua for all the builds. #### Build instructions @@ -19,7 +19,7 @@ May not work under Windows. #### Lua API Documentation -* An online version of the documentation can be found here : http://thomas99.no-ip.org/ctrulua +* An online version of the documentation can be found here : http://reuh.tk/ctrulua * To build the documentation, run `make build-doc` (requires [LDoc](https://github.com/stevedonovan/LDoc)). #### Based on ctrulib by smealum: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib) From 9db21c7831b421105ddcb9bc6bbf8e094f058ed1 Mon Sep 17 00:00:00 2001 From: Reuh Date: Wed, 9 Mar 2016 16:13:06 +0100 Subject: [PATCH 11/46] Added gfx.scissor Note: doesn't work on citra but it does on real hardware. --- source/gfx.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/source/gfx.c b/source/gfx.c index a7e1345..9ecd1a6 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -377,6 +377,33 @@ static int gfx_getTextSize(lua_State *L) { return 1; } +/*** +Enables or disable the scissor test. +When the scissor test is enabled, the drawing area will be limited to a specific rectangle, every pixel drawn outside will be discarded. +Calls this function without argument to disable the scissor test. +@function scissor +@tparam integer x scissor rectangle origin horizontal coordinate, in pixels +@tparam integer y scissor rectangle origin vertical coordinate, in pixels +@tparam integer width scissor rectangle width, in pixels +@tparam integer height scissor rectangle height, in pixels +@tparam[opt=false] boolean invert if true the scissor will be inverted (will draw only outside of the rectangle) +*/ +static int gfx_scissor(lua_State *L) { + if (lua_gettop(L) == 0) { + sf2d_set_scissor_test(GPU_SCISSOR_DISABLE, 0, 0, 0, 0); + } else { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int width = luaL_checkinteger(L, 3); + int height = luaL_checkinteger(L, 4); + bool invert = lua_toboolean(L, 5); + + sf2d_set_scissor_test(invert ? GPU_SCISSOR_INVERT : GPU_SCISSOR_NORMAL, x, y, width, height); + } + + return 0; +} + // Functions static const struct luaL_Reg gfx_lib[] = { { "start", gfx_start }, @@ -397,6 +424,7 @@ static const struct luaL_Reg gfx_lib[] = { { "calcBoundingBox", gfx_calcBoundingBox }, { "setTextSize", gfx_setTextSize }, { "getTextSize", gfx_getTextSize }, + { "scissor", gfx_scissor }, { NULL, NULL } }; From 6a9fbbb13357ecd4296902532231e906c4e85c0c Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Sat, 12 Mar 2016 19:57:59 +0100 Subject: [PATCH 12/46] Added ":addTrustedRootCA()" to the httpc contexts, close #8, Added some values in ctr. I didn't test :addTrustedRootCA(), but it's just a simple string-to-char+size function. --- Makefile | 4 +++- sdcard/3ds/ctruLua/examples/httpc/httpc.lua | 9 ++++----- source/ctr.c | 13 +++++++++++++ source/gfx.c | 2 +- source/httpc.c | 16 ++++++++++++++++ source/ir.c | 2 +- 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index fede3ea..a766a60 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,8 @@ APP_TITLE := ctruLua APP_DESCRIPTION := Lua for the 3DS. Yes, it works. APP_AUTHOR := Reuh, Firew0lf and NegiAD ICON := icon.png +APP_VERSION := $(shell git describe --abbrev=0 --tags) +LASTCOMMIT := $(shell git rev-parse HEAD) #--------------------------------------------------------------------------------- # options for code generation @@ -48,7 +50,7 @@ CFLAGS := -g -Wall -O2 -mword-relocations -std=gnu11 \ -fomit-frame-pointer -ffast-math \ $(ARCH) -CFLAGS += $(INCLUDE) -DARM11 -D_3DS +CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DCTR_VERSION=\"$(APP_VERSION)\" -DCTR_BUILD=\"$(LASTCOMMIT)\" CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 diff --git a/sdcard/3ds/ctruLua/examples/httpc/httpc.lua b/sdcard/3ds/ctruLua/examples/httpc/httpc.lua index 7ff1ef5..2f45f22 100644 --- a/sdcard/3ds/ctruLua/examples/httpc/httpc.lua +++ b/sdcard/3ds/ctruLua/examples/httpc/httpc.lua @@ -25,19 +25,18 @@ while ctr.run() do dls = dls + 1 end - gfx.startFrame(gfx.TOP) + gfx.start(gfx.TOP) gfx.text(0, 0, data) gfx.text(0, 20, "Downloaded "..dls.." times.") - gfx.endFrame() + gfx.stop() - gfx.startFrame(gfx.BOTTOM) + gfx.start(gfx.BOTTOM) gfx.text(2, 2, "HTTP Contexts example") gfx.text(2, 20, "The data is downloaded from '"..addr.."'.") gfx.text(2, 30, "Press [B] to redownload.") - gfx.endFrame() + gfx.stop() gfx.render() end - context:close() diff --git a/source/ctr.c b/source/ctr.c index 64f55e5..c7adad4 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -189,6 +189,19 @@ int luaopen_ctr_lib(lua_State *L) { ctr_libs[i].load(L); lua_setfield(L, -2, ctr_libs[i].name); } + + /*** + Running version of ctrµLua. This string contains the exact name of the last (pre-)release tag. + @field version + */ + lua_pushstring(L, CTR_VERSION); + lua_setfield(L, -2, "version"); + /*** + Running build of ctrµLua. This string contains the last commit hash. + @field build + */ + lua_pushstring(L, CTR_BUILD); + lua_setfield(L, -2, "build"); return 1; } diff --git a/source/gfx.c b/source/gfx.c index 9ecd1a6..f356eaa 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -8,7 +8,7 @@ The `gfx` module. #include #include -#include <3ds/vram.h> +//#include <3ds/vram.h> //#include <3ds/services/gsp.h> #include diff --git a/source/httpc.c b/source/httpc.c index 1386880..5f15af7 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -218,6 +218,21 @@ static int httpc_getResponseHeader(lua_State *L) { return 1; } +/*** +Add a trusted RootCA cert to a context. +@function :addTrustedRootCA +@tparam string DER certificate +*/ +static int httpc_addTrustedRootCA(lua_State *L) { + httpcContext *context = lua_touserdata(L, 1); + u32 certsize; + u8* cert = (u8*)luaL_checklstring(L, 2, (size_t*)&certsize); + + httpcAddTrustedRootCA(context, cert, certsize); + + return 0; +} + // object static const struct luaL_Reg httpc_methods[] = { {"open", httpc_open }, @@ -229,6 +244,7 @@ static const struct luaL_Reg httpc_methods[] = { {"close", httpc_close }, {"addPostData", httpc_addPostData }, {"getResponseHeader", httpc_getResponseHeader }, + {"addTrustedRootCA", httpc_addTrustedRootCA }, {NULL, NULL} }; diff --git a/source/ir.c b/source/ir.c index 5c5864e..86d85fe 100644 --- a/source/ir.c +++ b/source/ir.c @@ -5,7 +5,7 @@ The `ir` module. */ #include <3ds/types.h> #include <3ds/services/ir.h> -#include <3ds/linear.h> +//#include <3ds/linear.h> #include #include From e5467b663d13126870598b7bd3bd2bb3648339e1 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Sat, 12 Mar 2016 23:14:42 +0100 Subject: [PATCH 13/46] Added ctr.root, close #7, changed the max path length to 1024 ctr.root is not exactly the path to ctruLua.3dsx minus the file name, but is equivalent with HBL and $ (other launchers may work too, and it works perfectly with Citra), and is "romfs:/" if romfs is enabl$ --- source/ctr.c | 16 ++++++++++++++++ source/fs.c | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/source/ctr.c b/source/ctr.c index c7adad4..db919dc 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -3,6 +3,9 @@ The `ctr` module. @module ctr @usage local ctr = require("ctr") */ +#include +#include + #include <3ds/types.h> #include <3ds/services/apt.h> #include <3ds/os.h> @@ -202,6 +205,19 @@ int luaopen_ctr_lib(lua_State *L) { */ lua_pushstring(L, CTR_BUILD); lua_setfield(L, -2, "build"); + + /*** + Root directory of ctrµLua. Contains the working directory where ctrµLua has been launched OR the romfs root if romfs has been enabled. + @field root + */ + #ifdef ROMFS + char* buff = "romfs:"; + #else + char* buff = malloc(1024); + getcwd(buff, 1024); + #endif + lua_pushstring(L, buff); + lua_setfield(L, -2, "root"); return 1; } diff --git a/source/fs.c b/source/fs.c index 68e13e3..b3e2235 100644 --- a/source/fs.c +++ b/source/fs.c @@ -40,7 +40,7 @@ const char* prefix_path(const char* path) { char* prefix = "sdmc:"; #endif - char out[256]; + char out[1024]; strcpy(out, prefix); return strcat(out, path); @@ -156,9 +156,9 @@ Get the current working directory. @treturn string the current working directory */ static int fs_getDirectory(lua_State *L) { - char cwd[256]; + char cwd[1024]; - lua_pushstring(L, getcwd(cwd, 256)); + lua_pushstring(L, getcwd(cwd, 1024)); return 1; } From 694159f444e5bbf5ec46b7dba1b9d07d7e67170f Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Sun, 13 Mar 2016 21:30:14 +0100 Subject: [PATCH 14/46] Updated LSH to use the new constants Doesn't change much anyway. --- sdcard/3ds/ctruLua/libs/filepicker.lua | 2 +- sdcard/3ds/ctruLua/main.lua | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua index 7a017cf..e2b954a 100644 --- a/sdcard/3ds/ctruLua/libs/filepicker.lua +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -8,7 +8,7 @@ local function saveGraphicsState() local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), gfx.font.getDefault()} - local mono = gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf") + local mono = gfx.font.load(ctr.root .. "resources/VeraMono.ttf") gfx.set3D(false) gfx.color.setDefault(0xFFFDFDFD) diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 705397a..44c840e 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -2,14 +2,8 @@ local ctr = require("ctr") local fs = require("ctr.fs") local gfx = require("ctr.gfx") --- Initializing "constants" -ctruLua = {} - ---- The ctruLua root directory's absolute path -ctruLua.root = fs.getDirectory() - -- Set up path -local ldir = fs.getDirectory().."libs/" +local ldir = ctr.root.."libs/" package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua" -- Erroring @@ -17,7 +11,7 @@ local function displayError(err) gfx.set3D(false) gfx.color.setBackground(0xFF0000B3) gfx.color.setDefault(0xFFFDFDFD) - gfx.font.setDefault(gfx.font.load(ctruLua.root .. "resources/VeraMono.ttf")) + gfx.font.setDefault(gfx.font.load(ctr.root .. "resources/VeraMono.ttf")) while ctr.run() do gfx.start(gfx.TOP) From 5107f0277ce7754eaa97ae9d223704b9e177aebe Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Tue, 22 Mar 2016 21:48:52 +0100 Subject: [PATCH 15/46] Updated sf2dlib, added a "thickness" argument to lines (warning: break some old lines), Added WIP render targets, Added arguments (main file and root path) You'll need to update some of your codes if you used lines with colors, or you'll have some ... Nice line effects. --- libs/sf2dlib/.gitignore | 9 +- libs/sf2dlib/libsf2d/Makefile | 3 + libs/sf2dlib/libsf2d/include/sf2d.h | 67 ++++++++- libs/sf2dlib/libsf2d/source/sf2d.c | 92 ++++++++++--- libs/sf2dlib/libsf2d/source/sf2d_draw.c | 31 ++++- libs/sf2dlib/libsf2d/source/sf2d_private.c | 4 + libs/sf2dlib/libsf2d/source/sf2d_texture.c | 110 +++++++++++++++ sdcard/3ds/ctruLua/examples/example.lua | 4 +- source/ctr.c | 2 +- source/gfx.c | 149 ++++++++++++++++++++- source/httpc.c | 11 +- source/main.c | 36 ++++- 12 files changed, 477 insertions(+), 41 deletions(-) diff --git a/libs/sf2dlib/.gitignore b/libs/sf2dlib/.gitignore index e1071b6..a020760 100644 --- a/libs/sf2dlib/.gitignore +++ b/libs/sf2dlib/.gitignore @@ -1,2 +1,7 @@ -libsf2d/build/ -libsf2d/lib/ \ No newline at end of file +*.d +*.o +*.project +*.cproject +libsf2d/.settings/* +libsf2d/build/* +libsf2d/lib/* diff --git a/libs/sf2dlib/libsf2d/Makefile b/libs/sf2dlib/libsf2d/Makefile index 14e1112..28ccec1 100644 --- a/libs/sf2dlib/libsf2d/Makefile +++ b/libs/sf2dlib/libsf2d/Makefile @@ -30,6 +30,7 @@ CFLAGS := -g -Wall -O2\ $(ARCH) CFLAGS += $(INCLUDE) -DARM11 -D_3DS +#CFLAGS += -std=c11 #WILL HAVE TO BE REMOVED SOON CFLAGS += -DLIBCTRU_NO_DEPRECATION @@ -138,6 +139,8 @@ $(OUTPUT) : $(OFILES) @echo "extern const u8" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(notdir $<).shbin | tr . _)`.h @echo "extern const u32" `(echo $(notdir $<).shbin | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(notdir $<).shbin | tr . _)`.h +sf2d.c: shader.vsh + -include $(DEPENDS) #--------------------------------------------------------------------------------------- diff --git a/libs/sf2dlib/libsf2d/include/sf2d.h b/libs/sf2dlib/libsf2d/include/sf2d.h index 6385b83..f282d4f 100644 --- a/libs/sf2dlib/libsf2d/include/sf2d.h +++ b/libs/sf2dlib/libsf2d/include/sf2d.h @@ -4,7 +4,6 @@ * @date 22 March 2015 * @brief sf2dlib header */ - #ifndef SF2D_H #define SF2D_H @@ -136,6 +135,11 @@ typedef struct { void *data; /**< Pointer to the data */ } sf2d_texture; +typedef struct { + sf2d_texture texture; // "inherit"/extend standard texture + float projection[4*4]; /**< Orthographic projection matrix for this target */ +} sf2d_rendertarget; + // Basic functions /** @@ -171,6 +175,12 @@ void sf2d_set_3D(int enable); */ void sf2d_start_frame(gfxScreen_t screen, gfx3dSide_t side); +/** + * @brief Starts a frame bound to a rendertarget + * @param target rendertarget to draw to + */ +void sf2d_start_frame_target(sf2d_rendertarget *target); + /** * @brief Ends a frame, should be called on pair with sf2d_start_frame */ @@ -239,9 +249,10 @@ void sf2d_set_clear_color(u32 color); * @param y0 y coordinate of the first dot * @param x1 x coordinate of the second dot * @param y1 y coordinate of the sceond dot + * @param width thickness of the line * @param color the color to draw the line */ -void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color); + void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 color); /** * @brief Draws a rectangle @@ -292,12 +303,37 @@ void sf2d_draw_fill_circle(int x, int y, int radius, u32 color); */ sf2d_texture *sf2d_create_texture(int width, int height, sf2d_texfmt pixel_format, sf2d_place place); +/** + * @brief Creates an empty rendertarget. + * Functions similarly to sf2d_create_texture. + * @param width the width of the texture + * @param height the height of the texture + * @return a pointer to the newly created rendertarget + * @note Before drawing the texture, it needs to be tiled + * by calling sf2d_texture_tile32. + * The default texture params are both min and mag filters + * GPU_NEAREST, and both S and T wrappings GPU_CLAMP_TO_BORDER. + */ +sf2d_rendertarget *sf2d_create_rendertarget(int width, int height); + /** * @brief Frees a texture * @param texture pointer to the texture to freeze */ void sf2d_free_texture(sf2d_texture *texture); +/** + * @brief Frees a rendertarget + * @param target pointer to the rendertarget to free + */ +void sf2d_free_target(sf2d_rendertarget *target); + +/** + * @brief Clears a rendertarget to the specified color + * @param target pointer to the rendertarget to clear + */ +void sf2d_clear_target(sf2d_rendertarget *target, u32 color); + /** * @brief Fills an already allocated texture from a RGBA8 source * @param dst pointer to the destination texture to fill @@ -419,6 +455,33 @@ void sf2d_draw_texture_rotate(const sf2d_texture *texture, int x, int y, float r */ void sf2d_draw_texture_rotate_blend(const sf2d_texture *texture, int x, int y, float rad, u32 color); +/** + * @brief Draws a scaled texture with rotation around its hotspot + * @param texture the texture to draw + * @param x the x coordinate to draw the texture to + * @param y the y coordinate to draw the texture to + * @param rad rotation (in radians) to draw the texture + * @param x_scale the x scale + * @param y_scale the y scale + * @param center_x the x position of the hotspot + * @param center_y the y position of the hotspot + */ +void sf2d_draw_texture_rotate_scale_hotspot(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y); + +/** + * @brief Draws a scaled texture with rotation around its hotspot with color + * @param texture the texture to draw + * @param x the x coordinate to draw the texture to + * @param y the y coordinate to draw the texture to + * @param rad rotation (in radians) to draw the texture + * @param x_scale the x scale + * @param y_scale the y scale + * @param center_x the x position of the hotspot + * @param center_y the y position of the hotspot + * @param color the color to blend with the texture + */ +void sf2d_draw_texture_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y, u32 color); + /** * @brief Draws a part of a texture * @param texture the texture to draw diff --git a/libs/sf2dlib/libsf2d/source/sf2d.c b/libs/sf2dlib/libsf2d/source/sf2d.c index be9e4aa..0b76e62 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d.c +++ b/libs/sf2dlib/libsf2d/source/sf2d.c @@ -1,3 +1,4 @@ +#include #include "sf2d.h" #include "sf2d_private.h" #include "shader_vsh_shbin.h" @@ -32,6 +33,10 @@ static u32 projection_desc = -1; //Matrix static float ortho_matrix_top[4*4]; static float ortho_matrix_bot[4*4]; +//Rendertarget things +static sf2d_rendertarget * currentRenderTarget = NULL; +static void * targetDepthBuffer; +static int targetDepthBufferLen = 0; //Apt hook cookie static aptHookCookie apt_hook_cookie; //Functions @@ -111,6 +116,7 @@ int sf2d_fini() linearFree(gpu_cmd); vramFree(gpu_fb_addr); vramFree(gpu_depth_fb_addr); + linearFree(targetDepthBuffer); sf2d_initialized = 0; @@ -173,6 +179,53 @@ void sf2d_start_frame(gfxScreen_t screen, gfx3dSide_t side) GPU_SetDummyTexEnv(5); } +void sf2d_start_frame_target(sf2d_rendertarget *target) +{ + sf2d_pool_reset(); + GPUCMD_SetBufferOffset(0); + + // Upload saved uniform + matrix_gpu_set_uniform(target->projection, projection_desc); + + int bufferLen = target->texture.width * target->texture.height * 4; // apparently depth buffer is (or can be) 32bit? + if (bufferLen > targetDepthBufferLen) { // expand depth buffer + if (targetDepthBufferLen > 0) linearFree(targetDepthBuffer); + targetDepthBuffer = linearAlloc(bufferLen); + memset(targetDepthBuffer, 0, bufferLen); + targetDepthBufferLen = bufferLen; + } + + GPU_SetViewport((u32 *)osConvertVirtToPhys(targetDepthBuffer), + (u32 *)osConvertVirtToPhys(target->texture.data), + 0, 0, target->texture.height, target->texture.width); + + currentRenderTarget = target; + + GPU_DepthMap(-1.0f, 0.0f); + GPU_SetFaceCulling(GPU_CULL_NONE); + GPU_SetStencilTest(false, GPU_ALWAYS, 0x00, 0xFF, 0x00); + GPU_SetStencilOp(GPU_STENCIL_KEEP, GPU_STENCIL_KEEP, GPU_STENCIL_KEEP); + GPU_SetBlendingColor(0,0,0,0); + GPU_SetDepthTestAndWriteMask(true, GPU_GEQUAL, GPU_WRITE_ALL); + GPUCMD_AddMaskedWrite(GPUREG_EARLYDEPTH_TEST1, 0x1, 0); + GPUCMD_AddWrite(GPUREG_EARLYDEPTH_TEST2, 0); + + GPU_SetAlphaBlending( + GPU_BLEND_ADD, + GPU_BLEND_ADD, + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, + GPU_ONE, GPU_ZERO + ); + + GPU_SetAlphaTest(false, GPU_ALWAYS, 0x00); + + GPU_SetDummyTexEnv(1); + GPU_SetDummyTexEnv(2); + GPU_SetDummyTexEnv(3); + GPU_SetDummyTexEnv(4); + GPU_SetDummyTexEnv(5); +} + void sf2d_end_frame() { GPU_FinishDrawing(); @@ -180,23 +233,30 @@ void sf2d_end_frame() GPUCMD_FlushAndRun(); gspWaitForP3D(); - //Copy the GPU rendered FB to the screen FB - if (cur_screen == GFX_TOP) { - GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 400), - (u32 *)gfxGetFramebuffer(GFX_TOP, cur_side, NULL, NULL), - GX_BUFFER_DIM(240, 400), 0x1000); - } else { - GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 320), - (u32 *)gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), - GX_BUFFER_DIM(240, 320), 0x1000); - } - gspWaitForPPF(); + if (!currentRenderTarget) { + //Copy the GPU rendered FB to the screen FB + if (cur_screen == GFX_TOP) { + GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 400), + (u32 *)gfxGetFramebuffer(GFX_TOP, cur_side, NULL, NULL), + GX_BUFFER_DIM(240, 400), 0x1000); + } else { + GX_DisplayTransfer(gpu_fb_addr, GX_BUFFER_DIM(240, 320), + (u32 *)gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), + GX_BUFFER_DIM(240, 320), 0x1000); + } + gspWaitForPPF(); - //Clear the screen - GX_MemoryFill( - gpu_fb_addr, clear_color, &gpu_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH, - gpu_depth_fb_addr, 0, &gpu_depth_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH); - gspWaitForPSC0(); + //Clear the screen + GX_MemoryFill( + gpu_fb_addr, clear_color, &gpu_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH, + gpu_depth_fb_addr, 0, &gpu_depth_fb_addr[240*400], GX_FILL_TRIGGER | GX_FILL_32BIT_DEPTH); + gspWaitForPSC0(); + } else { + //gspWaitForPPF(); + //gspWaitForPSC0(); + sf2d_texture_tile32(&(currentRenderTarget->texture)); + } + currentRenderTarget = NULL; } void sf2d_swapbuffers() diff --git a/libs/sf2dlib/libsf2d/source/sf2d_draw.c b/libs/sf2dlib/libsf2d/source/sf2d_draw.c index 28ef34b..59a792e 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_draw.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_draw.c @@ -2,15 +2,36 @@ #include "sf2d_private.h" #include -void sf2d_draw_line(int x0, int y0, int x1, int y1, u32 color) +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 color) { sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8); if (!vertices) return; - vertices[0].position = (sf2d_vector_3f){(float)x0+1.0f, (float)y0+1.0f, SF2D_DEFAULT_DEPTH}; - vertices[1].position = (sf2d_vector_3f){(float)x0-1.0f, (float)y0-1.0f, SF2D_DEFAULT_DEPTH}; - vertices[2].position = (sf2d_vector_3f){(float)x1+1.0f, (float)y1+1.0f, SF2D_DEFAULT_DEPTH}; - vertices[3].position = (sf2d_vector_3f){(float)x1-1.0f, (float)y1-1.0f, SF2D_DEFAULT_DEPTH}; + float dx = x1 - x0; + float dy = y1 - y0; + + float nx = -dy; + float ny = dx; + + float len = sqrt(nx * nx + ny * ny); + + if (len > 0 ){ + nx /= len; + ny /= len; + } + + nx *= width*0.5f; + ny *= width*0.5f; + + vertices[0].position = (sf2d_vector_3f){x0+nx, y0+ny, SF2D_DEFAULT_DEPTH}; + vertices[1].position = (sf2d_vector_3f){x0-nx, y0-ny, SF2D_DEFAULT_DEPTH}; + + vertices[2].position = (sf2d_vector_3f){x1+nx, y1+ny, SF2D_DEFAULT_DEPTH}; + vertices[3].position = (sf2d_vector_3f){x1-nx, y1-ny, SF2D_DEFAULT_DEPTH}; vertices[0].color = color; vertices[1].color = vertices[0].color; diff --git a/libs/sf2dlib/libsf2d/source/sf2d_private.c b/libs/sf2dlib/libsf2d/source/sf2d_private.c index 073c437..30b0ed8 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_private.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_private.c @@ -2,6 +2,10 @@ #include #include "sf2d_private.h" +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + //stolen from staplebutt void GPU_SetDummyTexEnv(u8 num) { diff --git a/libs/sf2dlib/libsf2d/source/sf2d_texture.c b/libs/sf2dlib/libsf2d/source/sf2d_texture.c index df314a9..13345e9 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_texture.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_texture.c @@ -4,6 +4,10 @@ #include "sf2d.h" #include "sf2d_private.h" +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + #define TEX_MIN_SIZE 8 static unsigned int nibbles_per_pixel(sf2d_texfmt format) @@ -93,6 +97,22 @@ sf2d_texture *sf2d_create_texture(int width, int height, sf2d_texfmt pixel_forma return texture; } +sf2d_rendertarget *sf2d_create_rendertarget(int width, int height) +{ + sf2d_texture *tx = sf2d_create_texture(width, height, TEXFMT_RGBA8, SF2D_PLACE_RAM); + sf2d_rendertarget *rt = malloc(sizeof(*rt)); + //memcpy(rt, tx, sizeof(*tx)); + rt->texture = *tx; + free(tx); + //tx = * rt->texture; + //rt->projection + + matrix_init_orthographic(rt->projection, 0.0f, width, height, 0.0f, 0.0f, 1.0f); + matrix_rotate_z(rt->projection, M_PI / 2.0f); + + return rt; +} + void sf2d_free_texture(sf2d_texture *texture) { if (texture) { @@ -105,6 +125,27 @@ void sf2d_free_texture(sf2d_texture *texture) } } +void sf2d_free_target(sf2d_rendertarget *target) +{ + sf2d_free_texture(&(target->texture)); + //free(target); // unnecessary since the texture is the start of the target struct +} + +void sf2d_clear_target(sf2d_rendertarget *target, u32 color) { + if (color == 0) { // if fully transparent, take a shortcut + memset(target->texture.data, 0, target->texture.width * target->texture.height * 4); + sf2d_texture_tile32(&(target->texture)); + return; + } + + color = ((color>>24)&0x000000FF) | ((color>>8)&0x0000FF00) | ((color<<8)&0x00FF0000) | ((color<<24)&0xFF000000); // reverse byte order + + int itarget = target->texture.width * target->texture.height; + for (int i = 0; i < itarget; i++) { memcpy(target->texture.data + i*4, &color, 4); } + + sf2d_texture_tile32(&(target->texture)); +} + void sf2d_fill_texture_from_RGBA8(sf2d_texture *dst, const void *rgba8, int source_w, int source_h) { // TODO: add support for non-RGBA8 textures @@ -344,6 +385,75 @@ void sf2d_draw_texture_rotate_blend(const sf2d_texture *texture, int x, int y, f color); } +static inline void sf2d_draw_texture_rotate_scale_hotspot_generic(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y) +{ + sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8); + if (!vertices) return; + + const float w = texture->width; + const float h = texture->height; + + vertices[0].position.x = -center_x * scale_x; + vertices[0].position.y = -center_y * scale_y; + vertices[0].position.z = SF2D_DEFAULT_DEPTH; + + vertices[1].position.x = (w - center_x) * scale_x; + vertices[1].position.y = -center_y * scale_y; + vertices[1].position.z = SF2D_DEFAULT_DEPTH; + + vertices[2].position.x = -center_x * scale_x; + vertices[2].position.y = (h - center_y) * scale_y; + vertices[2].position.z = SF2D_DEFAULT_DEPTH; + + vertices[3].position.x = (w - center_x) * scale_x; + vertices[3].position.y = h - center_y * scale_y; + vertices[3].position.z = SF2D_DEFAULT_DEPTH; + + float u = w/(float)texture->pow2_w; + float v = h/(float)texture->pow2_h; + + vertices[0].texcoord = (sf2d_vector_2f){0.0f, 0.0f}; + vertices[1].texcoord = (sf2d_vector_2f){u, 0.0f}; + vertices[2].texcoord = (sf2d_vector_2f){0.0f, v}; + vertices[3].texcoord = (sf2d_vector_2f){u, v}; + + const float c = cosf(rad); + const float s = sinf(rad); + int i; + for (i = 0; i < 4; ++i) { // Rotate and translate + float _x = vertices[i].position.x; + float _y = vertices[i].position.y; + vertices[i].position.x = _x*c - _y*s + x; + vertices[i].position.y = _x*s + _y*c + y; + } + + GPU_SetAttributeBuffers( + 2, // number of attributes + (u32*)osConvertVirtToPhys(vertices), + GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 2, GPU_FLOAT), + 0xFFFC, //0b1100 + 0x10, + 1, //number of buffers + (u32[]){0x0}, // buffer offsets (placeholders) + (u64[]){0x10}, // attribute permutations for each buffer + (u8[]){2} // number of attributes for each buffer + ); + + GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4); +} + +void sf2d_draw_texture_rotate_scale_hotspot(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y) +{ + sf2d_bind_texture(texture, GPU_TEXUNIT0); + sf2d_draw_texture_rotate_scale_hotspot_generic(texture, x, y, rad, scale_x, scale_y, center_x, center_y); +} + +void sf2d_draw_texture_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, float scale_x, float scale_y, float center_x, float center_y, u32 color) +{ + sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color); + sf2d_draw_texture_rotate_scale_hotspot_generic(texture, x, y, rad, scale_x, scale_y, center_x, center_y); +} + static inline void sf2d_draw_texture_part_generic(const sf2d_texture *texture, int x, int y, int tex_x, int tex_y, int tex_w, int tex_h) { sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8); diff --git a/sdcard/3ds/ctruLua/examples/example.lua b/sdcard/3ds/ctruLua/examples/example.lua index b85c187..1c2a307 100644 --- a/sdcard/3ds/ctruLua/examples/example.lua +++ b/sdcard/3ds/ctruLua/examples/example.lua @@ -8,7 +8,7 @@ local dMul = 1 local angle = 0 -local texture1 = gfx.texture.load("sdmc:/3ds/ctruLua/icon.png"); +local texture1 = gfx.texture.load(ctr.root.."icon.png"); if not texture1 then error("Giants ducks came from another planet") end gfx.color.setBackground(gfx.color.RGBA8(200, 200, 200)) @@ -29,7 +29,7 @@ local function drawStuffIn3D(eye) gfx.color.setDefault(0xFF0000FF) gfx.rectangle(x + d(10*math.sin(ctr.time()/500)), y, 20, 20, angle) - gfx.line(50 + d(-6), 50, 75 + d(4), 96, gfx.color.RGBA8(52, 10, 65)) + gfx.line(50 + d(-6), 50, 75 + d(4), 96, 1, gfx.color.RGBA8(52, 10, 65)) gfx.circle(125 + d(-8), 125, 16) end diff --git a/source/ctr.c b/source/ctr.c index db919dc..49c93ba 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -182,7 +182,7 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } { "apt", load_apt_lib, NULL }, { "mic", load_mic_lib, NULL }, { "thread", load_thread_lib, NULL }, - { NULL, NULL } + { NULL, NULL, NULL } }; int luaopen_ctr_lib(lua_State *L) { diff --git a/source/gfx.c b/source/gfx.c index f356eaa..23a5faf 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -4,6 +4,8 @@ The `gfx` module. @usage local gfx = require("ctr.gfx") */ #include +#include +#include #include #include @@ -15,6 +17,11 @@ The `gfx` module. #include #include "font.h" +#include "texture.h" + +typedef struct { + sf2d_rendertarget *target; +} target_userdata; bool isGfxInitialized = false; bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib. @@ -51,17 +58,23 @@ The `ctr.gfx.map` module. void load_map_lib(lua_State *L); /*** -Start drawing to a screen. +Start drawing to a screen/target. Must be called before any draw operation. @function start -@tparam number screen the screen to draw to (`gfx.TOP` or `gfx.BOTTOM`) +@tparam number/target screen the screen or target to draw to (`gfx.TOP`, `gfx.BOTTOM`, or render target) @tparam[opt=gfx.LEFT] number eye the eye to draw to (`gfx.LEFT` or `gfx.RIGHT`) */ static int gfx_start(lua_State *L) { - u8 screen = luaL_checkinteger(L, 1); - u8 eye = luaL_optinteger(L, 2, GFX_LEFT); + if (lua_isinteger(L, 1)) { + u8 screen = luaL_checkinteger(L, 1); + u8 eye = luaL_optinteger(L, 2, GFX_LEFT); - sf2d_start_frame(screen, eye); + sf2d_start_frame(screen, eye); + } else if (lua_isuserdata(L, 1)) { + target_userdata *target = luaL_checkudata(L, 1, "LTarget"); + + sf2d_start_frame_target(target->target); + } return 0; } @@ -170,6 +183,7 @@ Draw a line on the current screen. @tparam integer y1 line's starting point vertical coordinate, in pixels @tparam integer x2 line's endpoint horizontal coordinate, in pixels @tparam integer y2 line's endpoint vertical coordinate, in pixels +@tparam[opt=1] number width line's thickness, in pixels @tparam[opt=default color] integer color drawing color */ static int gfx_line(lua_State *L) { @@ -177,10 +191,11 @@ static int gfx_line(lua_State *L) { int y1 = luaL_checkinteger(L, 2); int x2 = luaL_checkinteger(L, 3); int y2 = luaL_checkinteger(L, 4); + float width = luaL_optnumber(L, 5, 1.0f); - u32 color = luaL_optinteger(L, 5, color_default); + u32 color = luaL_optinteger(L, 6, color_default); - sf2d_draw_line(x1, y1, x2, y2, color); + sf2d_draw_line(x1, y1, x2, y2, width, color); return 0; } @@ -404,6 +419,111 @@ static int gfx_scissor(lua_State *L) { return 0; } +/*** +__Work in progress__. Create a render target. Don't use it. +@function target +@tparam integer width +@tparam integer height +@treturn target +*/ +static int gfx_target(lua_State *L) { + int width = luaL_checkinteger(L, 1); + int height = luaL_checkinteger(L, 2); + int wpo2 = 0, hpo2 = 0; + for (;width>pow(2,wpo2);wpo2++); + width = pow(2,wpo2); + for (;height>pow(2,hpo2);hpo2++); + height = pow(2,hpo2); + + target_userdata *target; + target = (target_userdata*)lua_newuserdata(L, sizeof(*target)); + + luaL_getmetatable(L, "LTarget"); + lua_setmetatable(L, -2); + + target->target = sf2d_create_rendertarget(width, height); + + return 1; +} + +/*** +Render targets +@section target +*/ + +/*** +Clear a target to a specified color. +@function :clear +@tparam[opt=default color] integer color color to fill the target with +*/ +static int gfx_target_clear(lua_State *L) { + target_userdata *target = luaL_checkudata(L, 1, "LTarget"); + u32 color = luaL_optinteger(L, 2, color_default); + + sf2d_clear_target(target->target, color); + + return 0; +} + +/*** +Destroy a target. +@function :destroy +*/ +static int gfx_target_destroy(lua_State *L) { + target_userdata *target = luaL_checkudata(L, 1, "LTarget"); + + sf2d_free_target(target->target); + + return 0; +} + +static const struct luaL_Reg target_methods[]; +/*** + +*/ +static int gfx_target___index(lua_State *L) { + target_userdata *target = luaL_checkudata(L, 1, "LTarget"); + const char* name = luaL_checkstring(L, 2); + + if (strcmp(name, "texture") == 0) { + texture_userdata *texture; + texture = (texture_userdata*)lua_newuserdata(L, sizeof(*texture)); + luaL_getmetatable(L, "LTexture"); + lua_setmetatable(L, -2); + + texture->texture = &(target->target->texture); + texture->scaleX = 1.0f; + texture->scaleY = 1.0f; + texture->blendColor = 0xffffffff; + + return 1; + } else if (strcmp(name, "duck") == 0) { + sf2d_rendertarget *target = sf2d_create_rendertarget(64, 64); + for(int i=0;;i++) { + sf2d_clear_target(target, 0xff000000); + sf2d_start_frame_target(target); + sf2d_draw_fill_circle(i%380, i%200, 10, 0xff0000ff); + sf2d_end_frame(); + //sf2d_texture_tile32(&target->texture); + + sf2d_start_frame(GFX_TOP, GFX_LEFT); + sf2d_draw_texture(&target->texture, 10, 10); + sf2d_end_frame(); + sf2d_swapbuffers(); + } + } else { + for (u8 i=0;target_methods[i].name;i++) { + if (strcmp(target_methods[i].name, name) == 0) { + lua_pushcfunction(L, target_methods[i].func); + return 1; + } + } + } + + lua_pushnil(L); + return 1; +} + // Functions static const struct luaL_Reg gfx_lib[] = { { "start", gfx_start }, @@ -425,6 +545,16 @@ static const struct luaL_Reg gfx_lib[] = { { "setTextSize", gfx_setTextSize }, { "getTextSize", gfx_getTextSize }, { "scissor", gfx_scissor }, + { "target", gfx_target }, + { NULL, NULL } +}; + +// Render target +static const struct luaL_Reg target_methods[] = { + { "__index", gfx_target___index }, + {"clear", gfx_target_clear }, + {"destroy", gfx_target_destroy }, + {"__gc", gfx_target_destroy }, { NULL, NULL } }; @@ -491,6 +621,11 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } }; int luaopen_gfx_lib(lua_State *L) { + luaL_newmetatable(L, "LTarget"); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, target_methods, 0); + luaL_newlib(L, gfx_lib); for (int i = 0; gfx_constants[i].name; i++) { diff --git a/source/httpc.c b/source/httpc.c index 5f15af7..1df43bd 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -228,9 +228,15 @@ static int httpc_addTrustedRootCA(lua_State *L) { u32 certsize; u8* cert = (u8*)luaL_checklstring(L, 2, (size_t*)&certsize); - httpcAddTrustedRootCA(context, cert, certsize); + Result ret = httpcAddTrustedRootCA(context, cert, certsize); + if (ret != 0) { + lua_pushnil(L); + lua_pushinteger(L, ret); + return 2; + } - return 0; + lua_pushboolean(L, true); + return 1; } // object @@ -242,6 +248,7 @@ static const struct luaL_Reg httpc_methods[] = { {"getDownloadSize", httpc_getDownloadSize }, {"downloadData", httpc_downloadData }, {"close", httpc_close }, + {"__gc", httpc_close }, {"addPostData", httpc_addPostData }, {"getResponseHeader", httpc_getResponseHeader }, {"addTrustedRootCA", httpc_addTrustedRootCA }, diff --git a/source/main.c b/source/main.c index 270c655..d106898 100644 --- a/source/main.c +++ b/source/main.c @@ -1,3 +1,5 @@ +#include + #include <3ds.h> #include @@ -32,7 +34,32 @@ void error(const char *error) { } // Main loop -int main() { +int main(int argc, char** argv) { + // Default arguments + char* mainFile = "main.lua"; + + // Parse arguments + for (int i=0;i Date: Wed, 23 Mar 2016 19:50:10 +0100 Subject: [PATCH 16/46] Added streaming support to ctr.audio Currently runs in the main thread because I can't get it to work on another thread. You will need to call audio.update() each frame to make audio streaming work. --- source/audio.c | 272 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 249 insertions(+), 23 deletions(-) diff --git a/source/audio.c b/source/audio.c index 2222999..9308cfe 100644 --- a/source/audio.c +++ b/source/audio.c @@ -11,6 +11,7 @@ There are 24 audio channels available, numbered from 0 to 23. #include #include #include +#include #include #include @@ -22,30 +23,78 @@ There are 24 audio channels available, numbered from 0 to 23. typedef enum { TYPE_UNKNOWN = -1, TYPE_OGG = 0, - TYPE_WAV = 1 + TYPE_WAV = 1, + TYPE_RAW = 2 } filetype; // Audio object userdata typedef struct { filetype type; // file type - // OGG Vorbis specific - OggVorbis_File vf; // ogg vorbis file + // File type specific + union { + // OGG Vorbis + struct { + OggVorbis_File vf; + int currentSection; // section and position at the end of the initial data + long rawPosition; + }; + // WAV + struct { + FILE* file; + long fileSize; + long filePosition; // position at the end of the initial data + }; + }; // Needed for playback float rate; // sample rate (per channel) (Hz) u32 channels; // channel count u32 encoding; // data encoding (NDSP_ENCODING_*) + + // Initial data u32 nsamples; // numbers of samples in the audio (per channel, not the total) u32 size; // number of bytes in the audio (total, ie data size) char* data; // raw audio data + // Other useful data + u16 bytePerSample; // bytes per sample (warning: undefined for ADPCM (only for raw data)) + u32 chunkSize; // size per chunk (for streaming) + u32 chunkNsamples; // number of samples per chunk + // Playing parameters (type-independant) float mix[12]; // mix parameters ndspInterpType interp; // interpolation type double speed; // playing speed } audio_userdata; +// Audio stream instance struct (when an audio is played; only used when streaming) +typedef struct { + audio_userdata* audio; + + // Current position information + union { + // OGG + struct { + int currentSection; + long rawPosition; + }; + // WAV + long filePosition; + }; + + double prevStartTime; // audio time when last chunk started playing + bool eof; // if reached end of file + bool done; // if streaming ended and the stream will be skipped on the next update + // (the struct should be keept in memory until replaced or it will break audio:time()) + + char* nextData; // the next data to play + ndspWaveBuf* nextWaveBuf; + + char* prevData; // the data actually playing + ndspWaveBuf* prevWaveBuf; +} audio_stream; + // Indicate if NDSP was initialized or not. // NDSP doesn't work on citra yet. // Please only throw an error related to this when using a ndsp function, so other parts of the @@ -55,12 +104,24 @@ bool isAudioInitialized = false; // Array of the last audio_userdata sent to each channel; channels range from 0 to 23 audio_userdata* channels[24]; +// Array of the audio_instance that needs to be updated when calling audio.update (indexed per channel). +audio_stream* streaming[24]; + /*** Load an audio file. OGG Vorbis and PCM WAV file format are currently supported. (Most WAV files use the PCM encoding). +NOTE: audio streaming doesn't use threading for now, this means that the decoding will be done on the main thread. +It should work fine with WAVs, but with OGG files you may suffer slowdowns when a new chunk of data is decoded. +To avoid that, you can either reduce the chunkDuration or disable streaming, but be careful if you do so, audio files +can fill the memory really quickly. @function load -@tparam string path path to the file +@tparam string path path to the file or the data if type is raw +@tparam[opt=0.1] number chunkDuration if set to -1, streaming will be disabled (all data is loaded in memory at once) + Other values are the stream chunk duration in seconds (ctrµLua will load + the audio per chunk of x seconds). Note that you need to call audio.update() each + frame in order for ctµLua to load new data from audio streams. Two chunks of data + will be loaded at the same time at most (one playing, the other ready to be played). @tparam[opt=detect] string type file type, `"ogg"` or `"wav"`. If set to `"detect"`, will try to deduce the type from the filename. @treturn[1] audio the loaded audio object @@ -69,7 +130,8 @@ OGG Vorbis and PCM WAV file format are currently supported. */ static int audio_load(lua_State *L) { const char *path = luaL_checkstring(L, 1); - const char* argType = luaL_optstring(L, 2, "detect"); + double streamChunk = luaL_optnumber(L, 2, 0.1); + const char* argType = luaL_optstring(L, 3, "detect"); // Create userdata audio_userdata *audio = lua_newuserdata(L, sizeof(*audio)); @@ -91,6 +153,8 @@ static int audio_load(lua_State *L) { type = TYPE_OGG; } else if (strcmp(argType, "wav") == 0) { type = TYPE_WAV; + } else if (strcmp(argType, "raw") == 0) { + type = TYPE_RAW; } // Open and read file @@ -114,16 +178,26 @@ static int audio_load(lua_State *L) { audio->encoding = NDSP_ENCODING_PCM16; audio->nsamples = ov_pcm_total(&audio->vf, -1); audio->size = audio->nsamples * audio->channels * 2; // *2 because output is PCM16 (2 bytes/sample) + audio->bytePerSample = 2; - if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available"); - audio->data = linearAlloc(audio->size); + // Streaming + if (streamChunk < 0) { + audio->chunkNsamples = audio->nsamples; + audio->chunkSize = audio->size; + } else { + audio->chunkNsamples = round(streamChunk * audio->rate); + audio->chunkSize = audio->chunkNsamples * audio->channels * 2; + } + + // Allocate + if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available"); + audio->data = linearAlloc(audio->chunkSize); // Decoding loop int offset = 0; int eof = 0; - int current_section; - while (!eof) { - long ret = ov_read(&audio->vf, &audio->data[offset], 4096, ¤t_section); + while (!eof && offset < audio->chunkSize) { + long ret = ov_read(&audio->vf, &audio->data[offset], fmin(audio->chunkSize - offset, 4096), &audio->currentSection); if (ret == 0) { eof = 1; @@ -137,6 +211,7 @@ static int audio_load(lua_State *L) { offset += ret; } } + audio->rawPosition = ov_raw_tell(&audio->vf); return 1; @@ -222,13 +297,29 @@ static int audio_load(lua_State *L) { return 0; } + audio->bytePerSample = byte_per_sample / audio->channels; + + // Streaming + if (streamChunk < 0) { + audio->chunkNsamples = audio->nsamples; + audio->chunkSize = audio->size; + } else { + audio->chunkNsamples = round(streamChunk * audio->rate); + audio->chunkSize = audio->chunkNsamples * audio->channels * audio->bytePerSample; + } + // Read data - if (linearSpaceFree() < audio->size) luaL_error(L, "not enough linear memory available"); - audio->data = linearAlloc(audio->size); + if (linearSpaceFree() < audio->chunkSize) luaL_error(L, "not enough linear memory available"); + audio->data = linearAlloc(audio->chunkSize); - fread(audio->data, audio->size, 1, file); + fread(audio->data, audio->chunkSize, 1, file); + + audio->file = file; + audio->filePosition = ftell(file); + + fseek(file, 0, SEEK_END); + audio->fileSize = ftell(file); - fclose(file); return 1; } else { @@ -244,6 +335,7 @@ static int audio_load(lua_State *L) { /*** Load raw audio data from a string. +No streaming. @function loadRaw @tparam string data raw audio data @tparam number rate sampling rate @@ -264,16 +356,18 @@ static int audio_loadRaw(lua_State *L) { luaL_getmetatable(L, "LAudio"); lua_setmetatable(L, -2); - audio->type = TYPE_WAV; + audio->type = TYPE_RAW; audio->rate = rate; audio->channels = channels; u8 sampleSize = 2; // default to 2 if (strcmp(argEncoding, "PCM8")) { audio->encoding = NDSP_ENCODING_PCM8; + audio->bytePerSample = 1; sampleSize = 1; } else if (strcmp(argEncoding, "PCM16")) { audio->encoding = NDSP_ENCODING_PCM16; + audio->bytePerSample = 2; } else if (strcmp(argEncoding, "ADPCM")) { audio->encoding = NDSP_ENCODING_ADPCM; } else { @@ -285,6 +379,9 @@ static int audio_loadRaw(lua_State *L) { audio->nsamples = dataSize/sampleSize; audio->size = dataSize; audio->data = data; + + audio->chunkSize = audio->size; + audio->chunkNsamples = audio->nsamples; audio->speed = 1.0; @@ -463,6 +560,102 @@ static int audio_stop(lua_State *L) { return 1; } +/*** +Update all the currently playing audio streams. +Must be called every frame if you want to use audio with streaming. +@function update +*/ +static int audio_update(lua_State *L) { + if (!isAudioInitialized) luaL_error(L, "audio wasn't initialized correctly"); + + for (int i = 0; i <= 23; i++) { + if (streaming[i] == NULL) continue; + audio_stream* stream = streaming[i]; + if (stream->done) continue; + audio_userdata* audio = stream->audio; + + // If the next chunk started to play, load the next one + if (stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) == stream->nextWaveBuf->sequence_id) { + if (stream->prevWaveBuf) stream->prevStartTime = stream->prevStartTime + (double)(audio->chunkNsamples) / audio->rate; + + if (!stream->eof) { + // Swap buffers + char* prevData = stream->prevData; // doesn't contain important data, can rewrite + char* nextData = stream->nextData; // contains the data that started playing + stream->prevData = nextData; // buffer in use + stream->nextData = prevData; // now contains an available buffer + stream->prevWaveBuf = stream->nextWaveBuf; + + // Decoding loop + u32 chunkNsamples = audio->chunkNsamples; // chunk nsamples and size may be lower than the defaults if reached EOF + u32 chunkSize = audio->chunkSize; + if (audio->type == TYPE_OGG) { + if (ov_seekable(&audio->vf) && ov_raw_tell(&audio->vf) != stream->rawPosition) + ov_raw_seek(&audio->vf, stream->rawPosition); // goto last read end (audio file may be played multiple times at one) + + int offset = 0; + while (!stream->eof && offset < audio->chunkSize) { + long ret = ov_read(&audio->vf, &stream->nextData[offset], fmin(audio->chunkSize - offset, 4096), &stream->currentSection); + if (ret == 0) { + stream->eof = 1; + } else if (ret < 0) { + luaL_error(L, "error in the ogg vorbis stream"); + return 0; + } else { + offset += ret; + } + } + stream->rawPosition = ov_raw_tell(&audio->vf); + chunkSize = offset; + chunkNsamples = chunkSize / audio->channels / audio->bytePerSample; + + } else if (audio->type == TYPE_WAV) { + chunkSize = fmin(audio->fileSize - stream->filePosition, audio->chunkSize); + chunkNsamples = chunkSize / audio->channels / audio->bytePerSample; + + fseek(audio->file, stream->filePosition, SEEK_SET); // goto last read end (audio file may be played multiple times at one) + fread(stream->nextData, chunkSize, 1, audio->file); + stream->filePosition = ftell(audio->file); + if (stream->filePosition == audio->fileSize) stream->eof = 1; + + } else luaL_error(L, "unknown audio type"); + + // Send & play audio data + ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); + + waveBuf->data_vaddr = stream->nextData; + waveBuf->nsamples = chunkNsamples; + waveBuf->looping = false; + + DSP_FlushDataCache((u32*)stream->nextData, chunkSize); + + ndspChnWaveBufAdd(i, waveBuf); + + stream->nextWaveBuf = waveBuf; + } + } + + // Free the last chunk if it's no longer played + if (stream->prevWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->prevWaveBuf->sequence_id) { + free(stream->prevWaveBuf); + stream->prevWaveBuf = NULL; + } + + // We're done + if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof) { + free(stream->nextWaveBuf); + stream->nextWaveBuf = NULL; + linearFree(stream->prevData); + stream->prevData = NULL; + linearFree(stream->nextData); + stream->nextData = NULL; + stream->done = true; + } + } + + return 0; +} + /*** audio object @section Methods @@ -505,8 +698,11 @@ static int audio_object_time(lua_State *L) { if (channel == -1 || channels[channel] != audio || !isAudioInitialized) // audio not playing lua_pushnumber(L, 0); - else - lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate); + else { + double additionnalTime = 0; + if (streaming[channel] != NULL) additionnalTime = streaming[channel]->prevStartTime; + lua_pushnumber(L, (double)(ndspChnGetSamplePos(channel)) / audio->rate + additionnalTime); + } return 1; } @@ -650,20 +846,46 @@ static int audio_object_play(lua_State *L) { ndspChnSetRate(channel, audio->rate * audio->speed); // maybe hackish way to set a different speed, but it works ndspChnSetFormat(channel, NDSP_CHANNELS(audio->channels) | NDSP_ENCODING(audio->encoding)); - // Send & play audio data + // Send & play audio initial data ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); waveBuf->data_vaddr = audio->data; - waveBuf->nsamples = audio->nsamples; + waveBuf->nsamples = audio->chunkNsamples; waveBuf->looping = loop; - DSP_FlushDataCache((u32*)audio->data, audio->size); + DSP_FlushDataCache((u32*)audio->data, audio->chunkSize); ndspChnWaveBufAdd(channel, waveBuf); channels[channel] = audio; lua_pushinteger(L, channel); + // Remove last audio stream + if (streaming[channel] != NULL) { + free(streaming[channel]); + streaming[channel] = NULL; + } + + // Stream the rest of the audio + if (audio->chunkSize < audio->size) { + audio_stream* stream = calloc(1, sizeof(audio_stream)); + stream->audio = audio; + stream->nextWaveBuf = waveBuf; + + // Allocate buffers + if (linearSpaceFree() < audio->chunkSize*2) luaL_error(L, "not enough linear memory available"); + stream->nextData = linearAlloc(audio->chunkSize); + stream->prevData = linearAlloc(audio->chunkSize); + + // Init stream values + if (audio->type == TYPE_OGG) { + stream->currentSection = audio->currentSection; + stream->rawPosition = audio->rawPosition; + } else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition; + + streaming[channel] = stream; + } + return 1; } @@ -718,6 +940,10 @@ static int audio_object_type(lua_State *L) { lua_pushstring(L, "ogg"); else if (audio->type == TYPE_WAV) lua_pushstring(L, "wav"); + else if (audio->type == TYPE_RAW) + lua_pushstring(L, "raw"); + else + lua_pushstring(L, "unknown"); return 1; } @@ -739,6 +965,7 @@ static int audio_object_unload(lua_State *L) { } if (audio->type == TYPE_OGG) ov_clear(&audio->vf); + else if (audio->type == TYPE_WAV) fclose(audio->file); // Free memory linearFree(audio->data); @@ -872,6 +1099,7 @@ static const struct luaL_Reg audio_lib[] = { { "interpolation", audio_interpolation }, { "speed", audio_speed }, { "stop", audio_stop }, + { "update", audio_update }, { NULL, NULL } }; @@ -887,9 +1115,7 @@ int luaopen_audio_lib(lua_State *L) { } void load_audio_lib(lua_State *L) { - if (!isAudioInitialized) { - isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success - } + if (!isAudioInitialized) isAudioInitialized = !ndspInit(); // ndspInit returns 0 in case of success luaL_requiref(L, "ctr.audio", luaopen_audio_lib, false); } From 04eb578892a1236c3fce35c5085071c5cd307828 Mon Sep 17 00:00:00 2001 From: Reuh Date: Wed, 23 Mar 2016 19:53:46 +0100 Subject: [PATCH 17/46] Updated audio example --- sdcard/3ds/ctruLua/examples/audio/audio.lua | 1 + source/audio.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdcard/3ds/ctruLua/examples/audio/audio.lua b/sdcard/3ds/ctruLua/examples/audio/audio.lua index d794708..34dead2 100644 --- a/sdcard/3ds/ctruLua/examples/audio/audio.lua +++ b/sdcard/3ds/ctruLua/examples/audio/audio.lua @@ -47,6 +47,7 @@ while true do gfx.text(5, 65, "Speed: "..(speed*100).."% - Left balance: "..(leftBalance*100).."%") gfx.stop() + audio.update() gfx.render() end diff --git a/source/audio.c b/source/audio.c index 9308cfe..bc6aca5 100644 --- a/source/audio.c +++ b/source/audio.c @@ -931,7 +931,7 @@ static int audio_object_stop(lua_State *L) { /*** Returns the audio object type. @function :type -@treturn string "ogg" or "wav" +@treturn string "ogg", "wav" or "raw" */ static int audio_object_type(lua_State *L) { audio_userdata *audio = luaL_checkudata(L, 1, "LAudio"); From 4c9bdf75fae28c5dc630fb0770fe1e3206b1be83 Mon Sep 17 00:00:00 2001 From: Reuh Date: Wed, 23 Mar 2016 20:16:20 +0100 Subject: [PATCH 18/46] Unload streaming data when stopping audio Forgot some memory freeing, sorry. --- source/audio.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/source/audio.c b/source/audio.c index bc6aca5..812436a 100644 --- a/source/audio.c +++ b/source/audio.c @@ -86,7 +86,7 @@ typedef struct { double prevStartTime; // audio time when last chunk started playing bool eof; // if reached end of file bool done; // if streaming ended and the stream will be skipped on the next update - // (the struct should be keept in memory until replaced or it will break audio:time()) + // (the struct should be keept in memory until replaced or audio stopped or it will break audio:time()) char* nextData; // the next data to play ndspWaveBuf* nextWaveBuf; @@ -107,6 +107,34 @@ audio_userdata* channels[24]; // Array of the audio_instance that needs to be updated when calling audio.update (indexed per channel). audio_stream* streaming[24]; +// Stop playing audio on a channel, and stop streaming/free memory. +void stopAudio(int channel) { + ndspChnWaveBufClear(channel); + + // Stop streaming and free data + if (streaming[channel] != NULL) { + audio_stream* stream = streaming[channel]; + if (stream->nextWaveBuf != NULL) { + free(stream->nextWaveBuf); + stream->nextWaveBuf = NULL; + } + if (stream->nextData != NULL) { + linearFree(stream->nextData); + stream->nextData = NULL; + } + if (stream->prevWaveBuf != NULL) { + free(stream->prevWaveBuf); + stream->prevWaveBuf = NULL; + } + if (stream->prevData != NULL) { + linearFree(stream->prevData); + stream->prevData = NULL; + } + free(stream); + streaming[channel] = NULL; + } +} + /*** Load an audio file. OGG Vorbis and PCM WAV file format are currently supported. @@ -542,7 +570,7 @@ static int audio_stop(lua_State *L) { if (channel == -1) { for (int i = 0; i <= 23; i++) { if (ndspChnIsPlaying(i)) { - ndspChnWaveBufClear(i); + stopAudio(i); n++; } } @@ -550,7 +578,7 @@ static int audio_stop(lua_State *L) { luaL_error(L, "channel number must be between 0 and 23"); } else { if (ndspChnIsPlaying(channel)) { - ndspChnWaveBufClear(channel); + stopAudio(channel); n++; } } @@ -838,7 +866,7 @@ static int audio_object_play(lua_State *L) { if (channel < 0 || channel > 23) luaL_error(L, "channel number must be between 0 and 23"); // Set channel parameters - ndspChnWaveBufClear(channel); + stopAudio(channel); ndspChnReset(channel); ndspChnInitParams(channel); ndspChnSetMix(channel, audio->mix); @@ -910,7 +938,7 @@ static int audio_object_stop(lua_State *L) { if (channel == -1) { for (int i = 0; i <= 23; i++) { if (channels[i] == audio && ndspChnIsPlaying(i)) { - ndspChnWaveBufClear(i); + stopAudio(i); n++; } } @@ -918,7 +946,7 @@ static int audio_object_stop(lua_State *L) { luaL_error(L, "channel number must be between 0 and 23"); } else { if (channels[channel] == audio && ndspChnIsPlaying(channel)) { - ndspChnWaveBufClear(channel); + stopAudio(channel); n++; } } @@ -959,7 +987,7 @@ static int audio_object_unload(lua_State *L) { if (isAudioInitialized) { for (int i = 0; i <= 23; i++) { if (channels[i] == audio) { - ndspChnWaveBufClear(i); + stopAudio(i); } } } From 61433417604d161db400ec3b26846475195366f7 Mon Sep 17 00:00:00 2001 From: Reuh Date: Thu, 24 Mar 2016 18:07:41 +0100 Subject: [PATCH 19/46] Fixed audio looping when using streaming Also made sure the chunk size is lower or equal the total file size. --- source/audio.c | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/source/audio.c b/source/audio.c index 812436a..fa56cd0 100644 --- a/source/audio.c +++ b/source/audio.c @@ -72,6 +72,8 @@ typedef struct { typedef struct { audio_userdata* audio; + bool loop; // loop audio? + // Current position information union { // OGG @@ -213,7 +215,7 @@ static int audio_load(lua_State *L) { audio->chunkNsamples = audio->nsamples; audio->chunkSize = audio->size; } else { - audio->chunkNsamples = round(streamChunk * audio->rate); + audio->chunkNsamples = fmin(round(streamChunk * audio->rate), audio->nsamples); audio->chunkSize = audio->chunkNsamples * audio->channels * 2; } @@ -332,7 +334,7 @@ static int audio_load(lua_State *L) { audio->chunkNsamples = audio->nsamples; audio->chunkSize = audio->size; } else { - audio->chunkNsamples = round(streamChunk * audio->rate); + audio->chunkNsamples = fmin(round(streamChunk * audio->rate), audio->nsamples); audio->chunkSize = audio->chunkNsamples * audio->channels * audio->bytePerSample; } @@ -673,11 +675,37 @@ static int audio_update(lua_State *L) { if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof) { free(stream->nextWaveBuf); stream->nextWaveBuf = NULL; - linearFree(stream->prevData); - stream->prevData = NULL; - linearFree(stream->nextData); - stream->nextData = NULL; - stream->done = true; + + // Free memory + if (!stream->loop) { + linearFree(stream->prevData); + stream->prevData = NULL; + linearFree(stream->nextData); + stream->nextData = NULL; + stream->done = true; + // Loop: goto start + } else { + // Send & play audio initial data + ndspWaveBuf* waveBuf = calloc(1, sizeof(ndspWaveBuf)); + + waveBuf->data_vaddr = audio->data; + waveBuf->nsamples = audio->chunkNsamples; + waveBuf->looping = false; + + DSP_FlushDataCache((u32*)audio->data, audio->chunkSize); + + ndspChnWaveBufAdd(i, waveBuf); + + stream->nextWaveBuf = waveBuf; + + // Reset stream values + stream->prevStartTime = 0; + stream->eof = false; + if (audio->type == TYPE_OGG) { + stream->currentSection = audio->currentSection; + stream->rawPosition = audio->rawPosition; + } else if (audio->type == TYPE_WAV) stream->filePosition = audio->filePosition; + } } } @@ -879,7 +907,7 @@ static int audio_object_play(lua_State *L) { waveBuf->data_vaddr = audio->data; waveBuf->nsamples = audio->chunkNsamples; - waveBuf->looping = loop; + waveBuf->looping = (audio->chunkSize < audio->size) ? false : loop; // let ndsp loop the chunk if not streaming DSP_FlushDataCache((u32*)audio->data, audio->chunkSize); @@ -898,6 +926,7 @@ static int audio_object_play(lua_State *L) { if (audio->chunkSize < audio->size) { audio_stream* stream = calloc(1, sizeof(audio_stream)); stream->audio = audio; + stream->loop = loop; stream->nextWaveBuf = waveBuf; // Allocate buffers From eae356ce8090e1d9df85c46310da618f47772a2d Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Sat, 26 Mar 2016 16:49:23 +0100 Subject: [PATCH 20/46] Added the :save() method to textures, Added support for some image formats The new image formats are loaded with stb_image, and the textures are saved with stb_image_write. https://github.com/nothings/stb You can now load GIFs (not animated), PSD, TGA, HDR, PIC and PNM. The texture loading still uses sfil for JPEG, PNG, and BMP. You can force the use of stbi by loading with the "type" argument in texture.load() set to 242 (or anything 5 +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_flip(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_flip(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } + #undef CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return stbi__err("bad component ID","Corrupt JPEG"); + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__jpeg j; + j.s = s; + return stbi__jpeg_info_raw(&j, x, y, comp); +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncomperssed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncomperssed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + int filter_bytes = img_n; + int width = x; + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*img_n; + #define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; + } + #undef CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; + if (has_trans) + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_out_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + info->mr = info->mg = info->mb = 0; + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif g; + if (!stbi__gif_header(s, &g, comp, 1)) { + stbi__rewind( s ); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } + else if (g.out) + STBI_FREE(g.out); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + diff --git a/libs/stb/include/stb_image_write.h b/libs/stb/include/stb_image_write.h new file mode 100644 index 0000000..1bb99e1 --- /dev/null +++ b/libs/stb/include/stb_image_write.h @@ -0,0 +1,1045 @@ +/* stb_image_write - v1.01 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + +LICENSE + +This software is in the public domain. Where that dedication is not +recognized, you are granted a perpetual, irrevocable license to copy, +distribute, and modify this file as you see fit. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +extern int stbi_write_tga_with_rle; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_tga_with_rle = 1; +#else +int stbi_write_tga_with_rle = 1; +#endif + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 1: + s->func(s->context,d,1); + break; + case 2: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + for (j = y - 1; j >= 0; --j) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson +#ifndef STBI_WRITE_NO_STDIO + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + return 1; + } +} + +int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char **hash_table[stbiw__ZHASH]; // 64KB on the stack! + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + f = fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + diff --git a/source/gfx.c b/source/gfx.c index 23a5faf..ed6bfe5 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -558,6 +558,10 @@ static const struct luaL_Reg target_methods[] = { { NULL, NULL } }; +/*** +Constants +@section constants +*/ // Constants struct { char *name; int value; } gfx_constants[] = { /*** diff --git a/source/texture.c b/source/texture.c index a369737..6468da6 100644 --- a/source/texture.c +++ b/source/texture.c @@ -12,6 +12,12 @@ The `gfx.texture` module. #include #include +#define STB_IMAGE_IMPLEMENTATION +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include +#include + #include "texture.h" int getType(const char *name) { @@ -58,9 +64,15 @@ static int texture_load(lua_State *L) { } else if (type==2) { //BMP texture->texture = sfil_load_BMP_file(path, place); //appears to be broken right now. } else { - lua_pushnil(L); - lua_pushstring(L, "Bad type"); - return 2; + int w, h; + char* data = (char*)stbi_load(path, &w, &h, NULL, 4); + if (data == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Can't open file"); + return 2; + } + texture->texture = sf2d_create_texture_mem_RGBA8(data, w, h, TEXFMT_RGBA8, place); + free(data); } if (texture->texture == NULL) { @@ -246,7 +258,7 @@ Set the blend color of the texture. @tparam number color new blend color */ static int texture_setBlendColor(lua_State *L) { - texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); + texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); u32 color = luaL_checkinteger(L, 2); texture->blendColor = color; @@ -254,6 +266,87 @@ static int texture_setBlendColor(lua_State *L) { return 0; } +/*** +Save a texture to a file. +@function :save +@tparam string filename path to the file to save the texture to +@tparam[opt=TYPE_PNG] number type type of the image to save. Can be TYPE_PNG or TYPE_BMP +@treturn[1] boolean true on success +@treturn[2] nil +@treturn[2] string error message +*/ +static int texture_save(lua_State *L) { + texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); + const char* path = luaL_checkstring(L, 2); + u8 type = luaL_optinteger(L, 3, 0); + + u32* buff = malloc(texture->texture->width * texture->texture->height * 4); + if (buff == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate buffer"); + return 2; + } + for (int y=0;ytexture->height;y++) { + for (int x=0;xtexture->width;x++) { + buff[x+(y*texture->texture->width)] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); + } + } + + int result = 0; + if (type == 0) { // PNG + FILE* file = fopen(path, "wb"); + if (file == NULL) { + free(buff); + lua_pushnil(L); + lua_pushstring(L, "Can open file"); + return 2; + } + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop infos = png_create_info_struct(png); + setjmp(png_jmpbuf(png)); + png_init_io(png, file); + + png_set_IHDR(png, infos, texture->texture->width, texture->texture->height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, infos); + + png_bytep row = malloc(4 * texture->texture->width * sizeof(png_byte)); + + for(int y=0;ytexture->height;y++) { + for (int x=0;xtexture->width;x++) { + ((u32*)row)[x] = buff[x+(y*texture->texture->width)]; + } + png_write_row(png, row); + } + + png_write_end(png, NULL); + + fclose(file); + png_free_data(png, infos, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png, &infos); + free(row); + + result = 1; + + } else if (type == 2) { // BMP + result = stbi_write_bmp(path, texture->texture->width, texture->texture->height, 4, buff); + } else { + free(buff); + lua_pushnil(L); + lua_pushstring(L, "Not a valid type"); + return 2; + } + free(buff); + + if (result == 0) { + lua_pushnil(L); + lua_pushstring(L, "Failed to save the texture"); + return 2; + } + + lua_pushboolean(L, true); + return 1; +} + // object static const struct luaL_Reg texture_methods[] = { { "draw", texture_draw }, @@ -264,6 +357,7 @@ static const struct luaL_Reg texture_methods[] = { { "getPixel", texture_getPixel }, { "setPixel", texture_setPixel }, { "setBlendColor", texture_setBlendColor }, + { "save", texture_save }, { "__gc", texture_unload }, {NULL, NULL} }; From c687efcfb48dd6c3a4ca9ce8c7d57e66979e214b Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Sat, 2 Apr 2016 18:29:54 +0200 Subject: [PATCH 21/46] Made keyboard.lua use a configuration file Thus removing the need to edit it directly as an user --- sdcard/3ds/ctruLua/config/keyboard.cfg | 40 ++++++++++++++++ sdcard/3ds/ctruLua/libs/keyboard.lua | 66 +++++++------------------- 2 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 sdcard/3ds/ctruLua/config/keyboard.cfg diff --git a/sdcard/3ds/ctruLua/config/keyboard.cfg b/sdcard/3ds/ctruLua/config/keyboard.cfg new file mode 100644 index 0000000..fda1efe --- /dev/null +++ b/sdcard/3ds/ctruLua/config/keyboard.cfg @@ -0,0 +1,40 @@ +keyWidth, keyHeight = 25, 25 + +layout = { + ["default"] = { + { "&", "é", "\"", "'", "(", "-", "è", "_", "ç", "à", ")", "=", "Bks" }, + { "a", "z", "e", "r", "t", "y", "u", "i", "o", "p", "^", "$", "Ent" }, + { "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "ù", "*", "Ent" }, + { "Shift", "<", "w", "x", "c", "v", "b", "n", ",", ";", ":", "!", "Tab" }, + { "SLck", ">", "+", "/", " ", " ", " ", " ", " ", "{", "}", ".", "Sym" } + }, + ["Shift"] = { + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "°", "+", "Bks" }, + { "A", "Z", "E", "R", "T", "Y", "U", "I", "O", "P", "¨", "£", "Ent" }, + { "Q", "S", "D", "F", "G", "H", "J", "K", "L", "M", "%", "µ", "Ent" }, + { "Shift", ">", "W", "X", "C", "V", "B", "N", "?", ".", "/", "§", "Tab" }, + { "SLck", "~", "#", "[", " ", " ", " ", " ", " ", "]", "|", "@", "Sym" } + }, + ["Sym"] = { + { "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}", "Bks" }, + { "a", "z", "€", "r", "t", "y", "u", "i", "o", "p", "", "¤", "Ent" }, + { "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "", "", "Ent" }, + { "Shift", "", "w", "x", "c", "v", "b", "n", "", "", "", "", "Tab" }, + { "SLck", "", "", "", " ", " ", " ", " ", " ", "", "", "", "Sym" } + }, +} + +alias = { + ["Tab"] = "\t", + ["Ent"] = "\n", + ["Bks"] = "\b" +} + +sticky = { + ["SLck"] = "Shift" +} + +keys = { + ["l"] = "Shift", + ["r"] = "Shift" +} \ No newline at end of file diff --git a/sdcard/3ds/ctruLua/libs/keyboard.lua b/sdcard/3ds/ctruLua/libs/keyboard.lua index af34714..d398194 100644 --- a/sdcard/3ds/ctruLua/libs/keyboard.lua +++ b/sdcard/3ds/ctruLua/libs/keyboard.lua @@ -1,44 +1,12 @@ +local ctr = require("ctr") local hid = require("ctr.hid") local gfx = require("ctr.gfx") local hex = gfx.color.hex -- Options -local keyWidth, keyHeight = 25, 25 -local layout = { - ["default"] = { - { "&", "é", "\"", "'", "(", "-", "è", "_", "ç", "à", ")", "=", "Back" }, - { "a", "z", "e", "r", "t", "y", "u", "i", "o", "p", "^", "$", "Enter" }, - { "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "ù", "*", "Enter" }, - { "Shift", "<", "w", "x", "c", "v", "b", "n", ",", ";", ":", "!", "Tab" }, - { "CpLck", ">", "+", "/", " ", " ", " ", " ", " ", "{", "}", ".", "AltGr" } - }, - ["Shift"] = { - { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "°", "+", "Back" }, - { "A", "Z", "E", "R", "T", "Y", "U", "I", "O", "P", "¨", "£", "Enter" }, - { "Q", "S", "D", "F", "G", "H", "J", "K", "L", "M", "%", "µ", "Enter" }, - { "Shift", ">", "W", "X", "C", "V", "B", "N", "?", ".", "/", "§", "Tab" }, - { "CpLck", "~", "#", "[", " ", " ", " ", " ", " ", "]", "|", "@", "AltGr" } - }, - ["AltGr"] = { - { "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}", "Back" }, - { "a", "z", "€", "r", "t", "y", "u", "i", "o", "p", "", "¤", "Enter" }, - { "q", "s", "d", "f", "g", "h", "j", "k", "l", "m", "", "", "Enter" }, - { "Shift", "", "w", "x", "c", "v", "b", "n", "", "", "", "", "Tab" }, - { "CpLck", "", "", "", " ", " ", " ", " ", " ", "", "", "", "AltGr" } - }, -} -local alias = { - ["Tab"] = "\t", - ["Enter"] = "\n", - ["Back"] = "BACK" -} -local sticky = { - ["CpLck"] = "Shift" -} -local keys = { - ["l"] = "Shift", - ["r"] = "Shift" -} + +local config = {} +loadfile(ctr.root .. "config/keyboard.cfg", nil, config)() -- Variables local currentModifier = { "default", "sticky" } @@ -51,7 +19,7 @@ return { local xTouch, yTouch if hidKeys.down.touch then xTouch, yTouch = hid.touch() end - for key, modifier in pairs(keys) do + for key, modifier in pairs(config.keys) do if hidKeys.down[key] then currentModifier = { modifier, "key" } elseif hidKeys.up[key] and currentModifier[2] == "key" and currentModifier[1] == modifier then @@ -59,27 +27,27 @@ return { end end - for row, rowKeys in pairs(layout[currentModifier[1]]) do + for row, rowKeys in pairs(config.layout[currentModifier[1]]) do for column, key in pairs(rowKeys) do - local xKey, yKey = x + (column-1)*(keyWidth-1), y + (row-1)*(keyHeight-1) + local xKey, yKey = x + (column-1)*(config.keyWidth-1), y + (row-1)*(config.keyHeight-1) - gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, hex(0xFFFFFFFF)) - gfx.rectangle(xKey + 1, yKey + 1, keyWidth - 2, keyHeight - 2, 0, hex(0x000000FF)) + gfx.rectangle(xKey, yKey, config.keyWidth, config.keyHeight, 0, hex(0xFFFFFFFF)) + gfx.rectangle(xKey + 1, yKey + 1, config.keyWidth - 2, config.keyHeight - 2, 0, hex(0x000000FF)) gfx.text(xKey + 2, yKey + 2, key) if xTouch then - if xTouch > xKey and xTouch < xKey + keyWidth then - if yTouch > yKey and yTouch < yKey + keyHeight then - gfx.rectangle(xKey, yKey, keyWidth, keyHeight, 0, hex(0xDDFFFFFF)) + if xTouch > xKey and xTouch < xKey + config.keyWidth then + if yTouch > yKey and yTouch < yKey + config.keyHeight then + gfx.rectangle(xKey, yKey, config.keyWidth, config.keyHeight, 0, hex(0xDDFFFFFF)) - local k = alias[key] or key - if sticky[k] and layout[sticky[k]] then - if currentModifier[1] == sticky[k] and currentModifier[2] == "sticky" then + local k = config.alias[key] or key + if config.sticky[k] and config.layout[config.sticky[k]] then + if currentModifier[1] == config.sticky[k] and currentModifier[2] == "sticky" then currentModifier = { "default", "sticky" } else - currentModifier = { sticky[k], "sticky" } + currentModifier = { config.sticky[k], "sticky" } end - elseif layout[k] then + elseif config.layout[k] then if currentModifier[1] == k and currentModifier[2] == "normal" then currentModifier = { "default", "sticky" } else From 4669a684027b2ac6f83f4c4d0a6c0419536abcfd Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Sat, 2 Apr 2016 18:31:32 +0200 Subject: [PATCH 22/46] Updated filepicker.lua and adding documentation Effectively rendering openfile.lua completely obsolete, which is why it was deleted. filepicker.lua is now way smarter, handles file creation and has a documentation file in LDoc. --- doc/config.ld | 2 +- doc/filepicker.md | 93 ++++++ sdcard/3ds/ctruLua/libs/filepicker.lua | 415 ++++++++++++++++--------- sdcard/3ds/ctruLua/libs/openfile.lua | 156 ---------- 4 files changed, 361 insertions(+), 305 deletions(-) create mode 100644 doc/filepicker.md delete mode 100644 sdcard/3ds/ctruLua/libs/openfile.lua diff --git a/doc/config.ld b/doc/config.ld index 13c1958..cfecb59 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -11,7 +11,7 @@ format = "markdown" plain = true -- Input files -topics = "../README.md" +topics = {"../README.md", "filepicker.md"} file = "../source/" examples = "../sdcard/3ds/ctruLua/examples/" manual_url = "file://../libs/lua-5.3.1/doc/manual.html" diff --git a/doc/filepicker.md b/doc/filepicker.md new file mode 100644 index 0000000..7f89590 --- /dev/null +++ b/doc/filepicker.md @@ -0,0 +1,93 @@ +# filepicker +## filePicker([workingDirectory[, bindings[, callbacks[, ...]]]]) +### Argument: workingDirectory +The directory that shows up first in the file browser. +If this is nil, ctr.fs.getDirectory() is used. +The recommended form is sdmc:/path/ or romfs:/path/, but it can be a simple /path/ instead. +#### Possible values +- string +- nil + +### Argument: bindings +A table, list of filetypes and key bindings related to these filetypes. +#### Format +``` +{ + __default, __directory, [Lua regexp] = { + [keys from ctr.hid.keys()] = { + function + string + }... + __name = string + }... +} +``` + +The Lua regexp is matched against the filename to determine if it is of this type. +__directory is the "file type" for directories +__default is the "file type" for files that cannot be matched with any other. + Also, every other type inherits the values it doesn't define from __default. +A file type contains the human-readable name (__name) of the type, displayed on the bottom screen, + as well as an optional binding for each key. +The optional binding is formed of an anonymous function, followed by the key's label to be displayed on the bottom screen, + if the label is nil, the key isn't displayed but still bound. + +The function is defined as-is: +##### function(externalConfig, selected, bindingPattern, bindingKey) +externalConfig.workingDirectory is the active directory for filePicker, doesn't necessarily match ctrµLua's. +externalConfig.bindings, externalConfig.callbacks and externalConfig.additionalArguments all are the arguments passed to filePicker, + starting from position 2. +externalConfig.fileList is the list of files currently displayed by filePicker, in the same format as is returned by ctr.fs.listDirectory(). +selected.inList is the absolute position of the cursor in externalConfig.fileList +selected.offset is the number of items skipped for display from fileList +bindingPattern is the [Lua regexp] defined earlier, and bindingKey the [key] that triggered this event. +This function may return the same thing as filePicker itself (Defined later here), or nothing. If it returns nothing, +filePicker will keep running, if it returns the same returns as filePicker, filePicker will exit, returning these values. + +#### Notes +Sane defaults are set if you did not set them otherwise: +__default.x quits filePicker, returning the current directory, "__directory", nil and "x". See the returns to understand what this means. +__directory.a changes directories to that directory. + +### Argument: callbacks +A table defining the callbacks ran at the end of each of the equivalent phases. +#### Format +``` +{ + drawTop, drawBottom, eventHandler = function +} +``` +All of these take the following parameters: (externalConfig, selected) +They have the meaning defined earlier. + +#### Notes +Although drawTop and drawBottom are ran at the end of their respective functions, +eventHandler is not, as it cannot be without being run repeatedly, so it's run at the beginning of +the ACTUAL eventHandler instead of its end. + +### Argument: ... +Additional parameters. All of these are aggregated orderly in externalConfig.additionalArguments and passed around as explained earlier. +They have no specific meaning unless defined so by event handling functions. + +### Return 1: selectedPath +The path selected by the either, may or may not have the sdmc:/romfs: prefix, depending on your input. +A string. + +### Return 2: bindingPattern +The pattern the file matched to. You can use this to know exactly which kind of file you're dealing with +A string. + +### Return 3: mode +Included handlers may have it be "open", in case you're opening an existing file, "new" in case the user wants to create a new file, +Or nil. A "nil" is assumed to mean that the user didn't pick anything. +A string or nil. + +### Return 4: key +The key that triggered the event that made filePicker exit. +A string. + +## Included event handlers you have available are: +- filepicker.changeDirectory - The name is on the tin, change workingDirectory to the active element and refresh the file list for that path. +- filepicker.openFile - Quits and returns as described by this document based on the active element. +- filepicker.newFile - Prompts the user to input a file name manually, relative to the current working directory, and with FAT-incompatible characters excluded. Quits and returns that. +- filepicker.nothing - Do nothing and keep on running. Literally. This is used as a plug to enable an action for all (or a certain type) but files of a certain type (or more precise than the initial type). \ No newline at end of file diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua index e2b954a..e4ebd42 100644 --- a/sdcard/3ds/ctruLua/libs/filepicker.lua +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -1,12 +1,13 @@ --- LSH version 0.1 --- ctrµLua official shell +local ctr = require('ctr') +local keyboard = require('keyboard') -local ctr = require("ctr") -local gfx = require("ctr.gfx") +local gfx = ctr.gfx -local function saveGraphicsState() +local externalConfig + +local function gfxPrepare() local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), - gfx.font.getDefault()} + gfx.font.getDefault(), gfx.getTextSize()} local mono = gfx.font.load(ctr.root .. "resources/VeraMono.ttf") @@ -14,186 +15,304 @@ local function saveGraphicsState() gfx.color.setDefault(0xFFFDFDFD) gfx.color.setBackground(0xFF333333) gfx.font.setDefault(mono) + gfx.setTextSize(12) return old end -local function restoreGraphicsState(state) +local function gfxRestore(state) gfx.set3D(state[1]) gfx.color.setDefault(state[2]) gfx.color.setBackground(state[3]) gfx.font.setDefault(state[4]) + gfx.setTextSize(state[5]) end -local function getExtension(sel, bindings) - for _, ext in ipairs(bindings) do - if ext.ext == sel:match("%..+$") then - return ext +local function systemBindings(bindings) + bindings.__default.up = { + function(_, selected, ...) + if selected.inList > 1 then + selected.inList = selected.inList - 1 + if selected.inList == selected.offset then + selected.offset = selected.offset - 1 + end + end end - end -end + } -local function getFilelist(cur) - local files = ctr.fs.list(cur) - - if cur ~= "/" and cur ~= "sdmc:/" then - table.insert(files, {name = "..", isDirectory = true}) - end - - -- Stealy stealing code from original openfile.lua - table.sort(files, function(i, j) - if i.isDirectory and not j.isDirectory then - return true - elseif i.isDirectory == j.isDirectory then - return string.lower(i.name) < string.lower(j.name) + bindings.__default.down = { + function(externalConfig, selected, ...) + if selected.inList < #externalConfig.fileList then + selected.inList = selected.inList + 1 + if selected.inList - selected.offset >= 16 then + selected.offset = selected.offset + 1 + end + end end - end) + } - return files + bindings.__default.left = { + function(_, selected, ...) + selected.inList, selected.offset = 1, 0 + end + } + + bindings.__default.right = { + function(externalConfig, selected, ...) + selected.inList = #externalConfig.fileList + if #externalConfig.fileList > 15 then + selected.offset = #externalConfig.fileList - 16 + end + end + } end -local function drawBottom(cur, selFile, bindings) - local ext = getExtension(selFile.name, bindings) +local function getFileList(workingDirectory) + local fileList = ctr.fs.list(workingDirectory) + if workingDirectory ~= "/" and workingDirectory ~= "sdmc:/" then + table.insert(fileList, {name = "..", isDirectory = true}) + end + + -- Stealy stealing code from original openfile.lua + table.sort(fileList, function(i, j) + if i.isDirectory and not j.isDirectory then + return true + elseif i.isDirectory == j.isDirectory then + return string.lower(i.name) < string.lower(j.name) + end + end) + + return fileList +end + +local function getBinding(selectedFile, bindings) + if selectedFile.isDirectory then + return bindings.__directory, "__directory" + end + for pattern, values in pairs(bindings) do + if selectedFile.name:match(pattern) then + return values, pattern + end + end + return bindings.__default, "__default" +end + +local function drawBottom(externalConfig, workingDirectoryScroll, selected) + local workingDirectory = externalConfig.workingDirectory + local bindings = externalConfig.bindings + local selectedFile = externalConfig.fileList[selected.inList] gfx.start(gfx.BOTTOM) gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3) - gfx.text(1, 0, cur, 12) - gfx.text(1, 15, selFile.name, 12) - if not selFile.isDirectory then - gfx.text(1, 45, selFile.fileSize, 12) + gfx.text(1 - workingDirectoryScroll.value, 0, workingDirectory) + if gfx.font.getDefault():width(workingDirectory) > gfx.BOTTOM_WIDTH - 2 then + workingDirectoryScroll.value = workingDirectoryScroll.value + workingDirectoryScroll.phase + if workingDirectoryScroll.value == (gfx.BOTTOM_WIDTH - 2) - gfx.font.getDefault():width(workingDirectory) or + workingDirectoryScroll.value == 0 then + workingDirectoryScroll.phase = - workingDirectoryScroll.phase + end end - local keys = {"X: Quit/Cancel"} - if selFile.isDirectory then - gfx.text(1, 30, "Directory", 12, 0xFF727272) - gfx.text(1, gfx.BOTTOM_HEIGHT - 30, "A: Open", 12) - gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) - elseif ext then - local lines = 1 + gfx.text(1, 15, selectedFile.name, 12) + if not selectedFile.isDirectory then + gfx.text(1, 45, tostring(selectedFile.fileSize) .. "B", 12, 0xFF727272) + end - -- Keys - if ext.y then - lines = lines + 1 - table.insert(keys, "Y: " .. ext.y) - end - if ext.a then - lines = lines + 1 - table.insert(keys, "A: " .. ext.a) - end - - -- Drawing - for i=lines, 1, -1 do - gfx.text(1, gfx.BOTTOM_HEIGHT - 15*i, keys[i], 12) - end - gfx.text(1, 30, ext.name, 12, 0xFF727272) - gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) + local binding, pattern = getBinding(selectedFile, bindings) + if selectedFile.isDirectory then + gfx.text(1, 30, bindings.__directory.__name, 12, 0xFF727272) else - gfx.text(1, 30, "File", 12, 0xFF727272) - gfx.text(1, 45, tostring(selFile.fileSize) .. "B", 12, 0xFF727272) - gfx.text(1, gfx.BOTTOM_HEIGHT - 15, keys[1], 12) + gfx.text(1, 30, binding.__name, 12, 0xFF727272) end + + local bindingNames = { + {"start", "Start"}, {"select", "Select"}, + {"x", "X"}, {"y", "Y"}, + {"b", "B"}, {"a", "A"}, + {"r", "R"}, {"l", "L"}, + {"zr", "ZR"}, {"zl", "ZL"}, + {"cstickDown", "C Down"}, {"cstickUp", "C Up"}, + {"cstickRight", "C Right"}, {"cstickLeft", "C Left"} + } + + local j = 0 + + for i, v in ipairs(bindingNames) do + if binding[v[1]] and binding[v[1]][2] then + j = j + 1 + gfx.text(1, gfx.BOTTOM_HEIGHT - 15*j, v[2] .. ": " .. binding[v[1]][2]) + end + end + + externalConfig.callbacks.drawBottom(externalConfig, selected) gfx.stop() end -local function drawTop(files, sel, scr) - gfx.start(gfx.TOP) - gfx.rectangle(0, (sel-scr-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3) - local over = #files - scr >= 16 and 16 or #files - scr - for i=scr+1, scr+over do - local color = files[i].isDirectory and 0xFF727272 or 0xFFFDFDFD - gfx.text(1, (i-scr-1)*15+1, files[i].name or "", 12, color) +local function drawTop(externalConfig, selected) + gfx.start(gfx.TOP) + gfx.rectangle(0, (selected.inList-selected.offset-1)*15, gfx.TOP_WIDTH, 16, 0, 0xFF0000B9) + local over = #externalConfig.fileList - selected.offset >= 16 and 16 or #externalConfig.fileList - selected.offset + for i=selected.offset+1, selected.offset+over do + local color = externalConfig.fileList[i].isDirectory and 0xFF727272 or 0xFFFDFDFD + gfx.text(1, (i-selected.offset-1)*15+1, externalConfig.fileList[i].name or "", 12, color) + end + externalConfig.callbacks.drawTop(externalConfig, selected) + gfx.stop() +end + +local function eventHandler(externalConfig, selected) + externalConfig.callbacks.eventHandler(externalConfig, selected) + ctr.hid.read() + local state = ctr.hid.keys() + local binding, pattern = getBinding(externalConfig.fileList[selected.inList], externalConfig.bindings) + for k, v in pairs(binding) do + if k ~= "__name" and state.down[k] then + local a, b, c, key = v[1](externalConfig, selected, pattern, k) + if key then return a, b, c, key + else return end + end + end + for k, v in pairs(externalConfig.bindings.__default) do + if k ~= "__name" and state.down[k] then + local a, b, c, key = v[1](externalConfig, selected, pattern, k) + if key then return a, b, c, key + else break end end - gfx.stop() -end - -local function runA(cur, selFile, bindings) - if not selFile.isDirectory then - local ext = getExtension(selFile.name, bindings) - if not ext then return end - if ext.a then return cur .. selFile.name, ext.ext end end end -local function runY(cur, selFile, bindings) - if not selFile.isDirectory then - local ext = getExtension(selFile.name, bindings) - if not ext then return end - if ext.y then return cur .. selFile.name, ext.ext end +local function nothing(externalConfig, selected, bindingName, bindingKey) + -- externalConfig = {workingDirectory=string, bindings=table, callbacks=table, additionalArguments=table, fileList=table} + -- selected = {file=string, inList=number, offset=number} + -- bindings = {__default/__directory/[regex] = {__name, [keyName] = {(handlingFunction), (name)}}} + -- callbacks = {drawTop, drawBottom, eventHandler} +end + +local function changeDirectory(externalConfig, selected, bindingName, bindingKey) + if externalConfig.fileList[selected.inList].isDirectory then + if externalConfig.fileList[selected.inList].name == ".." then + externalConfig.workingDirectory = externalConfig.workingDirectory:gsub("[^/]+/$", "") + else + externalConfig.workingDirectory = externalConfig.workingDirectory .. externalConfig.fileList[selected.inList].name .. "/" + end + externalConfig.fileList = getFileList(externalConfig.workingDirectory) + selected.inList, selected.offset = 1, 0 end end ---- Open a file browser to allow the user to select a file. --- It will save current graphical settings and set them back after ending. Press up or down to move one element at a time, and press left or right to go at the beginning or at the end of the list. This function is the return result of requiring filepicker.lua --- @name filePicker --- @param bindings A table of the extensions the user can select in the format {{name, ext, a, y},...}, name will show up instead of "File" or "Directory" on the bottom screen, a and y set the action names for those keys on the bottom screen (and also enable them, so if there's neither a or y, the file will have a custom type name but won't be effectively selectable), and ext is the extension to search for, dot included. Everything must be strings. --- @param workdir Optional, current working directory will be used if not specified, otherwise, sets the path at which the file browser first shows up, a string. --- @returns The absolute path to the file, nil in case no file was picked. --- @returns The extension of the file, this might be helpful in cases were multiple file types could be expected, nil in case no file was picked. --- @returns The "mode", which indicates which key was used to select the file, "A" or "Y". "X" in case no file was picked. -return function(bindings, workdir) - -- Initialization - local old = saveGraphicsState() - local cur = workdir or ctr.fs.getDirectory() - if cur:sub(-1) ~= "/" then - cur = cur .. "/" - end - local bindings = bindings or {} - - local files = getFilelist(cur) or {{name = "- Empty -"}} - local sel = 1 - local scr = 0 - +local function newFile(externalConfig, selected, bindingName, bindingKey) + local name = "" while ctr.run() do - drawBottom(cur, files[sel], bindings) - drawTop(files, sel, scr) + gfx.start(gfx.BOTTOM) + gfx.rectangle(0, 0, gfx.BOTTOM_WIDTH, 16, 0, 0xFF0000B3) + gfx.text(1, 0, externalConfig.workingDirectory) + keyboard.draw(4, 115) + gfx.stop() + + gfx.start(gfx.TOP) + gfx.rectangle(0, 0, gfx.TOP_WIDTH, 16, 0, 0xFF0000B3) + gfx.text(1, 0, "Creating new file") + gfx.rectangle(4, gfx.TOP_HEIGHT // 2 - 15, gfx.TOP_WIDTH - 8, 30, 0, 0xFF727272) + gfx.text(5, gfx.TOP_HEIGHT // 2 - 6, name, 12) + gfx.stop() gfx.render() + local char = (keyboard.read() or ""):gsub("[\t%/%?%<%>%\\%:%*%|%”%^]", "") ctr.hid.read() - local state = ctr.hid.keys() - if (state.down.dDown or state.down.cpadDown) and sel < #files then - sel = sel + 1 - if sel - scr >= 16 then - scr = scr + 1 - end - elseif (state.down.dUp or state.down.cpadUp) and sel > 1 then - sel = sel - 1 - if sel == scr then - scr = scr - 1 - end - elseif state.down.dLeft or state.down.cpadLeft then - sel = 1 - scr = 0 - elseif state.down.dRight or state.down.cpadRight then - sel = #files - if #files > 15 then - scr = #files - 16 - end + local keys = ctr.hid.keys() - elseif state.down.a then - local selFile = files[sel] - if selFile.isDirectory then - if selFile.name == ".." then - cur = cur:gsub("[^/]+/$", "") - else - cur = cur .. selFile.name .. "/" - end - files, sel, scr = getFilelist(cur), 1, 0 - else - local file, ext = runA(cur, selFile, bindings) - if file then - restoreGraphicsState(old) - return file, ext, "A" - end - end - elseif state.down.y then - local file, ext = runY(cur, files[sel], bindings) - if file then - restoreGraphicsState(old) - return file, ext, "Y" - end - elseif state.down.x then - restoreGraphicsState(old) - return nil, nil, "X" + if char ~= "" and char ~= "\b" and char ~= "\n" then + name = name .. char + elseif char ~= "" and char == "\b" then + name = name:sub(1, (utf8.offset(name, -1) or 0)-1) + elseif (char ~= "" and char == "\n" or keys.down.a) and name ~= "" then + local b, p = getBinding({name=name}, externalConfig.bindings) + return externalConfig.workingDirectory .. name, p, "new", b + elseif keys.down.b then + break end end -end \ No newline at end of file +end + +local function openFile(externalConfig, selected, bindingName, bindingKey) + return externalConfig.workingDirectory .. externalConfig.fileList[selected.inList].name, + bindingName, "open", bindingKey +end + +local function filePicker(workingDirectory, bindings, callbacks, ...) + -- Argument sanitization + local additionalArguments = { ... } + workingDirectory = workingDirectory or ctr.fs.getDirectory() + bindings = bindings or {} + callbacks = callbacks or {} + for _, v in ipairs({"drawTop", "drawBottom", "eventHandler"}) do + if not callbacks[v] then + callbacks[v] = function(...) end + end + end + + if workingDirectory:sub(utf8.offset(workingDirectory, -1) or -1) ~= "/" then + workingDirectory = workingDirectory .. "/" + end + + -- Default Bindings + bindings.__default = bindings.__default or {} + bindings.__default.__name = bindings.__default.__name or "File" + bindings.__default.x = bindings.__default.x or { + function(externalConfig, ...) + return externalConfig.workingDirectory, "__directory", nil, "x" + end, "Quit" + } + + bindings.__directory = bindings.__directory or {} + bindings.__directory.__name = bindings.__directory.__name or "Directory" + bindings.__directory.a = bindings.__directory.a or { + changeDirectory, "Open" + } + + local movementKeys = { + "up" , "down" , "left" , "right" , + "cpadUp", "cpadDown", "cpadLeft", "cpadRight", + "dUp" , "dDown" , "dLeft" , "dRight" + } + + for k, v in pairs(bindings) do + if k ~= "__default" then + setmetatable(bindings[k], {__index = bindings.__default}) + end + + for _, w in ipairs(movementKeys) do + if v[w] then bindings[k][w] = nil end + end + end + + systemBindings(bindings) + + -- Other Initialization + local selected = {inList = 1, offset = 0} + local workingDirectoryScroll = { value = 0, phase = -1 } + local gfxState = gfxPrepare() + + -- Main Loop + externalConfig = {workingDirectory=workingDirectory, bindings=bindings, + callbacks=callbacks, additionalArguments=additionalArguments, + fileList=getFileList(workingDirectory)} + while ctr.run() do + drawBottom(externalConfig, workingDirectoryScroll, selected) + drawTop(externalConfig, selected) + gfx.render() + + local file, binding, mode, key = eventHandler(externalConfig, selected) + + if key then + gfxRestore(gfxState) + return file, binding, mode, key + end + end +end + +local returnTable = {filePicker = filePicker, openFile = openFile, + newFile = newFile, changeDirectory = changeDirectory} +setmetatable(returnTable, {__call = function(self, ...) return self.filePicker(...) end}) + +return returnTable \ No newline at end of file diff --git a/sdcard/3ds/ctruLua/libs/openfile.lua b/sdcard/3ds/ctruLua/libs/openfile.lua deleted file mode 100644 index 0137005..0000000 --- a/sdcard/3ds/ctruLua/libs/openfile.lua +++ /dev/null @@ -1,156 +0,0 @@ --- Sort ctr.fs.list returns (directories first and alphabetical sorting) -local function sort(files) - table.sort(files, function(i, j) - if i.isDirectory and not j.isDirectory then - return true - elseif i.isDirectory == j.isDirectory then - return string.lower(i.name) < string.lower(j.name) - end - end) - return files -end - ---- Open a file explorer to select a file. --- string title: title of the file explorer. --- string curdir: the directory to initially open the file explorer in, or nil for the current directory. --- string exts: the file extensions the user can select, separated by ";". If nil, all extensions are accepted. --- string type: "exist" to select an existing file, "new" to select an non-existing file or "any" to select a existing --- or non-existing file name. If nil, defaults to "exist". --- returns string: the file the user has selected, or nil if the explorer was closed without selecting a file. --- string: "exist" if the file exist or "new" if it doesn't -return function(title, curdir, exts, type) - -- Open libs - local ctr = require("ctr") - local gfx = require("ctr.gfx") - - local keyboard = require("keyboard") - - -- Arguments - local curdir = curdir or ctr.fs.getDirectory() - local type = type or "exist" - - -- Variables - local sel = 1 - local scroll = 0 - local files = sort(ctr.fs.list(curdir)) - - if curdir ~= "/" then table.insert(files, 1, { name = "..", isDirectory = true }) end - local newFileName = "" - - local ret = nil - - -- Remember and set defaults - local was3D = gfx.get3D() - local wasDefault = gfx.color.getDefault() - local wasBackground = gfx.color.getBackground() - local wasFont = gfx.font.getDefault() - gfx.set3D(false) - gfx.color.setDefault(0xFFFFFFFF) - gfx.color.setBackground(0xFF000000) - gfx.font.setDefault() - - while ctr.run() do - ctr.hid.read() - local keys = ctr.hid.keys() - if keys.down.start then break end - - -- Keys input - if keys.down.down and sel < #files then - sel = sel + 1 - if sel > scroll + 14 then scroll = scroll + 1 end - elseif keys.down.up and sel > 1 then - sel = sel - 1 - if sel <= scroll then scroll = scroll - 1 end - end - - if keys.down.a then - local f = files[sel] - - if f.isDirectory then - if f.name == ".." then curdir = curdir:gsub("[^/]+/$", "") - else curdir = curdir..f.name.."/" end - - sel = 1 - scroll = 0 - files = sort(ctr.fs.list(curdir)) - - if curdir ~= "/" then - table.insert(files, 1, { name = "..", isDirectory = true }) - end - elseif type == "exist" or type == "any" then - if exts then - for ext in (exts..";"):gmatch("[^;]+") do - if f.name:match("%..+$") == ext then - ret = { curdir..f.name, "exist" } - break - end - end - else - ret = { curdir..f.name, "exist" } - end - if ret then break end - end - end - - -- Keyboard input - if type == "new" or type == "any" then - local input = keyboard.read() - if input then - if input == "BACK" then - newFileName = newFileName:sub(1, (utf8.offset(newFileName, -1) or 0)-1) - elseif input == "\n" then - local fileStatus = "new" - local f = io.open(curdir..newFileName) - if f then fileStatus = "exist" f:close() end - ret = { curdir..newFileName, fileStatus } - break - else - newFileName = newFileName..input - end - end - end - - -- Draw - gfx.start(gfx.TOP) - - gfx.rectangle(0, 10+(sel-scroll)*15, gfx.TOP_WIDTH, 15, 0, gfx.color.RGBA8(0, 0, 200)) - - for i = scroll+1, scroll+14, 1 do - local f = files[i] - if not f then break end - local name = f.isDirectory and "["..f.name.."]" or f.name.." ("..f.fileSize.."b)" - if not f.isHidden then gfx.text(5, 12+(i-scroll)*15, name) end - end - - gfx.rectangle(0, 0, gfx.TOP_WIDTH, 25, 0, gfx.color.RGBA8(200, 200, 200)) - gfx.text(3, 3, curdir, 13, gfx.color.RGBA8(0, 0, 0)) - - gfx.stop() - - gfx.start(gfx.BOTTOM) - - gfx.text(5, 5, title) - gfx.text(5, 20, "Accepted file extensions: "..(exts or "all")) - - if type == "new" or type == "any" then - gfx.text(5, 90, newFileName) - keyboard.draw(5, 115) - end - - gfx.stop() - - gfx.render() - end - - -- Reset defaults - gfx.set3D(was3D) - gfx.color.setDefault(wasDefault) - gfx.color.setBackground(wasBackground) - gfx.font.setDefault(wasFont) - - if ret then - return table.unpack(ret) - else - return ret - end -end From 05c9adc2a07770772cbaa2478ca22606295970ce Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Sat, 2 Apr 2016 18:36:27 +0200 Subject: [PATCH 23/46] Updated apps that rely on file selection The editor doesn't use openfile.lua anymore, the main shell is updated to use filepicker.filePicker's new format. Also, updated the editor to use resources/VeraMono.ttf instead of its own copy, which was deleted. --- sdcard/3ds/ctruLua/editor/VeraMono.ttf | Bin 49224 -> 0 bytes sdcard/3ds/ctruLua/editor/main.lua | 18 ++++++++++------- sdcard/3ds/ctruLua/main.lua | 27 ++++++++++++++++--------- 3 files changed, 28 insertions(+), 17 deletions(-) delete mode 100644 sdcard/3ds/ctruLua/editor/VeraMono.ttf diff --git a/sdcard/3ds/ctruLua/editor/VeraMono.ttf b/sdcard/3ds/ctruLua/editor/VeraMono.ttf deleted file mode 100644 index 139f0b4311ad2e0369a347b3be6c46e6c2b730d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49224 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 diff --git a/sdcard/3ds/ctruLua/editor/main.lua b/sdcard/3ds/ctruLua/editor/main.lua index 64c9145..1831568 100644 --- a/sdcard/3ds/ctruLua/editor/main.lua +++ b/sdcard/3ds/ctruLua/editor/main.lua @@ -4,19 +4,23 @@ local gfx = require("ctr.gfx") -- Open libs local keyboard = require("keyboard") -local openfile = require("openfile") +local filepicker = require("filepicker") local color = dofile("color.lua") local syntax = dofile("syntax.lua") -- Load data -local font = gfx.font.load("VeraMono.ttf") +local font = gfx.font.load(ctr.root .. "resources/VeraMono.ttf") -- Open file -local path, status = openfile("Choose a file to edit", nil, nil, "any") -if not path then return end +local path, binding, mode, key = filepicker(nil, {__default = { + a = {filepicker.openFile, "Open"}, + y = {filepicker.newFile, "New File"} + } +}) +if not mode then return end local lineEnding local lines = {} -if status == "exist" then +if mode == "open" then for line in io.lines(path, "L") do if not lineEnding then lineEnding = line:match("([\n\r]+)$") end table.insert(lines, line:match("^(.-)[\n\r]*$")) @@ -110,7 +114,7 @@ while ctr.run() do -- Keyboard input local input = keyboard.read() if input then - if input == "BACK" then + if input == "\b" then if cursorX > utf8.len(lines[cursorY])+1 then cursorX = utf8.len(lines[cursorY])+1 end if cursorX > 1 then lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX-1)-1).. @@ -173,7 +177,7 @@ while ctr.run() do gfx.text(3, 3, "FPS: "..math.ceil(gfx.getFPS())) - keyboard.draw(5, 115) + keyboard.draw(4, 115) gfx.stop() diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 44c840e..bea7791 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -5,21 +5,24 @@ local gfx = require("ctr.gfx") -- Set up path local ldir = ctr.root.."libs/" package.path = package.path..";".. ldir.."?.lua;".. ldir.."?/init.lua" +local filepicker = require("filepicker") -- Erroring -local function displayError(err) +local function displayError(err, trace) gfx.set3D(false) gfx.color.setBackground(0xFF0000B3) gfx.color.setDefault(0xFFFDFDFD) + gfx.setTextSize(12) gfx.font.setDefault(gfx.font.load(ctr.root .. "resources/VeraMono.ttf")) while ctr.run() do - gfx.start(gfx.TOP) - gfx.text(1, 1, "An error has occured.", 12) - gfx.wrappedText(1, 30, err, gfx.TOP_WIDTH-2, 12) - gfx.text(1, gfx.TOP_HEIGHT-15, "Press Start to continue.", 12) - gfx.stop() gfx.start(gfx.BOTTOM) + gfx.text(1, 1, "An error has occured.") + gfx.wrappedText(1, 30, err, gfx.BOTTOM_WIDTH-2) + gfx.text(1, gfx.BOTTOM_HEIGHT-15, "Press Start to continue.") + gfx.stop() + gfx.start(gfx.TOP) + gfx.wrappedText(2, 6, trace, gfx.TOP_WIDTH - 2) gfx.stop() gfx.render() @@ -34,11 +37,15 @@ while ctr.run() do gfx.font.setDefault() gfx.color.setDefault(0xFFFDFDFD) gfx.color.setBackground(0xFF333333) - local file, ext, mode = require("filepicker")({{name="Lua Script", ext=".lua", a="Execute"}}) - if file and mode == "A" then + local file, ext, mode, key = filepicker(ctr.root, { + ["%.lua$"] = { + a = {filepicker.openFile, "Run"}, + __name = "Lua Script" + } + }) + if mode then fs.setDirectory(file:match("^(.-)[^/]*$")) - local ok, err = pcall(dofile, file) - if not ok then displayError(err) end + xpcall(dofile, function(err) displayError(err, debug.traceback()) end, file) else break end From 1f23b863799bbfd6a2a45f946a09e7d8de4436e5 Mon Sep 17 00:00:00 2001 From: Neil Zeke Cecchini Date: Sat, 2 Apr 2016 18:40:16 +0200 Subject: [PATCH 24/46] Changed the icon, and deleted the test files. The new one is much nicer. As for the test files, they had nothing to do here from the start. --- icon.png | Bin 1221 -> 1080 bytes sdcard/3ds/ctruLua/tests/httpc.lua | 37 ----------------------------- 2 files changed, 37 deletions(-) delete mode 100644 sdcard/3ds/ctruLua/tests/httpc.lua diff --git a/icon.png b/icon.png index f035c569cd89ff4b8459816f3caca0d15c34a1c7..5c7a62bbacadbb208110faac588d587af6806e44 100644 GIT binary patch delta 1049 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 FV1n Date: Sat, 2 Apr 2016 19:03:24 +0200 Subject: [PATCH 25/46] Fixed sublime-completions doc building --- doc/ldoc.ltp | 4 ++-- sdcard/3ds/ctruLua/libs/keyboard.lua | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/ldoc.ltp b/doc/ldoc.ltp index 600da3e..ca2ab5d 100644 --- a/doc/ldoc.ltp +++ b/doc/ldoc.ltp @@ -5,9 +5,9 @@ # -- Typical usage: ldoc . --template ./ --ext sublime-completions --dir ./sublimetext/ # # local scope = "source.lua" -# local function e(str) return str:gsub("\"", "\\\"") end -- escape json string ("str") +# local function e(str) return (str or ""):gsub("\"", "\\\"") end -- escape json string ("str") # local function indent(indentation, str) -- indent str (except first line) with indentation -# return str:gsub("(.-)\n", indentation.."%1\n"):gsub("\n([^\n]*)$", "\n"..indentation.."%1"):gsub("^"..indentation, "") +# return (str or ""):gsub("(.-)\n", indentation.."%1\n"):gsub("\n([^\n]*)$", "\n"..indentation.."%1"):gsub("^"..indentation, "") # end # local function displayName(item) return item.type == "function" and item.name..item.args or item.name end -- nice name # local function autocompleteName(item) -- ST-autocomplete name diff --git a/sdcard/3ds/ctruLua/libs/keyboard.lua b/sdcard/3ds/ctruLua/libs/keyboard.lua index d398194..b6bba84 100644 --- a/sdcard/3ds/ctruLua/libs/keyboard.lua +++ b/sdcard/3ds/ctruLua/libs/keyboard.lua @@ -1,10 +1,8 @@ -local ctr = require("ctr") local hid = require("ctr.hid") local gfx = require("ctr.gfx") local hex = gfx.color.hex -- Options - local config = {} loadfile(ctr.root .. "config/keyboard.cfg", nil, config)() From e87651a4046b21e77ec79841c700d3086797ce84 Mon Sep 17 00:00:00 2001 From: Reuh Date: Sat, 2 Apr 2016 19:09:40 +0200 Subject: [PATCH 26/46] Readd require('ctr') in keyboard --- sdcard/3ds/ctruLua/libs/keyboard.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/sdcard/3ds/ctruLua/libs/keyboard.lua b/sdcard/3ds/ctruLua/libs/keyboard.lua index b6bba84..e220779 100644 --- a/sdcard/3ds/ctruLua/libs/keyboard.lua +++ b/sdcard/3ds/ctruLua/libs/keyboard.lua @@ -1,3 +1,4 @@ +local ctr = require("ctr") local hid = require("ctr.hid") local gfx = require("ctr.gfx") local hex = gfx.color.hex From e7ff54d58c6d10366b1a8bf34da1487197cbaf35 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Sun, 10 Apr 2016 01:47:52 +0200 Subject: [PATCH 27/46] Added SSL support to sockets, Added console/stdout support, Removed apt.init/apt.shutdown, Added some SSL options to httpc The console can __not__ be used with gfx.start() on a screen. You don't have to gfx.render() while print()ing, but you should do it when you are in the main loop. The SSL sockets don't work with Citra. --- source/apt.c | 32 ++------ source/cfgu.c | 21 ++++- source/ctr.c | 12 ++- source/gfx.c | 49 ++++++++++++ source/httpc.c | 21 +++++ source/news.c | 21 ++++- source/ptm.c | 25 +++++- source/socket.c | 197 +++++++++++++++++++++++++++++++++++++++++++---- source/texture.c | 30 ++++---- 9 files changed, 333 insertions(+), 75 deletions(-) diff --git a/source/apt.c b/source/apt.c index 99a1fd1..e860736 100644 --- a/source/apt.c +++ b/source/apt.c @@ -13,30 +13,6 @@ Used to manage the applets and application status. #include #include -/*** -Initialize the APT module. Useless. -@function init -*/ -static int apt_init(lua_State *L) { - Result ret = aptInit(); - if (ret!=0) { - lua_pushboolean(L, false); - lua_pushinteger(L, ret); - return 2; - } - lua_pushboolean(L, true); - return 1; -} - -/*** -Shutdown the APT module. Useless, don't use it. -@function shutdown -*/ -static int apt_shutdown(lua_State *L) { - aptExit(); - return 0; -} - /*** Open an APT session. Should only work if you don't use the homebrew menu. @function openSession @@ -132,8 +108,6 @@ static int apt_getMenuAppID(lua_State *L) { } static const struct luaL_Reg apt_lib[] = { - {"init", apt_init }, - {"shutdown", apt_shutdown }, {"openSession", apt_openSession }, {"closeSession", apt_closeSession }, {"setStatus", apt_setStatus }, @@ -327,6 +301,8 @@ struct { char *name; int value; } apt_constants[] = { }; int luaopen_apt_lib(lua_State *L) { + aptInit(); + luaL_newlib(L, apt_lib); for (int i = 0; apt_constants[i].name; i++) { @@ -340,3 +316,7 @@ int luaopen_apt_lib(lua_State *L) { void load_apt_lib(lua_State *L) { luaL_requiref(L, "ctr.apt", luaopen_apt_lib, false); } + +void unload_apt_lib(lua_State *L) { + aptExit(); +} diff --git a/source/cfgu.c b/source/cfgu.c index 1700e47..5b2e23b 100644 --- a/source/cfgu.c +++ b/source/cfgu.c @@ -14,13 +14,17 @@ Used to get some user config. #include #include +bool initStateCFGU = false; + /*** Initialize the CFGU module. @function init */ static int cfgu_init(lua_State *L) { - cfguInit(); - + if (!initStateCFGU) { + cfguInit(); + initStateCFGU = true; + } return 0; } @@ -29,8 +33,10 @@ Disable the CFGU module. @function shutdown */ static int cfgu_shutdown(lua_State *L) { - cfguExit(); - + if (initStateCFGU) { + cfguExit(); + initStateCFGU = false; + } return 0; } @@ -309,3 +315,10 @@ int luaopen_cfgu_lib(lua_State *L) { void load_cfgu_lib(lua_State *L) { luaL_requiref(L, "ctr.cfgu", luaopen_cfgu_lib, false); } + +void unload_cfgu_lib(lua_State *L) { + if (initStateCFGU) { + initStateCFGU = false; + cfguExit(); + } +} diff --git a/source/ctr.c b/source/ctr.c index 49c93ba..366fc10 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -28,6 +28,7 @@ The `ctr.news` module. @see ctr.news */ void load_news_lib(lua_State *L); +void unload_news_lib(lua_State *L); /*** The `ctr.ptm` module. @@ -35,6 +36,7 @@ The `ctr.ptm` module. @see ctr.ptm */ void load_ptm_lib(lua_State *L); +void unload_ptm_lib(lua_State *L); /*** The `ctr.hid` module. @@ -80,6 +82,7 @@ The `ctr.cfgu` module. @see ctr.cfgu */ void load_cfgu_lib(lua_State *L); +void unload_cfgu_lib(lua_State *L); /*** The `ctr.socket` module. @@ -109,6 +112,7 @@ The `ctr.apt` module. @see ctr.apt */ void load_apt_lib(lua_State *L); +void unload_apt_lib(lua_State *L); /*** The `ctr.mic` module. @@ -168,18 +172,18 @@ static const struct luaL_Reg ctr_lib[] = { // Subtables struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } ctr_libs[] = { { "gfx", load_gfx_lib, unload_gfx_lib }, - { "news", load_news_lib, NULL }, - { "ptm", load_ptm_lib, NULL }, + { "news", load_news_lib, unload_news_lib }, + { "ptm", load_ptm_lib, unload_ptm_lib }, { "hid", load_hid_lib, unload_hid_lib }, { "ir", load_ir_lib, NULL }, { "fs", load_fs_lib, unload_fs_lib }, { "httpc", load_httpc_lib, unload_httpc_lib }, { "qtm", load_qtm_lib, NULL }, - { "cfgu", load_cfgu_lib, NULL }, + { "cfgu", load_cfgu_lib, unload_cfgu_lib }, { "socket", load_socket_lib, NULL }, { "cam", load_cam_lib, NULL }, { "audio", load_audio_lib, unload_audio_lib }, - { "apt", load_apt_lib, NULL }, + { "apt", load_apt_lib, unload_apt_lib }, { "mic", load_mic_lib, NULL }, { "thread", load_thread_lib, NULL }, { NULL, NULL, NULL } diff --git a/source/gfx.c b/source/gfx.c index ed6bfe5..82342bb 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -12,6 +12,7 @@ The `gfx` module. //#include <3ds/vram.h> //#include <3ds/services/gsp.h> +#include <3ds/console.h> #include #include @@ -524,6 +525,51 @@ static int gfx_target___index(lua_State *L) { return 1; } +/*** +Initialize the console. You can print on it using print(), or any function that normally outputs to stdout. +Warning: you can't use a screen for both a console and drawing, you have to disable the console first. +@function console +@tparam[opt=gfx.TOP] number screen screen to draw the console on. +@tparam[opt=true] boolean debug enable stderr output on the console +*/ +u8 consoleScreen = GFX_TOP; +static int gfx_console(lua_State *L) { + consoleScreen = luaL_optinteger(L, 1, GFX_TOP); + bool err = false; + if (lua_isboolean(L, 2)) { + err = lua_toboolean(L, 2); + } + + consoleInit(consoleScreen, NULL); + if (err) + consoleDebugInit(debugDevice_CONSOLE); + + return 0; +} + +/*** +Clear the console. +@function clearConsole +*/ +static int gfx_clearConsole(lua_State *L) { + consoleClear(); + + return 0; +} + +/*** +Disable the console. +@function disableConsole +*/ +static int gfx_disableConsole(lua_State *L) { + gfxSetScreenFormat(consoleScreen, GSP_BGR8_OES); + gfxSetDoubleBuffering(consoleScreen, true); + gfxSwapBuffersGpu(); + gspWaitForVBlank(); + + return 0; +} + // Functions static const struct luaL_Reg gfx_lib[] = { { "start", gfx_start }, @@ -546,6 +592,9 @@ static const struct luaL_Reg gfx_lib[] = { { "getTextSize", gfx_getTextSize }, { "scissor", gfx_scissor }, { "target", gfx_target }, + { "console", gfx_console }, + { "clearConsole", gfx_clearConsole }, + { "disableConsole", gfx_disableConsole }, { NULL, NULL } }; diff --git a/source/httpc.c b/source/httpc.c index 1df43bd..ef70afc 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -9,6 +9,7 @@ The `httpc` module. #include <3ds.h> #include <3ds/types.h> #include <3ds/services/httpc.h> +#include <3ds/services/sslc.h> #include #include @@ -239,6 +240,25 @@ static int httpc_addTrustedRootCA(lua_State *L) { return 1; } +/*** +Set SSL options for a context. +@function :setSSLOptions +@tparam boolean disableVerify disable server certificate verification if `true` +@tparam[opt=false] boolean tlsv10 use TLS v1.0 if `true` +*/ +static int httpc_setSSLOptions(lua_State *L) { + httpcContext *context = lua_touserdata(L, 1); + + bool disVer = lua_toboolean(L, 2); + bool tsl10 = false; + if (lua_isboolean(L, 3)) + tsl10 = lua_toboolean(L, 3); + + httpcSetSSLOpt(context, (disVer?SSLCOPT_DisableVerify:0)|(tsl10?SSLCOPT_TLSv10:0)); + + return 0; +} + // object static const struct luaL_Reg httpc_methods[] = { {"open", httpc_open }, @@ -252,6 +272,7 @@ static const struct luaL_Reg httpc_methods[] = { {"addPostData", httpc_addPostData }, {"getResponseHeader", httpc_getResponseHeader }, {"addTrustedRootCA", httpc_addTrustedRootCA }, + {"setSSLOptions", httpc_setSSLOptions }, {NULL, NULL} }; diff --git a/source/news.c b/source/news.c index f97e6d9..5afdcbe 100644 --- a/source/news.c +++ b/source/news.c @@ -13,13 +13,17 @@ The `news` module. #include #include +bool initStateNews = false; + /*** Initialize the news module. @function init */ static int news_init(lua_State *L) { - newsInit(); - + if (!initStateNews) { + newsInit(); + initStateNews = true; + } return 0; } @@ -63,8 +67,10 @@ Disable the news module. @function shutdown */ static int news_shutdown(lua_State *L) { - newsExit(); - + if (initStateNews) { + newsExit(); + initStateNews = false; + } return 0; } @@ -83,3 +89,10 @@ int luaopen_news_lib(lua_State *L) { void load_news_lib(lua_State *L) { luaL_requiref(L, "ctr.news", luaopen_news_lib, 0); } + +void unload_news_lib(lua_State *L) { + if (initStateNews) { + newsExit(); + initStateNews = false; + } +} diff --git a/source/ptm.c b/source/ptm.c index 8a902e9..438acde 100644 --- a/source/ptm.c +++ b/source/ptm.c @@ -10,13 +10,19 @@ The `ptm` module. #include #include +bool initStatePTM = false; + /*** Initialize the PTM module. @function init */ static int ptm_init(lua_State *L) { - ptmuInit(); - ptmSysmInit(); + if (!initStatePTM) { + ptmuInit(); + ptmSysmInit(); + + initStatePTM = true; + } return 0; } @@ -26,8 +32,12 @@ Disable the PTM module. @function shutdown */ static int ptm_shutdown(lua_State *L) { - ptmuExit(); - ptmSysmExit(); + if (initStatePTM) { + ptmuExit(); + ptmSysmExit(); + + initStatePTM = false; + } return 0; } @@ -146,3 +156,10 @@ int luaopen_ptm_lib(lua_State *L) { void load_ptm_lib(lua_State *L) { luaL_requiref(L, "ctr.ptm", luaopen_ptm_lib, 0); } + +void unload_ptm_lib(lua_State *L) { + if (initStatePTM) { + ptmuExit(); + ptmSysmExit(); + } +} diff --git a/source/socket.c b/source/socket.c index c4eef76..fc2325d 100644 --- a/source/socket.c +++ b/source/socket.c @@ -8,6 +8,7 @@ The UDP part is only without connection. #include <3ds.h> #include <3ds/types.h> #include <3ds/services/soc.h> +#include <3ds/services/sslc.h> #include #include @@ -15,7 +16,9 @@ The UDP part is only without connection. #include #include #include +#include #include +#include #include #include #include @@ -26,21 +29,58 @@ typedef struct { int socket; struct sockaddr_in addr; struct hostent *host; // only used for client sockets + sslcContext sslContext; + bool isSSL; } socket_userdata; +bool initStateSocket = false; + +u32 rootCertChain = 0; + /*** Initialize the socket module @function init @tparam[opt=0x100000] number buffer size (in bytes), must be a multiple of 0x1000 */ static int socket_init(lua_State *L) { - u32 size = luaL_optinteger(L, 1, 0x100000); - Result ret = socInit((u32*)memalign(0x1000, size), size); + if (!initStateSocket) { + u32 size = luaL_optinteger(L, 1, 0x100000); + if (size%0x1000 != 0) { + lua_pushboolean(L, false); + lua_pushstring(L, "Not a multiple of 0x1000"); + return 2; + } + + u32* mem = (u32*)memalign(0x1000, size); + if (mem == NULL) { + lua_pushboolean(L, false); + lua_pushstring(L, "Failed to allocate memory"); + return 2; + } + + Result ret = socInit(mem, size); - if (ret) { - lua_pushboolean(L, false); - lua_pushinteger(L, ret); - return 2; + if (ret) { + lua_pushboolean(L, false); + lua_pushinteger(L, ret); + return 2; + } + + ret = sslcInit(0); + if (R_FAILED(ret)) { + lua_pushboolean(L, false); + lua_pushinteger(L, ret); + return 2; + } + + sslcCreateRootCertChain(&rootCertChain); + sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_CyberTrust, NULL); + sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_AddTrust_External_CA, NULL); + sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_COMODO, NULL); + sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_USERTrust, NULL); + sslcRootCertChainAddDefaultCert(rootCertChain, SSLC_DefaultRootCert_DigiCert_EV, NULL); + + initStateSocket = true; } lua_pushboolean(L, true); @@ -52,8 +92,11 @@ Disable the socket module. Must be called before exiting ctrµLua. @function shutdown */ static int socket_shutdown(lua_State *L) { + sslcDestroyRootCertChain(rootCertChain); + sslcExit(); socExit(); + initStateSocket = false; return 0; } @@ -76,6 +119,8 @@ static int socket_tcp(lua_State *L) { userdata->addr.sin_family = AF_INET; + userdata->isSSL = false; + return 1; } @@ -92,15 +137,36 @@ static int socket_udp(lua_State *L) { userdata->socket = socket(AF_INET, SOCK_DGRAM, 0); if (userdata->socket < 0) { lua_pushnil(L); - lua_pushstring(L, "Failed to create a TCP socket"); + lua_pushstring(L, strerror(errno)); return 2; } + fcntl(userdata->socket, F_SETFL, O_NONBLOCK); userdata->addr.sin_family = AF_INET; return 1; } +/*** +Add a trusted root CA to the certChain. +@function addTrustedRootCA +@tparam string cert DER cert +*/ +static int socket_addTrustedRootCA(lua_State *L) { + size_t size = 0; + const char* cert = luaL_checklstring(L, 1, &size); + + Result ret = sslcAddTrustedRootCA(rootCertChain, (u8*)cert, size, NULL); + if (R_FAILED(ret)) { + lua_pushnil(L); + lua_pushinteger(L, ret); + return 2; + } + + lua_pushboolean(L, true); + return 1; +} + /*** All sockets @section sockets @@ -130,6 +196,10 @@ Close an existing socket. static int socket_close(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + if (userdata->isSSL) { + sslcDestroyContext(&userdata->sslContext); + } + closesocket(userdata->socket); return 0; @@ -158,6 +228,7 @@ static int socket_accept(lua_State *L) { lua_pushnil(L); return 1; } + fcntl(client->socket, F_SETFL, O_NONBLOCK); return 1; } @@ -167,6 +238,7 @@ Connect a socket to a server. The TCP object becomes a TCPClient object. @function :connect @tparam string host address of the host @tparam number port port of the server +@tparam[opt=false] boolean ssl use SSL if `true` @treturn[1] boolean true if success @treturn[2] boolean false if failed @treturn[2] string error string @@ -175,11 +247,12 @@ static int socket_connect(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); char *addr = (char*)luaL_checkstring(L, 2); int port = luaL_checkinteger(L, 3); + bool ssl = lua_toboolean(L, 4); userdata->host = gethostbyname(addr); if (userdata->host == NULL) { lua_pushnil(L); - lua_pushstring(L, "No such host"); + lua_pushstring(L, strerror(errno)); return 2; } @@ -188,10 +261,23 @@ static int socket_connect(lua_State *L) { if (connect(userdata->socket, (const struct sockaddr*)&userdata->addr, sizeof(userdata->addr)) < 0) { lua_pushnil(L); - lua_pushstring(L, "Connection failed"); + lua_pushstring(L, strerror(errno)); return 2; } + if (ssl) { // SSL context setup + sslcCreateContext(&userdata->sslContext, userdata->socket, SSLCOPT_Default, addr); + sslcContextSetRootCertChain(&userdata->sslContext, rootCertChain); + if (R_FAILED(sslcStartConnection(&userdata->sslContext, NULL, NULL))) { + sslcDestroyContext(&userdata->sslContext); + lua_pushnil(L); + lua_pushstring(L, "SSL connection failed"); + return 2; + } + userdata->isSSL = true; + } + fcntl(userdata->socket, F_SETFL, O_NONBLOCK); + lua_pushboolean(L, 1); return 1; } @@ -237,7 +323,11 @@ static int socket_receive(lua_State *L) { luaL_buffinit(L, &b); char buff; - while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff); + if (!userdata->isSSL) { + while (recv(userdata->socket, &buff, 1, flags) > 0 && buff != '\n') luaL_addchar(&b, buff); + } else { + while (!R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false)) && buff != '\n') luaL_addchar(&b, buff); + } luaL_pushresult(&b); return 1; @@ -247,7 +337,11 @@ static int socket_receive(lua_State *L) { luaL_buffinit(L, &b); char buff; - while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff); + if (!userdata->isSSL) { + while (buff != '\n' && recv(userdata->socket, &buff, 1, flags) > 0) luaL_addchar(&b, buff); + } else { + while (buff != '\n' && !R_FAILED(sslcRead(&userdata->sslContext, &buff, 1, false))) luaL_addchar(&b, buff); + } luaL_pushresult(&b); return 1; @@ -258,7 +352,17 @@ static int socket_receive(lua_State *L) { } char *buff = malloc(count+1); - int len = recv(userdata->socket, buff, count, flags); + int len; + if (!userdata->isSSL) { + len = recv(userdata->socket, buff, count, flags); + } else { + len = sslcRead(&userdata->sslContext, buff, count, false); + if (R_FAILED(len)) { + lua_pushnil(L); + lua_pushinteger(L, len); + return 2; + } + } *(buff+len) = 0x0; // text end lua_pushstring(L, buff); @@ -276,12 +380,68 @@ static int socket_send(lua_State *L) { size_t size = 0; char *data = (char*)luaL_checklstring(L, 2, &size); - size_t sent = send(userdata->socket, data, size, 0); + size_t sent; + if (!userdata->isSSL) { + sent = send(userdata->socket, data, size, 0); + } else { + sent = sslcWrite(&userdata->sslContext, data, size); + if (R_FAILED(sent)) { + lua_pushnil(L); + lua_pushinteger(L, sent); + return 2; + } + } + + if (sent < 0) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } lua_pushinteger(L, sent); return 1; } +/*** +Get some informations from a socket. +@function :getpeername +@treturn string IP +@treturn number port +*/ +static int socket_getpeername(lua_State *L) { + socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + + struct sockaddr_in addr; + socklen_t addrSize = sizeof(addr); + + getpeername(userdata->socket, (struct sockaddr*)&addr, &addrSize); + + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_pushinteger(L, ntohs(addr.sin_port)); + + return 2; +} + +/*** +Get some local informations from a socket. +@function :getsockname +@treturn string IP +@treturn number port +*/ +static int socket_getsockname(lua_State *L) { + socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + + struct sockaddr_in addr; + socklen_t addrSize = sizeof(addr); + + getsockname(userdata->socket, (struct sockaddr*)&addr, &addrSize); + + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_pushinteger(L, ntohs(addr.sin_port)); + + return 2; +} + /*** UDP sockets @section UDP @@ -360,10 +520,11 @@ static int socket_sendto(lua_State *L) { // module functions static const struct luaL_Reg socket_functions[] = { - {"init", socket_init }, - {"shutdown", socket_shutdown}, - {"tcp", socket_tcp }, - {"udp", socket_udp }, + {"init", socket_init }, + {"shutdown", socket_shutdown }, + {"tcp", socket_tcp }, + {"udp", socket_udp }, + {"addTrustedRootCA", socket_addTrustedRootCA}, {NULL, NULL} }; @@ -379,6 +540,8 @@ static const struct luaL_Reg socket_methods[] = { {"receivefrom", socket_receivefrom}, {"send", socket_send }, {"sendto", socket_sendto }, + {"getpeername", socket_getpeername}, + {"getsockname", socket_getsockname}, {NULL, NULL} }; diff --git a/source/texture.c b/source/texture.c index 6468da6..df87a22 100644 --- a/source/texture.c +++ b/source/texture.c @@ -280,23 +280,10 @@ static int texture_save(lua_State *L) { const char* path = luaL_checkstring(L, 2); u8 type = luaL_optinteger(L, 3, 0); - u32* buff = malloc(texture->texture->width * texture->texture->height * 4); - if (buff == NULL) { - lua_pushnil(L); - lua_pushstring(L, "Failed to allocate buffer"); - return 2; - } - for (int y=0;ytexture->height;y++) { - for (int x=0;xtexture->width;x++) { - buff[x+(y*texture->texture->width)] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); - } - } - int result = 0; if (type == 0) { // PNG FILE* file = fopen(path, "wb"); if (file == NULL) { - free(buff); lua_pushnil(L); lua_pushstring(L, "Can open file"); return 2; @@ -313,7 +300,7 @@ static int texture_save(lua_State *L) { for(int y=0;ytexture->height;y++) { for (int x=0;xtexture->width;x++) { - ((u32*)row)[x] = buff[x+(y*texture->texture->width)]; + ((u32*)row)[x] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); } png_write_row(png, row); } @@ -328,14 +315,25 @@ static int texture_save(lua_State *L) { result = 1; } else if (type == 2) { // BMP + u32* buff = malloc(texture->texture->width * texture->texture->height * 4); + if (buff == NULL) { + lua_pushnil(L); + lua_pushstring(L, "Failed to allocate buffer"); + return 2; + } + for (int y=0;ytexture->height;y++) { + for (int x=0;xtexture->width;x++) { + buff[x+(y*texture->texture->width)] = __builtin_bswap32(sf2d_get_pixel(texture->texture, x, y)); + } + } result = stbi_write_bmp(path, texture->texture->width, texture->texture->height, 4, buff); - } else { free(buff); + + } else { lua_pushnil(L); lua_pushstring(L, "Not a valid type"); return 2; } - free(buff); if (result == 0) { lua_pushnil(L); From 6b65df0b8ea3556e7f05d2a0889ceeb37b738bb7 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Wed, 13 Apr 2016 16:19:25 +0200 Subject: [PATCH 28/46] Fixed some documentation, added some things to sockets, "Fixed" the ROMFS The romfs is still not working, but it works better. --- Makefile | 4 +- sdcard/3ds/ctruLua/examples/qtm/qtm.lua | 4 +- sdcard/3ds/ctruLua/main.lua | 2 +- source/cfgu.c | 8 +- source/ctr.c | 3 +- source/fs.c | 1 + source/gfx.c | 7 +- source/main.c | 28 +++--- source/qtm.c | 2 +- source/socket.c | 126 +++++++++++++++--------- 10 files changed, 115 insertions(+), 70 deletions(-) diff --git a/Makefile b/Makefile index 9ea0a1a..b4d10bb 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,9 @@ CFLAGS := -g -Wall -O2 -mword-relocations -std=gnu11 \ $(ARCH) CFLAGS += $(INCLUDE) -DARM11 -D_3DS -DCTR_VERSION=\"$(APP_VERSION)\" -DCTR_BUILD=\"$(LASTCOMMIT)\" +ifneq ($(ROMFS),) + CFLAGS += -DROMFS +endif CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 @@ -132,7 +135,6 @@ endif ifneq ($(ROMFS),) export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) - CFLAGS += -DROMFS endif .PHONY: $(BUILD) clean all diff --git a/sdcard/3ds/ctruLua/examples/qtm/qtm.lua b/sdcard/3ds/ctruLua/examples/qtm/qtm.lua index 25533c2..f4948ff 100644 --- a/sdcard/3ds/ctruLua/examples/qtm/qtm.lua +++ b/sdcard/3ds/ctruLua/examples/qtm/qtm.lua @@ -25,12 +25,12 @@ while ctr.run() do local keys = hid.keys() if keys.down.start then break end - local infos = qtm.getHeadTrackingInfo() + local infos = qtm.getHeadtrackingInfo() gfx.start(gfx.TOP) if infos:checkHeadFullyDetected() then for i=1, 4 do - gfx.point(infos:convertCoordToScreen(1, 400, 240)) + gfx.point(infos:convertCoordToScreen(i, 400, 240)) end end gfx.stop() diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index bea7791..e867261 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -51,4 +51,4 @@ while ctr.run() do end end -error("Main process has exited.\nPlease reboot.\nPressing Start does not work yet.") \ No newline at end of file +error("Main process has exited.\nPlease reboot.\nPressing Start does not work yet.") diff --git a/source/cfgu.c b/source/cfgu.c index 5b2e23b..c31a8af 100644 --- a/source/cfgu.c +++ b/source/cfgu.c @@ -109,9 +109,13 @@ static int cfgu_getUsername(lua_State *L) { CFGU_GetConfigInfoBlk2(0x1C, 0xA0000, (u8*)block); u8 *name = malloc(0x14); - utf16_to_utf8(name, block, 0x14); + ssize_t len = utf16_to_utf8(name, block, 0x14); + if (len < 0) { + lua_pushstring(L, ""); + return 1; + } - lua_pushlstring(L, (const char *)name, 0x14); // The username is only 0x14 characters long. + lua_pushlstring(L, (const char *)name, len); // The username is only 0x14 characters long. return 1; } diff --git a/source/ctr.c b/source/ctr.c index 366fc10..c545a84 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -215,7 +215,8 @@ int luaopen_ctr_lib(lua_State *L) { @field root */ #ifdef ROMFS - char* buff = "romfs:"; + char* buff = "romfs:/"; + chdir(buff); #else char* buff = malloc(1024); getcwd(buff, 1024); diff --git a/source/fs.c b/source/fs.c index b3e2235..dd5faeb 100644 --- a/source/fs.c +++ b/source/fs.c @@ -239,3 +239,4 @@ void unload_fs_lib(lua_State *L) { fsExit(); } + diff --git a/source/gfx.c b/source/gfx.c index 82342bb..d172eca 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -525,12 +525,17 @@ static int gfx_target___index(lua_State *L) { return 1; } +/*** +Console +@section console +*/ + /*** Initialize the console. You can print on it using print(), or any function that normally outputs to stdout. Warning: you can't use a screen for both a console and drawing, you have to disable the console first. @function console @tparam[opt=gfx.TOP] number screen screen to draw the console on. -@tparam[opt=true] boolean debug enable stderr output on the console +@tparam[opt=false] boolean debug enable stderr output on the console */ u8 consoleScreen = GFX_TOP; static int gfx_console(lua_State *L) { diff --git a/source/main.c b/source/main.c index d106898..33febb3 100644 --- a/source/main.c +++ b/source/main.c @@ -36,7 +36,23 @@ void error(const char *error) { // Main loop int main(int argc, char** argv) { // Default arguments + #ifdef ROMFS + char* mainFile = "romfs:/main.lua"; + #else char* mainFile = "main.lua"; + #endif + + // Init Lua + lua_State *L = luaL_newstate(); + if (L == NULL) { + error("Memory allocation error while creating a new Lua state"); + return 0; + } + + // Load libs + luaL_openlibs(L); + load_ctr_lib(L); + isGfxInitialized = true; // Parse arguments for (int i=0;iaddr.sin_family = AF_INET; userdata->isSSL = false; + fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK); return 1; } @@ -140,9 +147,9 @@ static int socket_udp(lua_State *L) { lua_pushstring(L, strerror(errno)); return 2; } - fcntl(userdata->socket, F_SETFL, O_NONBLOCK); userdata->addr.sin_family = AF_INET; + fcntl(userdata->socket, F_SETFL, fcntl(userdata->socket, F_GETFL, 0)|O_NONBLOCK); return 1; } @@ -151,6 +158,9 @@ static int socket_udp(lua_State *L) { Add a trusted root CA to the certChain. @function addTrustedRootCA @tparam string cert DER cert +@treturn[1] boolean `true` if everything went fine +@treturn[2] nil in case of error +@treturn[2] number error code */ static int socket_addTrustedRootCA(lua_State *L) { size_t size = 0; @@ -181,7 +191,7 @@ static int socket_bind(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); int port = luaL_checkinteger(L, 2); - userdata->addr.sin_addr.s_addr = htonl(INADDR_ANY); + userdata->addr.sin_addr.s_addr = gethostid(); userdata->addr.sin_port = htons(port); bind(userdata->socket, (struct sockaddr*)&userdata->addr, sizeof(userdata->addr)); @@ -205,6 +215,64 @@ static int socket_close(lua_State *L) { return 0; } +/*** +Get some informations from a socket. +@function :getpeername +@treturn string IP +@treturn number port +*/ +static int socket_getpeername(lua_State *L) { + socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + + struct sockaddr_in addr; + socklen_t addrSize = sizeof(addr); + + getpeername(userdata->socket, (struct sockaddr*)&addr, &addrSize); + + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_pushinteger(L, ntohs(addr.sin_port)); + + return 2; +} + +/*** +Get some local informations from a socket. +@function :getsockname +@treturn string IP +@treturn number port +*/ +static int socket_getsockname(lua_State *L) { + socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + + struct sockaddr_in addr; + socklen_t addrSize = sizeof(addr); + + getsockname(userdata->socket, (struct sockaddr*)&addr, &addrSize); + + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_pushinteger(L, ntohs(addr.sin_port)); + + return 2; +} + +/*** +Set if the socket should be blocking. +@function :setBlocking +@tparam[opt=true] boolean block if `false`, the socket won't block +*/ +static int socket_setBlocking(lua_State *L) { + socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); + bool block = true; + if (lua_isboolean(L, 2)) + block = lua_toboolean(L, 2); + + int flags = fcntl(userdata->socket, F_GETFL, 0); + flags = block?(flags&~O_NONBLOCK):(flags|O_NONBLOCK); + fcntl(userdata->socket, F_SETFL, flags); + + return 0; +} + /*** TCP Sockets @section TCP @@ -221,6 +289,7 @@ static int socket_accept(lua_State *L) { socket_userdata *client = lua_newuserdata(L, sizeof(*client)); luaL_getmetatable(L, "LSocket"); lua_setmetatable(L, -2); + client->isSSL = false; socklen_t addrSize = sizeof(client->addr); client->socket = accept(userdata->socket, (struct sockaddr*)&client->addr, &addrSize); @@ -228,7 +297,6 @@ static int socket_accept(lua_State *L) { lua_pushnil(L); return 1; } - fcntl(client->socket, F_SETFL, O_NONBLOCK); return 1; } @@ -276,7 +344,6 @@ static int socket_connect(lua_State *L) { } userdata->isSSL = true; } - fcntl(userdata->socket, F_SETFL, O_NONBLOCK); lua_pushboolean(L, 1); return 1; @@ -402,46 +469,6 @@ static int socket_send(lua_State *L) { return 1; } -/*** -Get some informations from a socket. -@function :getpeername -@treturn string IP -@treturn number port -*/ -static int socket_getpeername(lua_State *L) { - socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); - - struct sockaddr_in addr; - socklen_t addrSize = sizeof(addr); - - getpeername(userdata->socket, (struct sockaddr*)&addr, &addrSize); - - lua_pushstring(L, inet_ntoa(addr.sin_addr)); - lua_pushinteger(L, ntohs(addr.sin_port)); - - return 2; -} - -/*** -Get some local informations from a socket. -@function :getsockname -@treturn string IP -@treturn number port -*/ -static int socket_getsockname(lua_State *L) { - socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); - - struct sockaddr_in addr; - socklen_t addrSize = sizeof(addr); - - getsockname(userdata->socket, (struct sockaddr*)&addr, &addrSize); - - lua_pushstring(L, inet_ntoa(addr.sin_addr)); - lua_pushinteger(L, ntohs(addr.sin_port)); - - return 2; -} - /*** UDP sockets @section UDP @@ -533,6 +560,7 @@ static const struct luaL_Reg socket_methods[] = { {"accept", socket_accept }, {"bind", socket_bind }, {"close", socket_close }, + {"setBlocking", socket_setBlocking}, {"__gc", socket_close }, {"connect", socket_connect }, {"listen", socket_listen }, From b798818e99116f58ed4dd02ac994d1441fe92d0b Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Sat, 16 Apr 2016 13:24:03 +0200 Subject: [PATCH 29/46] Fixed the ROMFS, Removed some fields in fs.list(), Updated sftdlib fs.list() now returns a table with tables containing the fields "name", "fileSize", and "isDirectory" --- libs/sftdlib/libsftd/include/sftd.h | 13 +- libs/sftdlib/libsftd/source/sftd.c | 134 +++++++++--------- libs/sftdlib/libsftd/source/texture_atlas.c | 1 + source/font.c | 2 +- source/fs.c | 142 +++++++++----------- 5 files changed, 149 insertions(+), 143 deletions(-) diff --git a/libs/sftdlib/libsftd/include/sftd.h b/libs/sftdlib/libsftd/include/sftd.h index 9e3facc..8702a04 100644 --- a/libs/sftdlib/libsftd/include/sftd.h +++ b/libs/sftdlib/libsftd/include/sftd.h @@ -110,9 +110,19 @@ void sftd_draw_wtextf(sftd_font *font, int x, int y, unsigned int color, unsigne * @param font the font used to calculate the width * @param size the font size * @param text a pointer to the text that will be used to calculate the length + * @return the width in pixels */ int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text); +/** + * @brief Returns the width of the given wide text in pixels + * @param font the font used to calculate the width + * @param size the font size + * @param text a pointer to the wide text that will be used to calculate the length + * @return the width in pixels + */ +int sftd_get_wtext_width(sftd_font *font, unsigned int size, const wchar_t *text); + /** * @brief Draws text using a font. The text will wrap after the pixels specified in lineWidth. * @param font the font to use @@ -149,9 +159,6 @@ void sftd_calc_bounding_box(int *boundingWidth, int *boundingHeight, sftd_font * */ void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text, ...); -// (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn. -int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text); - #ifdef __cplusplus } #endif diff --git a/libs/sftdlib/libsftd/source/sftd.c b/libs/sftdlib/libsftd/source/sftd.c index d0adf24..6dca62f 100644 --- a/libs/sftdlib/libsftd/source/sftd.c +++ b/libs/sftdlib/libsftd/source/sftd.c @@ -227,6 +227,13 @@ void sftd_draw_text(sftd_font *font, int x, int y, unsigned int color, unsigned FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; while (*text) { + if(*text == '\n') { + pen_x = x; + pen_y += size; + text++; + continue; + } + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text); if (use_kerning && previous && glyph_index) { @@ -304,6 +311,13 @@ void sftd_draw_wtext(sftd_font *font, int x, int y, unsigned int color, unsigned FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; while (*text) { + if(*text == '\n') { + pen_x = x; + pen_y += size; + text++; + continue; + } + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text); if (use_kerning && previous && glyph_index) { @@ -417,6 +431,66 @@ int sftd_get_text_width(sftd_font *font, unsigned int size, const char *text) return pen_x; } +int sftd_get_wtext_width(sftd_font *font, unsigned int size, const wchar_t *text) +{ + FTC_FaceID face_id = (FTC_FaceID)font; + FT_Face face; + FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); + + FT_Int charmap_index; + charmap_index = FT_Get_Charmap_Index(face->charmap); + + FT_Glyph glyph; + FT_Bool use_kerning = FT_HAS_KERNING(face); + FT_UInt glyph_index, previous = 0; + int pen_x = 0; + int pen_y = size; + + FTC_ScalerRec scaler; + scaler.face_id = face_id; + scaler.width = size; + scaler.height = size; + scaler.pixel = 1; + + FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; + + while (*text) { + glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text); + + if (use_kerning && previous && glyph_index) { + FT_Vector delta; + FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); + pen_x += delta.x >> 6; + } + + if (!texture_atlas_exists(font->tex_atlas, glyph_index)) { + FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL); + + if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) { + continue; + } + } + + bp2d_rectangle rect; + int bitmap_left, bitmap_top; + int advance_x, advance_y; + int glyph_size; + + texture_atlas_get(font->tex_atlas, glyph_index, + &rect, &bitmap_left, &bitmap_top, + &advance_x, &advance_y, &glyph_size); + + const float draw_scale = size/(float)glyph_size; + + pen_x += (advance_x >> 16) * draw_scale; + pen_y += (advance_y >> 16) * draw_scale; + + previous = glyph_index; + text++; + } + return pen_x; +} + void sftd_draw_text_wrap(sftd_font *font, int x, int y, unsigned int color, unsigned int size, unsigned int lineWidth, const char *text) { FTC_FaceID face_id = (FTC_FaceID)font; @@ -610,63 +684,3 @@ void sftd_draw_textf_wrap(sftd_font *font, int x, int y, unsigned int color, uns sftd_draw_text_wrap(font, x, y, color, size, lineWidth, buffer); va_end(args); } - -// (ctruLua addition) Based on sftd_draw_wtext, returns the width of the text drawn. -int sftd_width_wtext(sftd_font *font, unsigned int size, const wchar_t *text) -{ - FTC_FaceID face_id = (FTC_FaceID)font; - FT_Face face; - FTC_Manager_LookupFace(font->ftcmanager, face_id, &face); - - FT_Int charmap_index; - charmap_index = FT_Get_Charmap_Index(face->charmap); - - FT_Glyph glyph; - FT_Bool use_kerning = FT_HAS_KERNING(face); - FT_UInt glyph_index, previous = 0; - int pen_x = 0; - - FTC_ScalerRec scaler; - scaler.face_id = face_id; - scaler.width = size; - scaler.height = size; - scaler.pixel = 1; - - FT_ULong flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; - - while (*text) { - glyph_index = FTC_CMapCache_Lookup(font->cmapcache, (FTC_FaceID)font, charmap_index, *text); - - if (use_kerning && previous && glyph_index) { - FT_Vector delta; - FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &delta); - pen_x += delta.x >> 6; - } - - if (!texture_atlas_exists(font->tex_atlas, glyph_index)) { - FTC_ImageCache_LookupScaler(font->imagecache, &scaler, flags, glyph_index, &glyph, NULL); - - if (!atlas_add_glyph(font->tex_atlas, glyph_index, (FT_BitmapGlyph)glyph, size)) { - continue; - } - } - - bp2d_rectangle rect; - int bitmap_left, bitmap_top; - int advance_x, advance_y; - int glyph_size; - - texture_atlas_get(font->tex_atlas, glyph_index, - &rect, &bitmap_left, &bitmap_top, - &advance_x, &advance_y, &glyph_size); - - const float draw_scale = size/(float)glyph_size; - - pen_x += (advance_x >> 16) * draw_scale; - - previous = glyph_index; - text++; - } - - return pen_x; -} diff --git a/libs/sftdlib/libsftd/source/texture_atlas.c b/libs/sftdlib/libsftd/source/texture_atlas.c index 03320c5..f8e76bc 100644 --- a/libs/sftdlib/libsftd/source/texture_atlas.c +++ b/libs/sftdlib/libsftd/source/texture_atlas.c @@ -15,6 +15,7 @@ texture_atlas *texture_atlas_create(int width, int height, sf2d_texfmt format, s rect.h = height; atlas->tex = sf2d_create_texture(width, height, format, place); + sf2d_texture_set_params(atlas->tex, GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR)); sf2d_texture_tile32(atlas->tex); atlas->bp_root = bp2d_create(&rect); diff --git a/source/font.c b/source/font.c index 935aa64..80542c9 100644 --- a/source/font.c +++ b/source/font.c @@ -95,7 +95,7 @@ static int font_object_width(lua_State *L) { len = mbstowcs(wtext, text, len); *(wtext+len) = 0x0; // text end - lua_pushinteger(L, sftd_width_wtext(font->font, size, wtext)); + lua_pushinteger(L, sftd_get_wtext_width(font->font, size, wtext)); return 1; } diff --git a/source/fs.c b/source/fs.c index dd5faeb..54d67d6 100644 --- a/source/fs.c +++ b/source/fs.c @@ -3,25 +3,24 @@ The `fs` module. @module ctr.fs @usage local fs = require("ctr.fs") */ +#include #include #include #include +#include +#include #include <3ds/types.h> #include <3ds/util/utf.h> #include <3ds/services/fs.h> +#include <3ds/sdmc.h> +#include <3ds/romfs.h> #include #include bool isFsInitialized = false; -Handle *fsuHandle; -FS_Archive sdmcArchive; -#ifdef ROMFS -FS_Archive romfsArchive; -#endif - /*** The `ctr.fs.lzlib` module. @table lzlib @@ -57,81 +56,76 @@ Lists a directory contents (unsorted). ` { name = "Item name.txt", - shortName = "ITEM~", - shortExt = "TXT", isDirectory = false, - isHidden = false, - isArchive = false, - isReadOnly = false, fileSize = 321 -- (integer) in bytes } ` */ static int fs_list(lua_State *L) { - const char *path = prefix_path(luaL_checkstring(L, 1)); + const char* basepath = prefix_path(luaL_checkstring(L, 1)); + char* path; + bool shouldFreePath = false; + if (basepath[strlen(basepath)-1] != '/') { + path = malloc(strlen(basepath)+2); + strcpy(path, basepath); + strcat(path, "/"); + shouldFreePath = true; + } else { + path = (char*)basepath; + } lua_newtable(L); int i = 1; // table index - - // Get default archive - #ifdef ROMFS - FS_Archive archive = romfsArchive; - #else - FS_Archive archive = sdmcArchive; - #endif - // Archive path override (and skip path prefix) - if (strncmp(path, "sdmc:", 5) == 0) { - path += 5; - archive = sdmcArchive; - #ifdef ROMFS - } else if (strncmp(path, "romfs:", 6) == 0) { - path += 6; - archive = romfsArchive; - #endif + + DIR* dir = opendir(path); + if (dir == NULL) { + if (shouldFreePath) free(path); + lua_pushboolean(L, false); + lua_pushstring(L, strerror(errno)); + return 2; } - - FS_Path dirPath = fsMakePath(PATH_ASCII, path); - - Handle dirHandle; - FSUSER_OpenDirectory(&dirHandle, archive, dirPath); - - u32 entriesRead = 0; - do { - FS_DirectoryEntry buffer; - - FSDIR_Read(dirHandle, &entriesRead, 1, &buffer); - - if (!entriesRead) break; - - uint8_t name[0x106+1]; // utf8 file name - size_t size = utf16_to_utf8(name, buffer.name, 0x106); - *(name+size) = 0x0; // mark text end - - lua_createtable(L, 0, 8); - - lua_pushstring(L, (const char *)name); + errno = 0; + struct dirent *entry; + while (((entry = readdir(dir)) != NULL) && !errno) { + lua_createtable(L, 0, 3); + + lua_pushstring(L, (const char*)entry->d_name); lua_setfield(L, -2, "name"); - lua_pushstring(L, (const char *)buffer.shortName); - lua_setfield(L, -2, "shortName"); - lua_pushstring(L, (const char *)buffer.shortExt); - lua_setfield(L, -2, "shortExt"); - lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_DIRECTORY); + lua_pushboolean(L, entry->d_type==DT_DIR); lua_setfield(L, -2, "isDirectory"); - lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_HIDDEN); - lua_setfield(L, -2, "isHidden"); - lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_ARCHIVE); - lua_setfield(L, -2, "isArchive"); - lua_pushboolean(L, buffer.attributes&FS_ATTRIBUTE_READ_ONLY); - lua_setfield(L, -2, "isReadOnly"); - lua_pushinteger(L, buffer.fileSize); + + if (entry->d_type==DT_REG) { // Regular files: check size + char* filepath = malloc(strlen(path)+strlen(entry->d_name)+1); + if (filepath == NULL) { + luaL_error(L, "Memory allocation error"); + } + memset(filepath, 0, strlen(path)+strlen(entry->d_name)+1); + + strcpy(filepath, path); + strcat(filepath, entry->d_name); + struct stat stats; + if (stat(filepath, &stats)) { + free(filepath); + if (shouldFreePath) free(path); + luaL_error(L, "Stat error: %s (%d)", strerror(errno), errno); + lua_pushboolean(L, false); + lua_pushstring(L, strerror(errno)); + return 2; + } else { + lua_pushinteger(L, stats.st_size); + } + free(filepath); + } else { // Everything else: 0 bytes + lua_pushinteger(L, 0); + } lua_setfield(L, -2, "fileSize"); lua_seti(L, -2, i); i++; - - } while (entriesRead > 0); - - FSDIR_Close(dirHandle); + } + + closedir(dir); + if (shouldFreePath) free(path); return 1; } @@ -214,16 +208,9 @@ int luaopen_fs_lib(lua_State *L) { void load_fs_lib(lua_State *L) { if (!isFsInitialized) { - fsInit(); - - fsuHandle = fsGetSessionHandle(); - FSUSER_Initialize(*fsuHandle); - - sdmcArchive = (FS_Archive){ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")}; - FSUSER_OpenArchive(&sdmcArchive); + sdmcInit(); #ifdef ROMFS - romfsArchive = (FS_Archive){ARCHIVE_ROMFS, fsMakePath(PATH_EMPTY, "")}; - FSUSER_OpenArchive(&romfsArchive); + romfsInit(); #endif isFsInitialized = true; } @@ -232,11 +219,8 @@ void load_fs_lib(lua_State *L) { } void unload_fs_lib(lua_State *L) { - FSUSER_CloseArchive(&sdmcArchive); + sdmcExit(); #ifdef ROMFS - FSUSER_CloseArchive(&romfsArchive); + romfsExit(); #endif - - fsExit(); } - From 347e480d5a0edfaca3ad34ff0f3cab935e37375c Mon Sep 17 00:00:00 2001 From: Reuh Date: Thu, 21 Apr 2016 21:51:45 +0200 Subject: [PATCH 30/46] Moved ctr.gfx.set|getTextSize to ctr.gfx.font.set|getSize, added missing documentation to font:width Started working again on the editor --- sdcard/3ds/ctruLua/editor/color.lua | 2 +- sdcard/3ds/ctruLua/editor/main.lua | 12 +++++------- sdcard/3ds/ctruLua/editor/syntax.lua | 4 ++-- sdcard/3ds/ctruLua/main.lua | 2 +- source/font.c | 27 +++++++++++++++++++++++++++ source/font.h | 2 ++ source/gfx.c | 23 ----------------------- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/sdcard/3ds/ctruLua/editor/color.lua b/sdcard/3ds/ctruLua/editor/color.lua index 5f6ef58..ed32152 100644 --- a/sdcard/3ds/ctruLua/editor/color.lua +++ b/sdcard/3ds/ctruLua/editor/color.lua @@ -15,4 +15,4 @@ return { ["keyword.control"] = hex(0xF92672FF), ["keyword.operator"] = hex(0xF92672FF), ["support.function"] = hex(0x66D9EFFF) -} \ No newline at end of file +} diff --git a/sdcard/3ds/ctruLua/editor/main.lua b/sdcard/3ds/ctruLua/editor/main.lua index 1831568..3c001e5 100644 --- a/sdcard/3ds/ctruLua/editor/main.lua +++ b/sdcard/3ds/ctruLua/editor/main.lua @@ -35,12 +35,13 @@ local coloredLines = syntax(lines, color) -- Variables local lineHeight = 10 +local fontSize = 9 local cursorX, cursorY = 1, 1 local scrollX, scrollY = 0, 0 -- Helper functions local function displayedText(text) - return text:gsub("\t", " ") + return text:gsub("\t", " "), nil end -- Set defaults @@ -48,6 +49,7 @@ gfx.set3D(false) gfx.color.setDefault(color.default) gfx.color.setBackground(color.background) gfx.font.setDefault(font) +gfx.font.setSize(fontSize) while ctr.run() do hid.read() @@ -157,11 +159,7 @@ while ctr.run() do for _,colored in ipairs(coloredLines[i]) do local str = displayedText(colored[1]) - - gfx.color.setDefault(colored[2]) - gfx.text(x, y, str) - gfx.color.setDefault(color.default) - + gfx.text(x, y, str, fontSize, colored[2]) x = x + font:width(str) end end @@ -184,4 +182,4 @@ while ctr.run() do gfx.render() end -font:unload() \ No newline at end of file +font:unload() diff --git a/sdcard/3ds/ctruLua/editor/syntax.lua b/sdcard/3ds/ctruLua/editor/syntax.lua index bebe3f5..18a47b9 100644 --- a/sdcard/3ds/ctruLua/editor/syntax.lua +++ b/sdcard/3ds/ctruLua/editor/syntax.lua @@ -50,7 +50,7 @@ local syntax = { return function(lines, color) local ret = {} - for _,line in ipairs(lines) do + for _, line in ipairs(lines) do local colored = { { line, color.default } } for _, patterns in ipairs(syntax) do @@ -87,4 +87,4 @@ return function(lines, color) end return ret -end \ No newline at end of file +end diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index e867261..9d21667 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -12,7 +12,7 @@ local function displayError(err, trace) gfx.set3D(false) gfx.color.setBackground(0xFF0000B3) gfx.color.setDefault(0xFFFDFDFD) - gfx.setTextSize(12) + gfx.font.setSize(12) gfx.font.setDefault(gfx.font.load(ctr.root .. "resources/VeraMono.ttf")) while ctr.run() do diff --git a/source/font.c b/source/font.c index 80542c9..a29f356 100644 --- a/source/font.c +++ b/source/font.c @@ -15,6 +15,8 @@ The `font` module #include "font.h" +u32 textSize = 9; + /*** Load a TTF font. @function load @@ -70,6 +72,28 @@ static int font_getDefault(lua_State *L) { return 1; } +/*** +Set the default text size. +@function setSize +@tparam number size new default text size +*/ +static int font_setSize(lua_State *L) { + textSize = luaL_checkinteger(L, 1); + + return 0; +} + +/*** +Return the default text size. +@function getSize +@treturn number the default text size +*/ +static int font_getSize(lua_State *L) { + lua_pushinteger(L, textSize); + + return 1; +} + /*** font object @section Methods @@ -79,6 +103,7 @@ font object Return the width of a string with a font. @function :width @tparam string text the text to test +@tparam[opt=default size] integer font size, in pixels @treturn number the width of the text (in pixels) */ static int font_object_width(lua_State *L) { @@ -127,6 +152,8 @@ static const struct luaL_Reg font_lib[] = { { "load", font_load }, { "setDefault", font_setDefault }, { "getDefault", font_getDefault }, + { "setSize", font_setSize }, + { "getSize", font_getSize }, { NULL, NULL } }; diff --git a/source/font.h b/source/font.h index 888176c..2dc87ad 100644 --- a/source/font.h +++ b/source/font.h @@ -5,4 +5,6 @@ typedef struct { sftd_font *font; } font_userdata; +extern u32 textSize; + #endif diff --git a/source/gfx.c b/source/gfx.c index d172eca..447f8df 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -26,7 +26,6 @@ typedef struct { bool isGfxInitialized = false; bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib. -u32 textSize = 9; /*** The `ctr.gfx.color` module. @@ -373,26 +372,6 @@ static int gfx_calcBoundingBox(lua_State *L) { return 2; } -/*** -Set the default text size. -@function setTextSize -@tparam number size new default text size -*/ -static int gfx_setTextSize(lua_State *L) { - textSize = luaL_checkinteger(L, 1); - return 0; -} - -/*** -Return the default text size. -@function getTextSize -@treturn number the default text size -*/ -static int gfx_getTextSize(lua_State *L) { - lua_pushinteger(L, textSize); - return 1; -} - /*** Enables or disable the scissor test. When the scissor test is enabled, the drawing area will be limited to a specific rectangle, every pixel drawn outside will be discarded. @@ -593,8 +572,6 @@ static const struct luaL_Reg gfx_lib[] = { { "text", gfx_text }, { "wrappedText", gfx_wrappedText }, { "calcBoundingBox", gfx_calcBoundingBox }, - { "setTextSize", gfx_setTextSize }, - { "getTextSize", gfx_getTextSize }, { "scissor", gfx_scissor }, { "target", gfx_target }, { "console", gfx_console }, From 2b7d37304dc17bc2f89495ade12f1533493592a0 Mon Sep 17 00:00:00 2001 From: Reuh Date: Thu, 21 Apr 2016 22:31:48 +0200 Subject: [PATCH 31/46] Made the editor usable It was able to edit file before, but didn't show the changes on the screen, so this was easy to do. Also renamed some setTextSize->setSize missed in last commit. --- sdcard/3ds/ctruLua/editor/main.lua | 46 +++++++++++++++++++++++--- sdcard/3ds/ctruLua/editor/syntax.lua | 4 +-- sdcard/3ds/ctruLua/libs/filepicker.lua | 6 ++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/sdcard/3ds/ctruLua/editor/main.lua b/sdcard/3ds/ctruLua/editor/main.lua index 3c001e5..5685fb5 100644 --- a/sdcard/3ds/ctruLua/editor/main.lua +++ b/sdcard/3ds/ctruLua/editor/main.lua @@ -38,6 +38,7 @@ local lineHeight = 10 local fontSize = 9 local cursorX, cursorY = 1, 1 local scrollX, scrollY = 0, 0 +local fileModified = false -- Helper functions local function displayedText(text) @@ -56,7 +57,30 @@ while ctr.run() do local keys = hid.keys() -- Keys input - if keys.down.start then return end + if keys.down.start then + local exit = not fileModified + if fileModified then + while ctr.run() do + hid.read() + local keys = hid.keys() + if keys.down.b then + exit = false + break + elseif keys.down.a then + exit = true + break + end + gfx.start(gfx.TOP) + gfx.text(3, 3, "The file was modified but not saved!") + gfx.text(3, 3 + lineHeight, "Are you sure you want to exit without saving?") + gfx.text(3, 3 + lineHeight*2, "Press A to exit and discard the modified file") + gfx.text(3, 3 + lineHeight*3, "Press B to return to the editor") + gfx.stop() + gfx.render() + end + end + if exit then break end + end if keys.down.dRight then cursorX = cursorX + 1 @@ -101,15 +125,16 @@ while ctr.run() do for i = 1, #lines, 1 do file:write(lines[i]..lineEnding) gfx.start(gfx.TOP) - gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF) - gfx.color.setDefault(color.background) - gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%") - gfx.color.setDefault(color.default) + gfx.rectangle(0, 0, math.ceil(i/#lines*gfx.TOP_WIDTH), gfx.TOP_HEIGHT, 0, 0xFFFFFFFF) + gfx.color.setDefault(color.background) + gfx.text(gfx.TOP_WIDTH/2, gfx.TOP_HEIGHT/2, math.ceil(i/#lines*100).."%") + gfx.color.setDefault(color.default) gfx.stop() gfx.render() end file:flush() file:close() + fileModified = false end end @@ -126,21 +151,30 @@ while ctr.run() do cursorX, cursorY = utf8.len(lines[cursorY-1])+1, cursorY - 1 lines[cursorY] = lines[cursorY]..lines[cursorY+1] table.remove(lines, cursorY+1) + table.remove(coloredLines, cursorY+1) end + + coloredLines[cursorY] = syntax(lines[cursorY], color) + elseif input == "\n" then local newline = lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1) local whitespace = lines[cursorY]:match("^%s+") if whitespace then newline = whitespace .. newline end lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1) + coloredLines[cursorY] = syntax(lines[cursorY], color) table.insert(lines, cursorY + 1, newline) + table.insert(coloredLines, cursorY + 1, syntax(newline, color)) cursorX, cursorY = whitespace and #whitespace+1 or 1, cursorY + 1 + else lines[cursorY] = lines[cursorY]:sub(1, utf8.offset(lines[cursorY], cursorX)-1)..input.. lines[cursorY]:sub(utf8.offset(lines[cursorY], cursorX), -1) + coloredLines[cursorY] = syntax(lines[cursorY], color) cursorX = cursorX + 1 end + fileModified = true end -- Draw @@ -174,6 +208,8 @@ while ctr.run() do gfx.start(gfx.BOTTOM) gfx.text(3, 3, "FPS: "..math.ceil(gfx.getFPS())) + gfx.text(3, 3 + lineHeight, "Press select to save.") + gfx.text(3, 3 + lineHeight*2, "Press start to exit.") keyboard.draw(4, 115) diff --git a/sdcard/3ds/ctruLua/editor/syntax.lua b/sdcard/3ds/ctruLua/editor/syntax.lua index 18a47b9..c6845af 100644 --- a/sdcard/3ds/ctruLua/editor/syntax.lua +++ b/sdcard/3ds/ctruLua/editor/syntax.lua @@ -50,7 +50,7 @@ local syntax = { return function(lines, color) local ret = {} - for _, line in ipairs(lines) do + for _, line in ipairs(type(lines) == "table" and lines or {lines}) do local colored = { { line, color.default } } for _, patterns in ipairs(syntax) do @@ -86,5 +86,5 @@ return function(lines, color) table.insert(ret, colored) end - return ret + return type(lines) == "table" and ret or ret[1] end diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua index e4ebd42..44f5f77 100644 --- a/sdcard/3ds/ctruLua/libs/filepicker.lua +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -7,7 +7,7 @@ local externalConfig local function gfxPrepare() local old = {gfx.get3D(), gfx.color.getDefault(), gfx.color.getBackground(), - gfx.font.getDefault(), gfx.getTextSize()} + gfx.font.getDefault(), gfx.font.getSize()} local mono = gfx.font.load(ctr.root .. "resources/VeraMono.ttf") @@ -15,7 +15,7 @@ local function gfxPrepare() gfx.color.setDefault(0xFFFDFDFD) gfx.color.setBackground(0xFF333333) gfx.font.setDefault(mono) - gfx.setTextSize(12) + gfx.font.setSize(12) return old end @@ -25,7 +25,7 @@ local function gfxRestore(state) gfx.color.setDefault(state[2]) gfx.color.setBackground(state[3]) gfx.font.setDefault(state[4]) - gfx.setTextSize(state[5]) + gfx.font.setSize(state[5]) end local function systemBindings(bindings) From 358b68c643c50d6fb431bf463475b49df3468be1 Mon Sep 17 00:00:00 2001 From: Reuh Date: Fri, 22 Apr 2016 13:42:59 +0200 Subject: [PATCH 32/46] Renamed item.fileSize to item.size in fs.list() return value, enabled 3D in the editor, added a lot of missing documentation For the return values, I followed this rule: if the fuction returns true on success, it should return false, error on error; for every other case it should return nil, error on error. Also, who doesn't want to edit code in 3D ? The line depth depends on the indentation level. --- sdcard/3ds/ctruLua/editor/main.lua | 62 +++++++++++++++----------- sdcard/3ds/ctruLua/libs/filepicker.lua | 2 +- source/cam.c | 3 ++ source/font.c | 6 ++- source/fs.c | 22 +++++---- source/httpc.c | 34 ++++++++++---- source/ir.c | 28 +++++++++--- source/map.c | 6 ++- source/mic.c | 7 ++- source/ptm.c | 4 +- source/qtm.c | 11 +++-- source/socket.c | 16 +++++-- source/texture.c | 14 +++--- 13 files changed, 143 insertions(+), 72 deletions(-) diff --git a/sdcard/3ds/ctruLua/editor/main.lua b/sdcard/3ds/ctruLua/editor/main.lua index 5685fb5..e247583 100644 --- a/sdcard/3ds/ctruLua/editor/main.lua +++ b/sdcard/3ds/ctruLua/editor/main.lua @@ -44,9 +44,36 @@ local fileModified = false local function displayedText(text) return text:gsub("\t", " "), nil end +local function drawTop(eye) + -- Depth multiplicator. Multiply by a positive and add to x to add depth; multiply by a negative and add to x to go out of the screen. + local function d(mul) return math.floor(mul * hid.pos3d() * eye) end + + -- Lines + local sI = math.floor(scrollY / lineHeight) + if sI < 1 then sI = 1 end + + local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight) + if eI > #lines then eI = #lines end + + for i = sI, eI, 1 do + local x = -scrollX + local y = -scrollY+ (i-1)*lineHeight + + for _,colored in ipairs(coloredLines[i]) do + local str = displayedText(colored[1]) + gfx.text(x + d(#(lines[i]:match("^%s+") or "")*3), y, str, fontSize, colored[2]) + x = x + font:width(str) + end + end + + -- Cursor + local curline = lines[cursorY] + gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))) + d(#(curline:match("^%s+") or "")*3), + -scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor) +end -- Set defaults -gfx.set3D(false) +gfx.set3D(true) gfx.color.setDefault(color.default) gfx.color.setBackground(color.background) gfx.font.setDefault(font) @@ -178,32 +205,17 @@ while ctr.run() do end -- Draw - gfx.start(gfx.TOP) - - -- Lines - local sI = math.floor(scrollY / lineHeight) - if sI < 1 then sI = 1 end - - local eI = math.ceil((scrollY + gfx.TOP_HEIGHT) / lineHeight) - if eI > #lines then eI = #lines end - - for i = sI, eI, 1 do - local x = -scrollX - local y = -scrollY+ (i-1)*lineHeight - - for _,colored in ipairs(coloredLines[i]) do - local str = displayedText(colored[1]) - gfx.text(x, y, str, fontSize, colored[2]) - x = x + font:width(str) - end - end - - -- Cursor - local curline = lines[cursorY] - gfx.rectangle(-scrollX+ font:width(displayedText(curline:sub(1, (utf8.offset(curline, cursorX) or 0)-1))), - -scrollY+ (cursorY-1)*lineHeight, 1, lineHeight, 0, color.cursor) + local is3D = hid.pos3d() > 0 + gfx.start(gfx.TOP, gfx.LEFT) + drawTop(is3D and 0 or -1) gfx.stop() + + if is3D then + gfx.start(gfx.TOP, gfx.RIGHT) + drawTop(1) + gfx.stop() + end gfx.start(gfx.BOTTOM) diff --git a/sdcard/3ds/ctruLua/libs/filepicker.lua b/sdcard/3ds/ctruLua/libs/filepicker.lua index 44f5f77..da4760d 100644 --- a/sdcard/3ds/ctruLua/libs/filepicker.lua +++ b/sdcard/3ds/ctruLua/libs/filepicker.lua @@ -115,7 +115,7 @@ local function drawBottom(externalConfig, workingDirectoryScroll, selected) gfx.text(1, 15, selectedFile.name, 12) if not selectedFile.isDirectory then - gfx.text(1, 45, tostring(selectedFile.fileSize) .. "B", 12, 0xFF727272) + gfx.text(1, 45, tostring(selectedFile.size) .. "B", 12, 0xFF727272) end local binding, pattern = getBinding(selectedFile, bindings) diff --git a/source/cam.c b/source/cam.c index 961d26f..4d756c5 100644 --- a/source/cam.c +++ b/source/cam.c @@ -21,6 +21,9 @@ The `cam` module. /*** Initialize the camera module. @function init +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int cam_init(lua_State *L) { Result ret = camInit(); diff --git a/source/font.c b/source/font.c index a29f356..bac5c44 100644 --- a/source/font.c +++ b/source/font.c @@ -1,5 +1,5 @@ /*** -The `font` module +The `gfx.font` module @module ctr.gfx.font @usage local font = require("ctr.gfx.font") */ @@ -21,7 +21,9 @@ u32 textSize = 9; Load a TTF font. @function load @tparam string path path to the file -@treturn font the loaded font. +@treturn[1] font the loaded font. +@treturn[2] nil if an error occurred +@treturn[2] string error message */ static int font_load(lua_State *L) { const char *path = luaL_checkstring(L, 1); diff --git a/source/fs.c b/source/fs.c index 54d67d6..bc08fdd 100644 --- a/source/fs.c +++ b/source/fs.c @@ -52,14 +52,16 @@ const char* prefix_path(const char* path) { Lists a directory contents (unsorted). @function list @tparam string path the directory we wants to list the content -@treturn table the item list. Each item is a table like: +@treturn[1] table the item list. Each item is a table like: ` { name = "Item name.txt", isDirectory = false, - fileSize = 321 -- (integer) in bytes + size = 321 -- (integer) item size, in bytes } ` +@treturn[2] nil if an error occurred +@treturn[2] string error message */ static int fs_list(lua_State *L) { const char* basepath = prefix_path(luaL_checkstring(L, 1)); @@ -80,8 +82,8 @@ static int fs_list(lua_State *L) { DIR* dir = opendir(path); if (dir == NULL) { if (shouldFreePath) free(path); - lua_pushboolean(L, false); - lua_pushstring(L, strerror(errno)); + lua_pushnil(L); + lua_pushfstring(L, "Can't open directory: %s (%s)", strerror(errno), errno); return 2; } errno = 0; @@ -96,21 +98,17 @@ static int fs_list(lua_State *L) { if (entry->d_type==DT_REG) { // Regular files: check size char* filepath = malloc(strlen(path)+strlen(entry->d_name)+1); - if (filepath == NULL) { + if (filepath == NULL) luaL_error(L, "Memory allocation error"); - } - memset(filepath, 0, strlen(path)+strlen(entry->d_name)+1); - strcpy(filepath, path); strcat(filepath, entry->d_name); + struct stat stats; if (stat(filepath, &stats)) { free(filepath); if (shouldFreePath) free(path); luaL_error(L, "Stat error: %s (%d)", strerror(errno), errno); - lua_pushboolean(L, false); - lua_pushstring(L, strerror(errno)); - return 2; + return 0; } else { lua_pushinteger(L, stats.st_size); } @@ -118,7 +116,7 @@ static int fs_list(lua_State *L) { } else { // Everything else: 0 bytes lua_pushinteger(L, 0); } - lua_setfield(L, -2, "fileSize"); + lua_setfield(L, -2, "size"); lua_seti(L, -2, i); i++; diff --git a/source/httpc.c b/source/httpc.c index ef70afc..97ad4a0 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -40,6 +40,9 @@ Open an url in the context. @function :open @tparam string url the url to open @tparam[opt="GET"] string method method to use; can be `"GET"`, `"POST"`, `"HEAD"`, `"PUT"` or `"DELETE"` +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int httpc_open(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -59,7 +62,7 @@ static int httpc_open(lua_State *L) { ret = httpcOpenContext(context, method, url, 0); if (ret != 0) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } @@ -72,6 +75,9 @@ Add a field in the request header. @function :addRequestHeaderField @tparam string name Name of the field @tparam string value Value of the field +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int httpc_addRequestHeaderField(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -80,7 +86,7 @@ static int httpc_addRequestHeaderField(lua_State *L) { Result ret = httpcAddRequestHeaderField(context, name ,value); if (ret != 0) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } @@ -91,6 +97,9 @@ static int httpc_addRequestHeaderField(lua_State *L) { /*** Begin a request to get the content at the URL. @function :beginRequest +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int httpc_beginRequest(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -98,7 +107,7 @@ static int httpc_beginRequest(lua_State *L) { ret = httpcBeginRequest(context); if (ret != 0) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } @@ -109,7 +118,9 @@ static int httpc_beginRequest(lua_State *L) { /*** Return the status code returned by the request. @function :getStatusCode -@treturn number the status code +@treturn[1] integer the status code +@treturn[2] nil in case of error +@treturn[2] integer error code */ static int httpc_getStatusCode(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -143,7 +154,9 @@ static int httpc_getDownloadSize(lua_State *L) { /*** Download and return the data of the context. @function :downloadData -@treturn string data +@treturn[1] string data +@treturn[2] nil in case of error +@treturn[2] integer error code */ static int httpc_downloadData(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -167,9 +180,9 @@ static int httpc_downloadData(lua_State *L) { } lua_pushstring(L, (char*)buff); - //free(buff); - lua_pushinteger(L, size); // only for test purposes. - return 2; + //free(buff); FIXME we need to free this buffer at some point ? + //lua_pushinteger(L, size); // only for test purposes. + return 1; } /*** @@ -223,6 +236,9 @@ static int httpc_getResponseHeader(lua_State *L) { Add a trusted RootCA cert to a context. @function :addTrustedRootCA @tparam string DER certificate +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int httpc_addTrustedRootCA(lua_State *L) { httpcContext *context = lua_touserdata(L, 1); @@ -231,7 +247,7 @@ static int httpc_addTrustedRootCA(lua_State *L) { Result ret = httpcAddTrustedRootCA(context, cert, certsize); if (ret != 0) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } diff --git a/source/ir.c b/source/ir.c index 86d85fe..0144bfb 100644 --- a/source/ir.c +++ b/source/ir.c @@ -37,6 +37,9 @@ Bitrate codes list (this is not a part of the module, just a reference) Initialize the IR module. @function init @tparam[opt=6] number bitrate bitrate of the IR module (more informations below) +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int ir_init(lua_State *L) { u8 bitrate = luaL_optinteger(L, 1, 6); @@ -56,6 +59,9 @@ static int ir_init(lua_State *L) { /*** Disable the IR module. @function shutdown +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int ir_shutdown(lua_State *L) { Result ret = IRU_Shutdown(); @@ -74,6 +80,9 @@ Send some data over the IR module. @function send @tparam string data just some data @tparam[opt=false] boolean wait set to `true` to wait until the data is sent. +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int ir_send(lua_State *L) { u8 *data = (u8*)luaL_checkstring(L, 1); @@ -98,7 +107,9 @@ Receive some data from the IR module. @function receive @tparam number size bytes to receive @tparam[opt=false] boolean wait wait until the data is received -@return string data +@treturn[1] string data +@treturn[2] nil in case of error +@treturn[2] integer error code */ static int ir_receive(lua_State *L) { u32 size = luaL_checkinteger(L, 1); @@ -108,7 +119,7 @@ static int ir_receive(lua_State *L) { Result ret = iruRecvData(data, size, 0x00, &transfercount, wait); if (ret) { - lua_pushboolean(L, false); + lua_pushnil(L); lua_pushinteger(L, ret); return 2; } @@ -122,6 +133,9 @@ static int ir_receive(lua_State *L) { Set the bitrate of the communication. @function setBitRate @tparam number bitrate new bitrate for the communication +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int ir_setBitRate(lua_State *L) { u8 bitrate = luaL_checkinteger(L, 1); @@ -133,20 +147,24 @@ static int ir_setBitRate(lua_State *L) { return 2; } - return 0; + lua_pushboolean(L, true); + + return 1; } /*** Return the actual bitrate of the communication. @function getBitRate -@treturn number actual bitrate +@treturn[1] number actual bitrate +@treturn[2] nil in case of error +@treturn[2] integer error code */ static int ir_getBitRate(lua_State *L) { u8 bitrate = 0; Result ret = IRU_GetBitRate(&bitrate); if (ret) { - lua_pushboolean(L, false); + lua_pushnil(L); lua_pushinteger(L, ret); return 2; } diff --git a/source/map.c b/source/map.c index 6509d0c..c92242d 100644 --- a/source/map.c +++ b/source/map.c @@ -1,5 +1,5 @@ /*** -The `map` module. +The `gfx.map` module. Tile coordinates start at x=0,y=0. @module ctr.gfx.map @usage local map = require("ctr.gfx.map") @@ -47,7 +47,9 @@ Load a map from a file. @tparam texture tileset containing the tileset @tparam number tileWidth tile width @tparam number tileHeight tile height -@treturn map loaded map object +@treturn[1] map loaded map object +@treturn[2] nil in case of error +@treturn[2] string error message */ static int map_load(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 2, "LTexture"); diff --git a/source/mic.c b/source/mic.c index ed9d308..0e3f19a 100644 --- a/source/mic.c +++ b/source/mic.c @@ -20,20 +20,23 @@ u32 bufferSize = 0; Initialize the mic module. @function init @tparam[opt=0x50000] number bufferSize size of the buffer (must be a multiple of 0x1000) +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer/string error code/message */ static int mic_init(lua_State *L) { bufferSize = luaL_optinteger(L, 1, 0x50000); buff = memalign(0x1000, bufferSize); if (buff == NULL) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushstring(L, "Couldn't allocate buffer"); return 2; } Result ret = micInit(buff, bufferSize); if (ret) { free(buff); - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushinteger(L, ret); return 2; } diff --git a/source/ptm.c b/source/ptm.c index 438acde..5401fe3 100644 --- a/source/ptm.c +++ b/source/ptm.c @@ -117,7 +117,9 @@ Setup the new 3DS CPU features (overclock, 4 cores ...) @newonly @function configureNew3DSCPU @tparam boolean enable enable the New3DS CPU features -@treturn boolean `true` if everything went fine +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int ptm_configureNew3DSCPU(lua_State *L) { u8 conf = false; diff --git a/source/qtm.c b/source/qtm.c index 1356d0d..dfa9ca4 100644 --- a/source/qtm.c +++ b/source/qtm.c @@ -22,6 +22,9 @@ static const struct luaL_Reg qtm_methods[]; /*** Initialize the QTM module. @function init +@treturn[1] boolean `true` if everything went fine +@treturn[2] boolean `false` in case of error +@treturn[2] integer error code */ static int qtm_init(lua_State *L) { Result ret = qtmInit(); @@ -121,8 +124,10 @@ Convert QTM coordinates to screen coordinates @tparam number coordinates index @tparam[opt=400] number screenWidth specify a screen width @tparam[opt=320] number screenHeight specify a screen height -@treturn number screen X coordinate -@treturn number screen Y coordinate +@treturn[1] number screen X coordinate +@treturn[1] number screen Y coordinate +@treturn[2] nil in case of error +@treturn[2] string error message */ static int qtm_convertCoordToScreen(lua_State *L) { qtm_userdata *info = luaL_checkudata(L, 1, "LQTM"); @@ -130,7 +135,7 @@ static int qtm_convertCoordToScreen(lua_State *L) { index = index - 1; // Lua index begins at 1 if (index > 3 || index < 0) { lua_pushnil(L); - lua_pushnil(L); + lua_pushstring(L, "Index must be between 1 and 3"); return 2; } float screenWidth = luaL_optnumber(L, 3, 400.0f); diff --git a/source/socket.c b/source/socket.c index 7eb61f8..27fa28f 100644 --- a/source/socket.c +++ b/source/socket.c @@ -109,7 +109,9 @@ static int socket_shutdown(lua_State *L) { /*** Return a TCP socket. @function tcp -@treturn TCPMaster TCP socket +@treturn[1] TCPMaster TCP socket +@treturn[2] nil in case of error +@treturn[2] string error message */ static int socket_tcp(lua_State *L) { socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); @@ -134,7 +136,9 @@ static int socket_tcp(lua_State *L) { /*** Return an UDP socket. @function udp -@treturn UDPMaster UDP socket +@treturn[1] UDPMaster UDP socket +@treturn[2] nil in case of error +@treturn[2] string error message */ static int socket_udp(lua_State *L) { socket_userdata *userdata = lua_newuserdata(L, sizeof(*userdata)); @@ -371,7 +375,9 @@ If no data is avaible, it returns an empty string (non-blocking). "a" to receive everything, "l" to receive the next line, skipping the end of line, "L" to receive the next line, keeping the end of line. -@treturn string data +@treturn[1] string data +@treturn[2] nil in case of error +@treturn[2] integer error code */ static int socket_receive(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); @@ -440,7 +446,9 @@ static int socket_receive(lua_State *L) { Send some data over the TCP socket. @function :send @tparam string data data to send -@treturn number amount of data sent +@treturn[1] number amount of data sent +@treturn[2] nil in case of error +@treturn[2] integer/string error code/message */ static int socket_send(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); diff --git a/source/texture.c b/source/texture.c index df87a22..f995386 100644 --- a/source/texture.c +++ b/source/texture.c @@ -43,7 +43,9 @@ Load a texture from a file. Supported formats: PNG, JPEG, BMP. @tparam string path path to the image file @tparam[opt=PLACE_RAM] number place where to put the loaded texture @tparam[opt=auto] number type type of the image -@treturn texture the loaded texture object +@treturn[1] texture the loaded texture object +@treturn[2] nil in case of error +@treturn[2] string error message */ static int texture_load(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -272,7 +274,7 @@ Save a texture to a file. @tparam string filename path to the file to save the texture to @tparam[opt=TYPE_PNG] number type type of the image to save. Can be TYPE_PNG or TYPE_BMP @treturn[1] boolean true on success -@treturn[2] nil +@treturn[2] boolean `false` in case of error @treturn[2] string error message */ static int texture_save(lua_State *L) { @@ -284,7 +286,7 @@ static int texture_save(lua_State *L) { if (type == 0) { // PNG FILE* file = fopen(path, "wb"); if (file == NULL) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushstring(L, "Can open file"); return 2; } @@ -317,7 +319,7 @@ static int texture_save(lua_State *L) { } else if (type == 2) { // BMP u32* buff = malloc(texture->texture->width * texture->texture->height * 4); if (buff == NULL) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushstring(L, "Failed to allocate buffer"); return 2; } @@ -330,13 +332,13 @@ static int texture_save(lua_State *L) { free(buff); } else { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushstring(L, "Not a valid type"); return 2; } if (result == 0) { - lua_pushnil(L); + lua_pushboolean(L, false); lua_pushstring(L, "Failed to save the texture"); return 2; } From 4d1e3ec455d9ae2593bac57e33e40e4312ec1178 Mon Sep 17 00:00:00 2001 From: Reuh Date: Fri, 22 Apr 2016 16:45:56 +0200 Subject: [PATCH 33/46] Improved map:draw a lot, fixed GC issues with ctr.map, changed some things internally map:draw should be faster and more flexible. --- source/gfx.c | 28 ++++++++++++++------ source/gfx.h | 12 +++++++++ source/map.c | 73 ++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 source/gfx.h diff --git a/source/gfx.c b/source/gfx.c index 447f8df..f464c7e 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -17,6 +17,7 @@ The `gfx` module. #include #include +#include "gfx.h" #include "font.h" #include "texture.h" @@ -27,6 +28,13 @@ typedef struct { bool isGfxInitialized = false; bool is3DEnabled = false; //TODO: add a function for this in the ctrulib/sf2dlib. +// The scissor-test state, as defined in Lua code. When you apply a new scissor in C, remember to get back to this state to avoid unexpected behaviour. +scissor_state lua_scissor = { + GPU_SCISSOR_DISABLE, + 0, 0, + 0, 0 +}; + /*** The `ctr.gfx.color` module. @table color @@ -385,17 +393,21 @@ Calls this function without argument to disable the scissor test. */ static int gfx_scissor(lua_State *L) { if (lua_gettop(L) == 0) { - sf2d_set_scissor_test(GPU_SCISSOR_DISABLE, 0, 0, 0, 0); + lua_scissor.mode = GPU_SCISSOR_DISABLE; + lua_scissor.x = 0; + lua_scissor.y = 0; + lua_scissor.width = 0; + lua_scissor.height = 0; } else { - int x = luaL_checkinteger(L, 1); - int y = luaL_checkinteger(L, 2); - int width = luaL_checkinteger(L, 3); - int height = luaL_checkinteger(L, 4); - bool invert = lua_toboolean(L, 5); - - sf2d_set_scissor_test(invert ? GPU_SCISSOR_INVERT : GPU_SCISSOR_NORMAL, x, y, width, height); + lua_scissor.x = luaL_checkinteger(L, 1); + lua_scissor.y = luaL_checkinteger(L, 2); + lua_scissor.width = luaL_checkinteger(L, 3); + lua_scissor.height = luaL_checkinteger(L, 4); + lua_scissor.mode = lua_toboolean(L, 5) ? GPU_SCISSOR_INVERT : GPU_SCISSOR_NORMAL; } + sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height); + return 0; } diff --git a/source/gfx.h b/source/gfx.h new file mode 100644 index 0000000..7a7afbe --- /dev/null +++ b/source/gfx.h @@ -0,0 +1,12 @@ +#ifndef GFX_H +#define GFX_H + +typedef struct { + GPU_SCISSORMODE mode; + u32 x; u32 y; + u32 width; u32 height; +} scissor_state; + +extern scissor_state lua_scissor; + +#endif diff --git a/source/map.c b/source/map.c index c92242d..193347d 100644 --- a/source/map.c +++ b/source/map.c @@ -13,7 +13,9 @@ Tile coordinates start at x=0,y=0. #include #include #include +#include +#include "gfx.h" #include "texture.h" typedef struct { @@ -59,7 +61,16 @@ static int map_load(lua_State *L) { map_userdata *map = lua_newuserdata(L, sizeof(map_userdata)); luaL_getmetatable(L, "LMap"); lua_setmetatable(L, -2); - + + // Block GC of the texture by keeping a reference to it in the registry + // registry[map_userdata] = texture_userdata + lua_pushnil(L); + lua_copy(L, -2, -1); // map_userdata + lua_pushnil(L); + lua_copy(L, 2, -1); // texture_userdata + lua_settable(L, LUA_REGISTRYINDEX); + + // Init userdata fields map->texture = texture; map->tileSizeX = tileSizeX; map->tileSizeY = tileSizeY; @@ -158,50 +169,81 @@ Map object */ /*** -Draw a map. +Draw (a part of) the map on the screen. @function :draw -@tparam number x X position -@tparam number y Y position -@within Methods +@tparam integer x X top-left coordinate to draw the map on the screen (pixels) +@tparam integer y Y top-left coordinate to draw the map on the screen (pixels) +@tparam[opt=0] integer offsetX drawn area X start coordinate on the map (pixels) (x=0,y=0 correspond to the first tile top-left corner) +@tparam[opt=0] integer offsetY drawn area Y start coordinate on the map (pixels) +@tparam[opt=400] integer width width of the drawn area on the map (pixels) +@tparam[opt=240] integer height height of the drawn area on the map (pixels) +@usage +-- This will draw on the screen at x=5,y=5 a part of the map. The part is the rectangle on the map starting at x=16,y=16 and width=32,height=48. +-- For example, if you use 16x16 pixel tiles, this will draw the tiles from 1,1 (top-left corner of the rectangle) to 2,3 (bottom-right corner). +map:draw(5, 5, 16, 16, 32, 48) */ static int map_draw(lua_State *L) { map_userdata *map = luaL_checkudata(L, 1, "LMap"); int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); + int offsetX = luaL_optinteger(L, 4, 0); + int offsetY = luaL_optinteger(L, 5, 0); + int width = luaL_optinteger(L, 6, 400); + int height = luaL_optinteger(L, 7, 240); + + int xI = fmax(floor((double)offsetX / map->tileSizeX), 0); // initial tile X + int xF = fmin(ceil((double)(offsetX + width) / map->tileSizeX), map->width); // final tile X + + int yI = fmax(floor((double)offsetY / map->tileSizeY), 0); // initial tile Y + int yF = fmin(ceil((double)(offsetY + height) / map->tileSizeY), map->height); // final tile Y + + if (sf2d_get_current_screen() == GFX_TOP) + sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 400 - x), fmin(height, 240 - y)); // Scissor test doesn't work when x/y + width > screenWidth/Height + else + sf2d_set_scissor_test(GPU_SCISSOR_NORMAL, x, y, fmin(width, 320 - x), fmin(height, 240 - y)); + int texX = 0; int texY = 0; - + if (map->texture->blendColor == 0xffffffff) { - for (int xp=0; xpwidth; xp++) { - for (int yp=0; ypheight; yp++) { + for (int xp = xI; xp < xF; xp++) { + for (int yp = yI; yp < yF; yp++) { u16 tile = getTile(map, xp, yp); getTilePos(map, tile, &texX, &texY); - sf2d_draw_texture_part(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY); + sf2d_draw_texture_part(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY); } } } else { - for (int xp=0; xpwidth; xp++) { - for (int yp=0; ypheight; yp++) { + for (int xp = xI; xp < xF; xp++) { + for (int yp = yI; yp < yF; yp++) { u16 tile = getTile(map, xp, yp); getTilePos(map, tile, &texX, &texY); - sf2d_draw_texture_part_blend(map->texture->texture, (x+(map->tileSizeX*xp)+(xp*map->spaceX)), (y+(map->tileSizeY*yp)+(yp*map->spaceY)), texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor); + sf2d_draw_texture_part_blend(map->texture->texture, x+(map->tileSizeX*xp)+(xp*map->spaceX)-offsetX, y+(map->tileSizeY*yp)+(yp*map->spaceY)-offsetY, texX, texY, map->tileSizeX, map->tileSizeY, map->texture->blendColor); } } } - + + sf2d_set_scissor_test(lua_scissor.mode, lua_scissor.x, lua_scissor.y, lua_scissor.width, lua_scissor.height); + return 0; } /*** Unload a map. @function :unload -@within Methods */ static int map_unload(lua_State *L) { map_userdata *map = luaL_checkudata(L, 1, "LMap"); free(map->data); + // Remove the reference to the texture in the registry + // registry[map_userdata] = nil + lua_pushnil(L); + lua_copy(L, 1, -1); // map_userdata + lua_pushnil(L); + lua_settable(L, LUA_REGISTRYINDEX); + return 0; } @@ -210,7 +252,6 @@ Return the size of a map. @function :getSize @treturn number width of the map, in tiles @treturn number height of the map, in tiles -@within Methods */ static int map_getSize(lua_State *L) { map_userdata *map = luaL_checkudata(L, 1, "LMap"); @@ -227,7 +268,6 @@ Return the value of a tile. @tparam number x X position of the tile (in tiles) @tparam number y Y position of the tile (in tiles) @treturn number value of the tile -@within Methods */ static int map_getTile(lua_State *L) { map_userdata *map = luaL_checkudata(L, 1, "LMap"); @@ -245,7 +285,6 @@ Set the value of a tile. @tparam number x X position of the tile (in tiles) @tparam number y Y position of the tile (in tiles) @tparam number value new value for the tile -@within Methods */ static int map_setTile(lua_State *L) { map_userdata *map = luaL_checkudata(L, 1, "LMap"); From ac47b1d981118dd2ac670286f3deda76834ae85b Mon Sep 17 00:00:00 2001 From: Reuh Date: Sun, 24 Apr 2016 18:21:21 +0200 Subject: [PATCH 34/46] Added missing documentation in texture.load --- source/texture.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/texture.c b/source/texture.c index f995386..ee71ad3 100644 --- a/source/texture.c +++ b/source/texture.c @@ -38,11 +38,11 @@ int getType(const char *name) { // module functions /*** -Load a texture from a file. Supported formats: PNG, JPEG, BMP. +Load a texture from a file. Supported formats: PNG, JPEG, BMP, GIF, PSD, TGA, HDR, PIC, PNM. @function load @tparam string path path to the image file @tparam[opt=PLACE_RAM] number place where to put the loaded texture -@tparam[opt=auto] number type type of the image +@tparam[opt=auto] number type type of the image. This is only used to force loading PNG, JPEG or BMP files with the sfil library; any value between 5 and 250 will force using the stbi library. Leave nil to autodetect the format. @treturn[1] texture the loaded texture object @treturn[2] nil in case of error @treturn[2] string error message From d0fb7042059b76af9ee9aefdc4a325f20b5a03e8 Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 25 Apr 2016 19:43:09 +0200 Subject: [PATCH 35/46] Added hotspot arguments when drawing rotated textures Did related changes to sprite.lua and cleaned stuff. I had to add a function to sf2dlib to make this work, so a make build-sf2dlib is required. Also, we should probably send this change to the original sf2dlib repository... --- libs/sf2dlib/libsf2d/include/sf2d.h | 18 ++++ libs/sf2dlib/libsf2d/source/sf2d_texture.c | 24 +++-- sdcard/3ds/ctruLua/libs/sprite.lua | 10 ++- source/texture.c | 100 +++++++++++++-------- 4 files changed, 103 insertions(+), 49 deletions(-) diff --git a/libs/sf2dlib/libsf2d/include/sf2d.h b/libs/sf2dlib/libsf2d/include/sf2d.h index f282d4f..9322ebb 100644 --- a/libs/sf2dlib/libsf2d/include/sf2d.h +++ b/libs/sf2dlib/libsf2d/include/sf2d.h @@ -588,6 +588,24 @@ void sf2d_draw_texture_part_rotate_scale(const sf2d_texture *texture, int x, int */ void sf2d_draw_texture_part_rotate_scale_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, u32 color); +/** + * @brief Draws a part of a texture, with rotation, scaling, color and hotspot + * @param texture the texture to draw + * @param x the x coordinate to draw the texture to + * @param y the y coordinate to draw the texture to + * @param rad rotation (in radians) to draw the texture + * @param tex_x the starting point (x coordinate) where to start drawing + * @param tex_y the starting point (y coordinate) where to start drawing + * @param tex_w the width to draw from the starting point + * @param tex_h the height to draw from the starting point + * @param x_scale the x scale + * @param y_scale the y scale + * @param center_x the x position of the hotspot + * @param center_y the y position of the hotspot + * @param color the color to blend with the texture + */ +void sf2d_draw_texture_part_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y, u32 color); + /** * @brief Draws a texture blended in a certain depth * @param texture the texture to draw diff --git a/libs/sf2dlib/libsf2d/source/sf2d_texture.c b/libs/sf2dlib/libsf2d/source/sf2d_texture.c index 13345e9..12469ed 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_texture.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_texture.c @@ -599,18 +599,18 @@ void sf2d_draw_texture_part_scale_blend(const sf2d_texture *texture, float x, fl sf2d_draw_texture_part_scale_generic(texture, x, y, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale); } -static inline void sf2d_draw_texture_part_rotate_scale_generic(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale) +static inline void sf2d_draw_texture_part_rotate_scale_hotspot_generic(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y) { sf2d_vertex_pos_tex *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_tex), 8); if (!vertices) return; - int w2 = (tex_w * x_scale)/2.0f; - int h2 = (tex_h * y_scale)/2.0f; + int w = tex_w; + int h = tex_h; - vertices[0].position = (sf2d_vector_3f){(float)-w2, (float)-h2, SF2D_DEFAULT_DEPTH}; - vertices[1].position = (sf2d_vector_3f){(float) w2, (float)-h2, SF2D_DEFAULT_DEPTH}; - vertices[2].position = (sf2d_vector_3f){(float)-w2, (float) h2, SF2D_DEFAULT_DEPTH}; - vertices[3].position = (sf2d_vector_3f){(float) w2, (float) h2, SF2D_DEFAULT_DEPTH}; + vertices[0].position = (sf2d_vector_3f){(float)-center_x * x_scale, (float)-center_y * y_scale, SF2D_DEFAULT_DEPTH}; + vertices[1].position = (sf2d_vector_3f){(float) (w - center_x) * x_scale, (float)-center_y * y_scale, SF2D_DEFAULT_DEPTH}; + vertices[2].position = (sf2d_vector_3f){(float)-center_x * x_scale, (float) (h - center_y) * y_scale, SF2D_DEFAULT_DEPTH}; + vertices[3].position = (sf2d_vector_3f){(float) (w - center_x) * x_scale, (float) h - center_y * y_scale, SF2D_DEFAULT_DEPTH}; float u0 = tex_x/(float)texture->pow2_w; float v0 = tex_y/(float)texture->pow2_h; @@ -650,13 +650,19 @@ static inline void sf2d_draw_texture_part_rotate_scale_generic(const sf2d_textur void sf2d_draw_texture_part_rotate_scale(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale) { sf2d_bind_texture(texture, GPU_TEXUNIT0); - sf2d_draw_texture_part_rotate_scale_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale); + sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, tex_w/2.0f, tex_h/2.0f); } void sf2d_draw_texture_part_rotate_scale_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, u32 color) { sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color); - sf2d_draw_texture_part_rotate_scale_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale); + sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, tex_w/2.0f, tex_h/2.0f); +} + +void sf2d_draw_texture_part_rotate_scale_hotspot_blend(const sf2d_texture *texture, int x, int y, float rad, int tex_x, int tex_y, int tex_w, int tex_h, float x_scale, float y_scale, float center_x, float center_y, u32 color) +{ + sf2d_bind_texture_color(texture, GPU_TEXUNIT0, color); + sf2d_draw_texture_part_rotate_scale_hotspot_generic(texture, x, y, rad, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, center_x, center_y); } static inline void sf2d_draw_texture_depth_generic(const sf2d_texture *texture, int x, int y, signed short z) diff --git a/sdcard/3ds/ctruLua/libs/sprite.lua b/sdcard/3ds/ctruLua/libs/sprite.lua index aec1995..621e4d8 100644 --- a/sdcard/3ds/ctruLua/libs/sprite.lua +++ b/sdcard/3ds/ctruLua/libs/sprite.lua @@ -26,7 +26,7 @@ local function draw(self, x, y, rad) local tsx, tsy = self.texture:getSize() local sx, sy = getBox(tsx, tsy, frame, self.frameSizeX, self.frameSizeY) - self.texture:drawPart(x, y, sx, sy, self.frameSizeX, self.frameSizeY, rad) + self.texture:drawPart(x, y, sx, sy, self.frameSizeX, self.frameSizeY, rad, self.offsetX, self.offsetY) return frame end @@ -52,12 +52,19 @@ local function resetTimer(self) self.frameTimer = ctr.time() end +local function setOffset(self, x, y) + self.offsetX = x or 0 + self.offsetY = y or self.offsetX +end + -- Sprite object constructor function mod.new(texture, fsx, fsy) return { texture = texture, frameSizeX = fsx, frameSizeY = fsy, + offsetX = 0, + offsetY = 0, animations = {}, currentAnimation = 0, currentFrame = 1, @@ -66,6 +73,7 @@ function mod.new(texture, fsx, fsy) draw = draw, addAnimation = addAnimation, setAnimation = setAnimation, + setOffset = setOffset, resetTimer = resetTimer, } end diff --git a/source/texture.c b/source/texture.c index f995386..84467de 100644 --- a/source/texture.c +++ b/source/texture.c @@ -54,10 +54,10 @@ static int texture_load(lua_State *L) { texture_userdata *texture; texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture)); - + luaL_getmetatable(L, "LTexture"); lua_setmetatable(L, -2); - + if (type==3) type = getType(path); if (type==0) { //PNG texture->texture = sfil_load_PNG_file(path, place); @@ -76,17 +76,17 @@ static int texture_load(lua_State *L) { texture->texture = sf2d_create_texture_mem_RGBA8(data, w, h, TEXFMT_RGBA8, place); free(data); } - + if (texture->texture == NULL) { lua_pushnil(L); lua_pushstring(L, "No such file"); return 2; } - + texture->scaleX = 1.0f; texture->scaleY = 1.0f; texture->blendColor = 0xffffffff; - + return 1; } @@ -102,20 +102,20 @@ static int texture_new(lua_State *L) { int w = luaL_checkinteger(L, 1); int h = luaL_checkinteger(L, 2); u8 place = luaL_checkinteger(L, 3); - + texture_userdata *texture; texture = (texture_userdata *)lua_newuserdata(L, sizeof(*texture)); - + luaL_getmetatable(L, "LTexture"); lua_setmetatable(L, -2); - + texture->texture = sf2d_create_texture(w, h, TEXFMT_RGBA8, place); sf2d_texture_tile32(texture->texture); - + texture->scaleX = 1.0f; texture->scaleY = 1.0f; texture->blendColor = 0xffffffff; - + return 1; } @@ -127,35 +127,41 @@ Texture object /*** Draw a texture. @function :draw -@tparam number x X position -@tparam number y Y position -@tparam[opt=0.0] number rad rotation of the texture (in radians) +@tparam integer x X position +@tparam integer y Y position +@tparam[opt=0.0] number rad rotation of the texture around the hotspot (in radians) +@tparam[opt=0.0] number hotspotX the hostpot X coordinate +@tparam[opt=0.0] number hotspotY the hostpot Y coordinate */ static int texture_draw(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); float rad = luaL_optnumber(L, 4, 0.0f); - + float hotspotX = luaL_optnumber(L, 5, 0.0f); + float hotspotY = luaL_optnumber(L, 6, 0.0f); + if (rad == 0.0f && texture->scaleX == 1.0f && texture->scaleY == 1.0f && texture->blendColor == 0xffffffff) { - sf2d_draw_texture(texture->texture, x, y); + sf2d_draw_texture(texture->texture, x - hotspotX, y - hotspotY); } else { - sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, 0, 0, texture->texture->width, texture->texture->height, texture->scaleX, texture->scaleY, texture->blendColor); + sf2d_draw_texture_rotate_scale_hotspot_blend(texture->texture, x, y, rad, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor); } - + return 0; } /*** Draw a part of the texture @function :drawPart -@tparam number x X position -@tparam number y Y position -@tparam number sx X position of the beginning of the part -@tparam number sy Y position of the beginning of the part -@tparam number w width of the part -@tparam number h height of the part -@tparam[opt=0.0] number rad rotation of the part (in radians) +@tparam integer x X position +@tparam integer y Y position +@tparam integer sx X position of the beginning of the part +@tparam integer sy Y position of the beginning of the part +@tparam integer w width of the part +@tparam integer h height of the part +@tparam[opt=0.0] number rad rotation of the part around the hotspot (in radians) +@tparam[opt=0.0] number hotspotX the hostpot X coordinate +@tparam[opt=0.0] number hotspotY the hostpot Y coordinate */ static int texture_drawPart(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); @@ -166,9 +172,11 @@ static int texture_drawPart(lua_State *L) { int w = luaL_checkinteger(L, 6); int h = luaL_checkinteger(L, 7); int rad = luaL_optnumber(L, 8, 0.0f); - - sf2d_draw_texture_part_rotate_scale_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, texture->blendColor); - + float hotspotX = luaL_optnumber(L, 9, 0.0f); + float hotspotY = luaL_optnumber(L, 10, 0.0f); + + sf2d_draw_texture_part_rotate_scale_hotspot_blend(texture->texture, x, y, rad, sx, sy, w, h, texture->scaleX, texture->scaleY, hotspotX, hotspotY, texture->blendColor); + return 0; } @@ -193,12 +201,12 @@ Unload a texture. */ static int texture_unload(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); - + if (texture->texture == NULL) return 0; sf2d_free_texture(texture->texture); texture->texture = NULL; - + return 0; } @@ -206,16 +214,16 @@ static int texture_unload(lua_State *L) { Rescale the texture. The default scale is `1.0`. @function :scale @tparam number scaleX new scale of the width -@tparam number scaleY new scale of the height +@tparam[opt=scaleX] number scaleY new scale of the height */ static int texture_scale(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); float sx = luaL_checknumber(L, 2); - float sy = luaL_checknumber(L, 3); - + float sy = luaL_optnumber(L, 3, sx); + texture->scaleX = sx; texture->scaleY = sy; - + return 0; } @@ -230,9 +238,9 @@ static int texture_getPixel(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); - + lua_pushinteger(L, sf2d_get_pixel(texture->texture, x, y)); - + return 1; } @@ -248,9 +256,9 @@ static int texture_setPixel(lua_State *L) { int x = luaL_checkinteger(L, 2); int y = luaL_checkinteger(L, 3); u32 color = luaL_checkinteger(L, 4); - + sf2d_set_pixel(texture->texture, x, y, color); - + return 0; } @@ -262,12 +270,25 @@ Set the blend color of the texture. static int texture_setBlendColor(lua_State *L) { texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); u32 color = luaL_checkinteger(L, 2); - + texture->blendColor = color; - + return 0; } +/*** +Get the blend color of the texture. +@function :getBlendColor +@treturn number the blend color +*/ +static int texture_getBlendColor(lua_State *L) { + texture_userdata *texture = luaL_checkudata(L, 1, "LTexture"); + + lua_pushinteger(L, texture->blendColor); + + return 1; +} + /*** Save a texture to a file. @function :save @@ -357,6 +378,7 @@ static const struct luaL_Reg texture_methods[] = { { "getPixel", texture_getPixel }, { "setPixel", texture_setPixel }, { "setBlendColor", texture_setBlendColor }, + { "getBlendColor", texture_getBlendColor }, { "save", texture_save }, { "__gc", texture_unload }, {NULL, NULL} From f554f53a47d08ce79e0dfb153b48ae91180fc45c Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 25 Apr 2016 21:02:32 +0200 Subject: [PATCH 36/46] ctr.time() returns a negative value; updated documentation and sprite.lua --- sdcard/3ds/ctruLua/libs/sprite.lua | 2 +- source/ctr.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sdcard/3ds/ctruLua/libs/sprite.lua b/sdcard/3ds/ctruLua/libs/sprite.lua index 621e4d8..7e82555 100644 --- a/sdcard/3ds/ctruLua/libs/sprite.lua +++ b/sdcard/3ds/ctruLua/libs/sprite.lua @@ -68,7 +68,7 @@ function mod.new(texture, fsx, fsy) animations = {}, currentAnimation = 0, currentFrame = 1, - frameTimer = 0, + frameTimer = ctr.time(), draw = draw, addAnimation = addAnimation, diff --git a/source/ctr.c b/source/ctr.c index c545a84..80d36f1 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -140,11 +140,23 @@ static int ctr_run(lua_State *L) { } /*** -Return the number of milliseconds since 1st Jan 1900 00:00. +Return the number of milliseconds spent since some point in time. +This can be used to measure a duration with milliseconds precision; however this can't be used to get the current time or date. +See Lua's os.date() for this use. +For various reasons (see the C source), this will actually returns a negative value. @function time @treturn number milliseconds +@usage +-- Measuring a duration: +local startTime = ctr.time() +-- do stuff +local duration = ctr.time() - startTime */ static int ctr_time(lua_State *L) { + // osGetTime actually returns the number of seconds elapsed since 1st Jan 1900 00:00. + // However, it returns a u64, we build Lua with 32bits numbers, and every number is signed in Lua, so this obvioulsy doesn't work + // and actually returns a negative value. It still works for durations however. Because having the date and time with millisecond-presion + // doesn't really seem useful and changing ctrµLua's API to work on 64bits numbers will take a long time, we choosed to keep this as-is. lua_pushinteger(L, osGetTime()); return 1; From 707b1a451ee79c546f0363e01c17a4dd11277902 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Tue, 26 Apr 2016 14:44:18 +0200 Subject: [PATCH 37/46] Added the ctr.uds lib (3DS-to-3DS wireless communication), Added a function to add ssl certificates for HTTP contexts, Minor documentation update. The UDS lib is completely untested, because I only have 1 3DS. --- source/ctr.c | 9 + source/httpc.c | 17 ++ source/socket.c | 2 +- source/uds.c | 589 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 source/uds.c diff --git a/source/ctr.c b/source/ctr.c index 80d36f1..62e1a38 100644 --- a/source/ctr.c +++ b/source/ctr.c @@ -128,6 +128,14 @@ The `ctr.thread` module. */ void load_thread_lib(lua_State *L); +/*** +The `ctr.uds` module. +@table uds +@see ctr.uds +*/ +void load_uds_lib(lua_State *L); +void unload_uds_lib(lua_State *L); + /*** Return whether or not the program should continue. @function run @@ -198,6 +206,7 @@ struct { char *name; void (*load)(lua_State *L); void (*unload)(lua_State *L); } { "apt", load_apt_lib, unload_apt_lib }, { "mic", load_mic_lib, NULL }, { "thread", load_thread_lib, NULL }, + { "uds", load_uds_lib, unload_uds_lib }, { NULL, NULL, NULL } }; diff --git a/source/httpc.c b/source/httpc.c index 97ad4a0..d114445 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -275,6 +275,22 @@ static int httpc_setSSLOptions(lua_State *L) { return 0; } +/*** +Add all the default certificates to the context. +@function addDefaultCert +*/ +static int httpc_addDefaultCert(lua_State *L) { + httpcContext *context = lua_touserdata(L, 1); + + httpcAddDefaultCert(context, SSLC_DefaultRootCert_CyberTrust); + httpcAddDefaultCert(context, SSLC_DefaultRootCert_AddTrust_External_CA); + httpcAddDefaultCert(context, SSLC_DefaultRootCert_COMODO); + httpcAddDefaultCert(context, SSLC_DefaultRootCert_USERTrust); + httpcAddDefaultCert(context, SSLC_DefaultRootCert_DigiCert_EV); + + return 0; +} + // object static const struct luaL_Reg httpc_methods[] = { {"open", httpc_open }, @@ -289,6 +305,7 @@ static const struct luaL_Reg httpc_methods[] = { {"getResponseHeader", httpc_getResponseHeader }, {"addTrustedRootCA", httpc_addTrustedRootCA }, {"setSSLOptions", httpc_setSSLOptions }, + {"addDefaultCert", httpc_addDefaultCert }, {NULL, NULL} }; diff --git a/source/socket.c b/source/socket.c index 27fa28f..08d30c8 100644 --- a/source/socket.c +++ b/source/socket.c @@ -92,7 +92,7 @@ static int socket_init(lua_State *L) { } /*** -Disable the socket module. Must be called before exiting ctrµLua. +Disable the socket module. @function shutdown */ static int socket_shutdown(lua_State *L) { diff --git a/source/uds.c b/source/uds.c new file mode 100644 index 0000000..3139fbf --- /dev/null +++ b/source/uds.c @@ -0,0 +1,589 @@ +/*** +The `uds` module. Used for 3DS-to-3DS wireless communication. +The default wlancommID is 0x637472c2. +@module ctr.uds +@usage local uds = require("ctr.uds") +*/ + +#include +#include +#include + +#include <3ds/result.h> +#include <3ds/types.h> +#include <3ds/services/uds.h> + +#include +#include + +bool initStateUDS = false; + +udsBindContext bind = {0}; +udsNetworkStruct network = {0}; +u8 data_channel = 1; + +/*** +Initialize the UDS module. +@function init +@tparam[opt=0x3000] number context size in bytes, must be a multiple of 0x1000 +@tparam[opt=3DS username] string username UTF-8 username on the network +@treturn[1] boolean `true` on success +@treturn[2] boolean `false` on error +@treturn[2] number error code +*/ +static int uds_init(lua_State *L) { + if (!initStateUDS) { + size_t memSize = luaL_optinteger(L, 1, 0x3000); + const char* username = luaL_optstring(L, 2, NULL); + + Result ret = udsInit(memSize, username); + if (R_FAILED(ret)) { + lua_pushboolean(L, false); + lua_pushinteger(L, ret); + return 2; + } + initStateUDS = true; + } + + lua_pushboolean(L, true); + return 1; +} + +/*** +Disable the UDS module. +@function shutdown +*/ +static int uds_shutdown(lua_State *L) { + udsExit(); + initStateUDS = false; + + return 0; +} + +/*** +Scan for network beacons. +@function scan +@tparam[opt=0x637472c2] number commID application local-WLAN unique ID +@tparam[opt=0] number id8 additional ID to use different network types +@tparam[opt=all] string hostMAC if set, only scan networks from this MAC address (format: `XX:XX:XX:XX:XX:XX`, with hexadecimal values) +@treturn[1] table a table containing beacons objects +@treturn[2] nil +@treturn[2] number/string error code +*/ +static int uds_scan(lua_State *L) { + static const size_t tmpbuffSize = 0x4000; + u32* tmpbuff = malloc(tmpbuffSize); + + udsNetworkScanInfo* networks = NULL; + size_t totalNetworks = 0; + + u32 wlanCommID = luaL_optinteger(L, 1, 0x637472c2); + u8 id8 = luaL_optinteger(L, 2, 0); + + // MAC address conversion + const char* hostMACString = luaL_optstring(L, 3, NULL); + u8* hostMAC; + if (hostMACString != NULL) { + hostMAC = malloc(6*sizeof(u8)); + unsigned int tmpMAC[6]; + if (sscanf(hostMACString, "%x:%x:%x:%x:%x:%x", &tmpMAC[0], &tmpMAC[1], &tmpMAC[2], &tmpMAC[3], &tmpMAC[4], &tmpMAC[5]) != 6) { + free(tmpbuff); + free(hostMAC); + lua_pushnil(L); + lua_pushstring(L, "Bad MAC formating"); + return 2; + } + for (int i=0;i<6;i++) { + hostMAC[i] = tmpMAC[i]; + } + } else { + hostMAC = NULL; + } + + udsConnectionStatus status; + udsGetConnectionStatus(&status); + Result ret = udsScanBeacons(tmpbuff, tmpbuffSize, &networks, &totalNetworks, wlanCommID, id8, hostMAC, (status.status!=0x03)); + free(tmpbuff); + free(hostMAC); + if (R_FAILED(ret)) { + lua_pushnil(L); + lua_pushinteger(L, ret); + return 2; + } + + // Convert the networks to a table of userdatas + lua_createtable(L, 0, totalNetworks); + for (int i=1;i<=totalNetworks;i++) { + udsNetworkScanInfo* beacon = lua_newuserdata(L, sizeof(udsNetworkScanInfo)); + luaL_getmetatable(L, "LUDSBeaconScan"); + lua_setmetatable(L, -2); + memcpy(beacon, &networks[0], sizeof(udsNetworkScanInfo)); + lua_seti(L, -3, i); + } + free(networks); + + return 1; +} + +/*** +Check for data in the receive buffer. +@function available +@treturn boolean `true` if there is data to receive, `false` if not +*/ +static int uds_available(lua_State *L) { + lua_pushboolean(L, udsWaitDataAvailable(&bind, false, false)); + return 1; +} + +/*** +Return a packet from the receive buffer. +@function receive +@tparam[opt=maximum] number size maximum size of the data to receive +@treturn string data, can be an empty string +@treturn number source node, 0 if nothing has been received +*/ +static int uds_receive(lua_State *L) { + size_t maxSize = luaL_optinteger(L, 1, UDS_DATAFRAME_MAXSIZE); + char* buff = malloc(maxSize); + if (buff == NULL) luaL_error(L, "Memory allocation error"); + size_t received = 0; + u16 sourceID = 0; + + Result ret = udsPullPacket(&bind, buff, maxSize, &received, &sourceID); + if (R_FAILED(ret)) { + free(buff); + lua_pushnil(L); + lua_pushinteger(L, ret); + return 2; + } + + lua_pushlstring(L, buff, received); + free(buff); + lua_pushinteger(L, sourceID); + return 2; +} + +/*** +Send a packet to a node. +@function send +@tparam string data data to send +@tparam[opt=BROADCAST] number nodeID nodeID to send the packet to +*/ +static int uds_send(lua_State *L) { + size_t size = 0; + const char *buff = luaL_checklstring(L, 1, &size); + u16 nodeID = luaL_optinteger(L, 2, UDS_BROADCAST_NETWORKNODEID); + + udsSendTo(nodeID, data_channel, 0, buff, size); + + return 0; +} + +/*** +Return information about nodes in the networks +@function getNodesInfo +@treturn tablea table containing nodes informations, as tables (not userdatas). +A node table is like: `{ username = "azerty", nodeID = 12 }` +*/ +static int uds_getNodesInfo(lua_State *L) { + lua_newtable(L); + for (int i=0;inetwork, passphrase, passphraseSize, &bind, recvNetworkNodeID, connType, dataChannel, recvBufferSize); + if (R_FAILED(ret)) { + lua_pushnil(L); + lua_pushinteger(L, ret); + return 2; + } + + lua_pushboolean(L, true); + return 1; +} + +/*** +Disconnect from the network. +@function disconnect +*/ +static int uds_disconnect(lua_State *L) { + udsDisconnectNetwork(); + udsUnbind(&bind); + + return 0; +} + +/*** +Server part +@section server +*/ + +/*** +Create a network. +@function createNetwork +@tparam[opt=""] string passphrase passphrase of the network +@tparam[opt=16] number maxNodes maximum number of nodes that can be connected to the network, including the host (max 16) +@tparam[opt=0x637472c2] number commID application local-WLAN unique ID +@tparam[opt=default] number recvBuffSize size of the buffer that receives data +@tparam[opt=1] number dataChannel data channel of the network; this should be 1 +@treturn[1] boolean `true` on success +@treturn[2] boolean `false` on error +@treturn[2] number error code +*/ +static int uds_createNetwork(lua_State *L) { + size_t passSize = 0; + const char *pass = luaL_optlstring(L, 1, "", &passSize); + u8 maxNodes = luaL_optinteger(L, 2, UDS_MAXNODES); + u32 commID = luaL_optinteger(L, 3, 0x637472c2); + u32 recvBuffSize = luaL_optinteger(L, 4, UDS_DEFAULT_RECVBUFSIZE); + u8 dataChannel = luaL_optinteger(L, 5, 1); + + udsGenerateDefaultNetworkStruct(&network, commID, dataChannel, maxNodes); + Result ret = udsCreateNetwork(&network, pass, passSize, &bind, dataChannel, recvBuffSize); + if (R_FAILED(ret)) { + lua_pushboolean(L, false); + lua_pushinteger(L, ret); + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + +/*** +Set the application data of the created network. +@function setAppData +@tparam string appData application data +@treturn[1] boolean `true` on success +@treturn[2] boolean `false` on error +@treturn[2] number error code +*/ +static int uds_setAppData(lua_State *L) { + size_t size = 0; + const char* data = luaL_checklstring(L, 1, &size); + + Result ret = udsSetApplicationData(data, size); + if (R_FAILED(ret)) { + lua_pushboolean(L, false); + lua_pushinteger(L, ret); + return 2; + } + + lua_pushboolean(L, true); + return 1; +} + +/*** +Destroy the network. +@function destroyNetwork +*/ +static int uds_destroyNetwork(lua_State *L) { + udsDestroyNetwork(); + udsUnbind(&bind); + + return 0; +} + +/*** +Eject all the spectators connected to the network. +@function ejectSpectators +@tparam[opt=false] boolean reallow set to `true` to still allow the spectators to connect +*/ +static int uds_ejectSpectators(lua_State *L) { + bool reallow = false; + if (lua_isboolean(L, 1)) + reallow = lua_toboolean(L, 1); + + udsEjectSpectator(); + + if (reallow) + udsAllowSpectators(); + + return 0; +} + +/*** +Eject a client connected to the network. +@function ejectClient +@tparam[opt=BROADCAST] number nodeID node ID of the client to eject, or `BROADCAST` for all the clients +*/ +static int uds_ejectClient(lua_State *L) { + u16 nodeID = luaL_optinteger(L, 1, UDS_BROADCAST_NETWORKNODEID); + + udsEjectClient(nodeID); + + return 0; +} + +/*** +beaconScan +@section beaconScan +*/ +static const struct luaL_Reg beaconScan_methods[]; + +static int beaconScan___index(lua_State *L) { + udsNetworkScanInfo* beacon = luaL_checkudata(L, 1, "LUDSBeaconScan"); + + if (lua_isstring(L, 2)) { + const char* index = luaL_checkstring(L, 2); + /*** + @tfield integer channel Wifi channel of the beacon + */ + if (!strcmp(index, "channel")) { + lua_pushinteger(L, beacon->datareply_entry.channel); + return 1; + /*** + @tfield string mac MAC address of the beacon (`mac` for lowercase, `MAC` for uppercase) + @usage +beacon.mac -> ab:cd:ef:ab:cd:ef +beacon.MAC -> AB:CD:EF:AB:CD:EF + */ + } else if (!strcmp(index, "mac") && !strcmp(index, "MAC")) { + char* macString = malloc(18); + if (macString == NULL) luaL_error(L, "Out of memory"); + if (index[1] == 'm') { // lowercase + sprintf(macString, "%0x2:%0x2:%0x2:%0x2:%0x2:%0x2", beacon->datareply_entry.mac_address[0], beacon->datareply_entry.mac_address[1], beacon->datareply_entry.mac_address[2], beacon->datareply_entry.mac_address[3], beacon->datareply_entry.mac_address[4], beacon->datareply_entry.mac_address[5]); + } else { + sprintf(macString, "%0X2:%0X2:%0X2:%0X2:%0X2:%0X2", beacon->datareply_entry.mac_address[0], beacon->datareply_entry.mac_address[1], beacon->datareply_entry.mac_address[2], beacon->datareply_entry.mac_address[3], beacon->datareply_entry.mac_address[4], beacon->datareply_entry.mac_address[5]); + } + lua_pushstring(L, macString); + free(macString); + return 1; + /*** + @tfield table nodes a table containing nodes informations, as tables (not userdatas). + A node table is like: `{ username = "azerty", nodeID = 12 }` + */ + } else if (!strcmp(index, "nodes")) { + lua_newtable(L); + for (int i=0;inodes[i])) continue; + lua_createtable(L, 0, 2); + char tmpstr[256]; // 256 is maybe too much ... But we have a lot of RAM. + memset(tmpstr, 0, sizeof(tmpstr)); + udsGetNodeInfoUsername(&beacon->nodes[i], tmpstr); + lua_pushstring(L, tmpstr); + lua_setfield(L, -2, "username"); + lua_pushinteger(L, (&beacon->nodes[i])->NetworkNodeID); + lua_setfield(L, -2, "nodeID"); + + lua_seti(L, -3, i+1); + } + return 1; + /*** + @tfield number id8 id8 of the beacon's network + */ + } else if (!strcmp(index, "id8")) { + lua_pushinteger(L, beacon->network.id8); + return 1; + /*** + @tfield number networkID random ID of the network + */ + } else if (!strcmp(index, "networkID")) { + lua_pushinteger(L, beacon->network.networkID); + return 1; + /*** + @tfield boolean allowSpectators `true` if new spectators are allowed on the network + */ + } else if (!strcmp(index, "allowSpectators")) { + lua_pushboolean(L, !(beacon->network.attributes&UDSNETATTR_DisableConnectSpectators)); + return 1; + /*** + @tfield boolean allowClients `true` if new clients are allowed on the network + */ + } else if (!strcmp(index, "allowClients")) { + lua_pushboolean(L, !(beacon->network.attributes&UDSNETATTR_DisableConnectClients)); + return 1; + // methods + } else { + for (int i=0;beaconScan_methods[i].name;i++) { + if (!strcmp(beaconScan_methods[i].name, index)) { + lua_pushcfunction(L, beaconScan_methods[i].func); + return 1; + } + } + } + } + + lua_pushnil(L); + return 1; +} + +/*** +Return the application data of the beacon +@function :getAppData +@tparam[opt=0x4000] number maxSize maximum application data size to return +@treturn[1] string application data +@treturn[2] nil +@treturn[2] error code +*/ +static int beaconScan_getAppData(lua_State *L) { + udsNetworkScanInfo* beacon = luaL_checkudata(L, 1, "LUDSBeaconScan"); + size_t maxSize = luaL_optinteger(L, 2, 0x4000); + + u8* data = malloc(maxSize); + if (data == NULL) luaL_error(L, "Memory allocation error"); + + size_t size = 0; + udsGetNetworkStructApplicationData(&beacon->network, data, maxSize, &size); + + lua_pushlstring(L, (const char*)data, size); + free(data); + + return 1; +} + +// beaconScan object +static const struct luaL_Reg beaconScan_methods[] = { + {"__index", beaconScan___index }, + {"getAppData", beaconScan_getAppData}, + {NULL, NULL} +}; + +// module functions +static const struct luaL_Reg uds_lib[] = { + {"init", uds_init }, + {"shutdown", uds_shutdown }, + {"scan", uds_scan }, + {"getNodesInfo ", uds_getNodesInfo }, + {"getAppData", uds_getAppData }, + {"connect", uds_connect }, + {"available", uds_available }, + {"send", uds_send }, + {"receive", uds_receive }, + {"disconnect", uds_disconnect }, + {"createNetwork", uds_createNetwork }, + {"setAppData", uds_setAppData }, + {"destroyNetwork", uds_destroyNetwork }, + {"ejectSpectators", uds_ejectSpectators}, + {"ejectClient", uds_ejectClient }, + {NULL, NULL} +}; + +/*** +Constants +@section constants +*/ + +struct { char *name; int value; } uds_constants[] = { + /*** + @field BROADCAST broadcast node ID + */ + {"BROADCAST", UDS_BROADCAST_NETWORKNODEID}, + /*** + @field HOST host node ID + */ + {"HOST", UDS_HOST_NETWORKNODEID }, + /*** + @field CLIENT used to specify a connection as a client + */ + {"CLIENT", UDSCONTYPE_Client }, + /*** + @field SPECTATOR used to specify a connection as a spectator + */ + {"SPECTATOR", UDSCONTYPE_Spectator }, + {NULL, 0} +}; + +int luaopen_uds_lib(lua_State *L) { + luaL_newmetatable(L, "LUDSBeaconScan"); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, beaconScan_methods, 0); + + luaL_newlib(L, uds_lib); + + for (int i = 0; uds_constants[i].name; i++) { + lua_pushinteger(L, uds_constants[i].value); + lua_setfield(L, -2, uds_constants[i].name); + } + + return 1; +} + +void load_uds_lib(lua_State *L) { + luaL_requiref(L, "ctr.uds", luaopen_uds_lib, 0); +} + +void unload_uds_lib(lua_State *L) { + if (initStateUDS) { + udsConnectionStatus status; + udsGetConnectionStatus(&status); + switch (status.status) { + case 0x6: // connected as host + udsDestroyNetwork(); + udsUnbind(&bind); + break; + + case 0x9: // connected as client + case 0xA: // connected as spectator + udsDisconnectNetwork(); + udsUnbind(&bind); + break; + + default: + break; + } + udsExit(); + initStateUDS = false; + } +} From b4ceb200eafe16e1a1ec02564822ba136037432d Mon Sep 17 00:00:00 2001 From: Reuh Date: Fri, 29 Apr 2016 17:55:59 +0200 Subject: [PATCH 38/46] Added gfx.linedRectangle and gfx.linedCircle --- sdcard/3ds/ctruLua/main.lua | 1 + source/gfx.c | 108 +++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/sdcard/3ds/ctruLua/main.lua b/sdcard/3ds/ctruLua/main.lua index 9d21667..a574487 100644 --- a/sdcard/3ds/ctruLua/main.lua +++ b/sdcard/3ds/ctruLua/main.lua @@ -14,6 +14,7 @@ local function displayError(err, trace) gfx.color.setDefault(0xFFFDFDFD) gfx.font.setSize(12) gfx.font.setDefault(gfx.font.load(ctr.root .. "resources/VeraMono.ttf")) + gfx.disableConsole() while ctr.run() do gfx.start(gfx.BOTTOM) diff --git a/source/gfx.c b/source/gfx.c index f464c7e..43a4fb5 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -35,6 +35,14 @@ scissor_state lua_scissor = { 0, 0 }; +// Rotate a point (x,y) around the center (cx,cy) by angle radians. +void rotatePoint(int x, int y, int cx, int cy, float angle, int* outx, int* outy) { + float s = sin(angle), c = cos(angle); + int tx = x - cx, ty = y - cy; + *outx = round(tx * c - ty * s) + cx; + *outy = round(tx * s + ty * c) + cy; +} + /*** The `ctr.gfx.color` module. @table color @@ -227,7 +235,7 @@ static int gfx_point(lua_State *L) { } /*** -Draw a rectangle on the current screen. +Draw a filled rectangle on the current screen. @function rectangle @tparam integer x rectangle origin horizontal coordinate, in pixels @tparam integer y rectangle origin vertical coordinate, in pixels @@ -254,7 +262,51 @@ static int gfx_rectangle(lua_State *L) { } /*** -Draw a circle on the current screen. +Draw a rectangle outline on the current screen. +@function linedRectangle +@tparam integer x rectangle origin horizontal coordinate, in pixels +@tparam integer y rectangle origin vertical coordinate, in pixels +@tparam integer width rectangle width, in pixels +@tparam integer height rectangle height, in pixels +@tparam[opt=1] integer lineWidth line's thickness, in pixels +@tparam[opt=0] number angle rectangle rotation, in radians +@tparam[opt=default color] integer color drawing color +*/ +static int gfx_linedRectangle(lua_State *L) { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int width = luaL_checkinteger(L, 3); + int height = luaL_checkinteger(L, 4); + float lineWidth = luaL_optnumber(L, 5, 1.0f); + + float angle = luaL_optnumber(L, 6, 0); + u32 color = luaL_optinteger(L, 7, color_default); + + // Corner coordinates + int x2 = x + width, y2 = y; + int x3 = x2, y3 = y + height; + int x4 = x, y4 = y3; + + // Rotate corners + if (angle != 0) { + int cx = x + width/2, cy = y + height/2; + rotatePoint(x, y, cx, cy, angle, &x, &y ); + rotatePoint(x2, y2, cx, cy, angle, &x2, &y2); + rotatePoint(x3, y3, cx, cy, angle, &x3, &y3); + rotatePoint(x4, y4, cx, cy, angle, &x4, &y4); + } + + // Draw lines + sf2d_draw_line(x, y, x2, y2, lineWidth, color); + sf2d_draw_line(x2, y2, x3, y3, lineWidth, color); + sf2d_draw_line(x3, y3, x4, y4, lineWidth, color); + sf2d_draw_line(x4, y4, x, y, lineWidth, color); + + return 0; +} + +/*** +Draw a filled circle on the current screen. @function circle @tparam integer x circle center horizontal coordinate, in pixels @tparam integer y circle center vertical coordinate, in pixels @@ -273,6 +325,56 @@ static int gfx_circle(lua_State *L) { return 0; } +/*** +Draw a circle outline on the current screen. +@function linedCircle +@tparam integer x circle center horizontal coordinate, in pixels +@tparam integer y circle center vertical coordinate, in pixels +@tparam integer radius circle radius, in pixels +@tparam[opt=1] integer width line's thickness, in pixels +@tparam[opt=default color] integer color drawing color +*/ +static int gfx_linedCircle(lua_State *L) { + int x0 = luaL_checkinteger(L, 1); + int y0 = luaL_checkinteger(L, 2); + int radius = luaL_checkinteger(L, 3); + float width = luaL_optnumber(L, 4, 1.0f); + + u32 color = luaL_optinteger(L, 5, color_default); + + for (int r = ceil(radius - width/2), maxr = ceil(radius + width/2)-1; r <= maxr; r++) { + // Implementatin of the Andres circle algorithm. + int x = 0; + int y = r; + int d = r - 1; + while (y >= x) { + // Best way to draw a lot of points, 10/10 + sf2d_draw_rectangle(x0 + x , y0 + y, 1, 1, color); + sf2d_draw_rectangle(x0 + y , y0 + x, 1, 1, color); + sf2d_draw_rectangle(x0 - x , y0 + y, 1, 1, color); + sf2d_draw_rectangle(x0 - y , y0 + x, 1, 1, color); + sf2d_draw_rectangle(x0 + x , y0 - y, 1, 1, color); + sf2d_draw_rectangle(x0 + y , y0 - x, 1, 1, color); + sf2d_draw_rectangle(x0 - x , y0 - y, 1, 1, color); + sf2d_draw_rectangle(x0 - y , y0 - x, 1, 1, color); + + if (d >= 2*x) { + d -= 2*x + 1; + x++; + } else if (d < 2*(r-y)) { + d += 2*y - 1; + y--; + } else { + d += 2*(y - x - 1); + y--; + x++; + } + } + } + + return 0; +} + /*** Draw a text on the current screen. @function text @@ -580,7 +682,9 @@ static const struct luaL_Reg gfx_lib[] = { { "line", gfx_line }, { "point", gfx_point }, { "rectangle", gfx_rectangle }, + { "linedRectangle", gfx_linedRectangle }, { "circle", gfx_circle }, + { "linedCircle", gfx_linedCircle }, { "text", gfx_text }, { "wrappedText", gfx_wrappedText }, { "calcBoundingBox", gfx_calcBoundingBox }, From 5494f3d2e5e9c56e592fc994e079eb7128638c22 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Mon, 9 May 2016 23:28:41 +0200 Subject: [PATCH 39/46] Added some sleep mode related functions, fixed the example, fixed some things --- sdcard/3ds/ctruLua/examples/example.lua | 3 ++- source/apt.c | 26 +++++++++++++++++++++++++ source/font.c | 2 ++ source/httpc.c | 3 ++- source/ptm.c | 4 ++-- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/sdcard/3ds/ctruLua/examples/example.lua b/sdcard/3ds/ctruLua/examples/example.lua index 1c2a307..67ed406 100644 --- a/sdcard/3ds/ctruLua/examples/example.lua +++ b/sdcard/3ds/ctruLua/examples/example.lua @@ -10,6 +10,7 @@ local angle = 0 local texture1 = gfx.texture.load(ctr.root.."icon.png"); if not texture1 then error("Giants ducks came from another planet") end +local tWidth, tHeight = texture1:getSize() gfx.color.setBackground(gfx.color.RGBA8(200, 200, 200)) gfx.set3D(true) @@ -66,7 +67,7 @@ while ctr.run() do gfx.text(5, 17, "Hello world, from Lua ! éàçù", 20, gfx.color.RGBA8(0, 0, 0)) gfx.text(5, 50, "Time: "..os.date()) - texture1:draw(280, 80, angle); + texture1:draw(280, 80, angle, tWidth/2, tHeight/2); local cx, cy = hid.circle() gfx.rectangle(40, 90, 60, 60, 0, 0xDDDDDDFF) diff --git a/source/apt.c b/source/apt.c index e860736..bcbc26b 100644 --- a/source/apt.c +++ b/source/apt.c @@ -107,6 +107,30 @@ static int apt_getMenuAppID(lua_State *L) { return 1; } +/*** +Allow or not the system to enter sleep mode. +@function setSleepAllowed +@tparam boolean allowed `true` to allow, `false` to disallow +*/ +static int apt_setSleepAllowed(lua_State *L) { + bool allowed = lua_toboolean(L, 1); + + aptSetSleepAllowed(allowed); + + return 0; +} + +/*** +Check if sleep mode is allowed. +@function isSleepAllowed +@treturn boolean `true` is allowed, false if not. +*/ +static int apt_isSleepAllowed(lua_State *L) { + lua_pushboolean(L, aptIsSleepAllowed()); + + return 1; +} + static const struct luaL_Reg apt_lib[] = { {"openSession", apt_openSession }, {"closeSession", apt_closeSession }, @@ -117,6 +141,8 @@ static const struct luaL_Reg apt_lib[] = { {"setStatusPower", apt_setStatusPower }, {"signalReadyForSleep", apt_signalReadyForSleep}, {"getMenuAppID", apt_getMenuAppID }, + {"setSleepAllowed", apt_setSleepAllowed }, + {"isSleepAllowed", apt_isSleepAllowed }, {NULL, NULL} }; diff --git a/source/font.c b/source/font.c index bac5c44..1110432 100644 --- a/source/font.c +++ b/source/font.c @@ -189,4 +189,6 @@ void unload_font_lib(lua_State *L) { if (luaL_testudata(L, -1, "LFont") != NULL) sftd_free_font(((font_userdata *)lua_touserdata(L, -1))->font); // Unload current font + + lua_pop(L, 1); } diff --git a/source/httpc.c b/source/httpc.c index d114445..87c826d 100644 --- a/source/httpc.c +++ b/source/httpc.c @@ -174,13 +174,14 @@ static int httpc_downloadData(lua_State *L) { ret = httpcDownloadData(context, buff, size, NULL); if (ret != 0) { + free(buff); lua_pushnil(L); lua_pushinteger(L, ret); return 2; } lua_pushstring(L, (char*)buff); - //free(buff); FIXME we need to free this buffer at some point ? + free(buff); //lua_pushinteger(L, size); // only for test purposes. return 1; } diff --git a/source/ptm.c b/source/ptm.c index 5401fe3..125e0ea 100644 --- a/source/ptm.c +++ b/source/ptm.c @@ -45,13 +45,13 @@ static int ptm_shutdown(lua_State *L) { /*** Return the shell state. @function getShellState -@treturn number shell state +@treturn boolean shell state, `true` if open, `false` if closed. */ static int ptm_getShellState(lua_State *L) { u8 out = 0; PTMU_GetShellState(&out); - lua_pushinteger(L, out); + lua_pushboolean(L, out); return 1; } From 20f8cd3cb9fab9f15fc5931baf3e4f4047e81c41 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Mon, 6 Jun 2016 19:17:47 +0200 Subject: [PATCH 40/46] Added "ctr.hid.cstick()", untested with the circle pad pro, works on New 3DS. I'm working on the issue with :bind(), looks like it's not just my code ... --- source/hid.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/source/hid.c b/source/hid.c index 141f49d..ed76ee7 100644 --- a/source/hid.c +++ b/source/hid.c @@ -1,11 +1,12 @@ /*** The `hid` module. -The circle pad pro is supported, it's keys replace de "3ds only" keys +The circle pad pro is supported, it's keys replace the "3ds only" keys @module ctr.hid @usage local hid = require("ctr.hid") */ #include <3ds/types.h> #include <3ds/services/hid.h> +#include <3ds/services/irrst.h> #include #include @@ -175,6 +176,25 @@ static int hid_circle(lua_State *L) { return 2; } +/*** +Return the C-stick position. +`0,0` is the center position, and the stick should return to it if not touched (95% of the time). +Range is from `-146` to `146` on both X and Y, but these are hard to reach. +@newonly +@function cstick +@treturn number X position +@treturn number Y position +*/ +static int hid_cstick(lua_State *L) { + circlePosition pos; + irrstCstickRead(&pos); + + lua_pushinteger(L, pos.dx); + lua_pushinteger(L, pos.dy); + + return 2; +} + /*** Return the accelerometer vector @function accel @@ -243,6 +263,7 @@ static const struct luaL_Reg hid_lib[] = { { "keys", hid_keys }, { "touch", hid_touch }, { "circle", hid_circle }, + { "cstick", hid_cstick }, { "accel", hid_accel }, { "gyro", hid_gyro }, { "volume", hid_volume }, From 4a2c1a7c68d99e1d4c16d486b21208b6922f47e9 Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 27 Jun 2016 13:42:38 +0200 Subject: [PATCH 41/46] Updated ctr.apt to the latest ctrulib, added ctr.apt.isNew3DS() --- source/apt.c | 111 +++++++++++++++++++++++++++----------------------- source/font.c | 3 +- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/source/apt.c b/source/apt.c index bcbc26b..6f2f28c 100644 --- a/source/apt.c +++ b/source/apt.c @@ -13,44 +13,29 @@ Used to manage the applets and application status. #include #include -/*** -Open an APT session. Should only work if you don't use the homebrew menu. -@function openSession -*/ -static int apt_openSession(lua_State *L) { - aptOpenSession(); - return 0; -} - -/*** -Close the current APT session. -@function closeSession -*/ -static int apt_closeSession(lua_State *L) { - aptCloseSession(); - return 0; -} - /*** Set the app status. @function setStatus +@tparam integer status the new app status */ static int apt_setStatus(lua_State *L) { APT_AppStatus status = luaL_checkinteger(L, 1); - + aptSetStatus(status); - + return 0; } /*** Get the app status. @function getStatus +@treturn integer the app status */ static int apt_getStatus(lua_State *L) { APT_AppStatus status = aptGetStatus(); lua_pushinteger(L, status); + return 1; } @@ -60,6 +45,7 @@ Return to the Home menu. */ static int apt_returnToMenu(lua_State *L) { aptReturnToMenu(); + return 0; } @@ -70,8 +56,9 @@ Get the power status. */ static int apt_getStatusPower(lua_State *L) { u32 status = aptGetStatusPower(); - + lua_pushboolean(L, status); + return 1; } @@ -82,9 +69,9 @@ Set the power status. */ static int apt_setStatusPower(lua_State *L) { u32 status = lua_toboolean(L, 1); - + aptSetStatusPower(status); - + return 0; } @@ -94,6 +81,7 @@ Signal that the application is ready for sleeping. */ static int apt_signalReadyForSleep(lua_State *L) { aptSignalReadyForSleep(); + return 0; } @@ -104,6 +92,7 @@ Return the Home menu AppID. */ static int apt_getMenuAppID(lua_State *L) { lua_pushinteger(L, aptGetMenuAppID()); + return 1; } @@ -114,9 +103,9 @@ Allow or not the system to enter sleep mode. */ static int apt_setSleepAllowed(lua_State *L) { bool allowed = lua_toboolean(L, 1); - + aptSetSleepAllowed(allowed); - + return 0; } @@ -127,22 +116,36 @@ Check if sleep mode is allowed. */ static int apt_isSleepAllowed(lua_State *L) { lua_pushboolean(L, aptIsSleepAllowed()); - + + return 1; +} + +/*** +Checks whether the system is a New 3DS. +@function isNew3DS +@treturn boolean `true` if it's a New3DS, false otherwise +*/ +static int apt_isNew3DS(lua_State *L) { + bool isNew3ds; + + APT_CheckNew3DS(&isNew3ds); + + lua_pushboolean(L, isNew3ds); + return 1; } static const struct luaL_Reg apt_lib[] = { - {"openSession", apt_openSession }, - {"closeSession", apt_closeSession }, - {"setStatus", apt_setStatus }, - {"getStatus", apt_getStatus }, - {"returnToMenu", apt_returnToMenu }, - {"getStatusPower", apt_getStatusPower }, - {"setStatusPower", apt_setStatusPower }, - {"signalReadyForSleep", apt_signalReadyForSleep}, - {"getMenuAppID", apt_getMenuAppID }, - {"setSleepAllowed", apt_setSleepAllowed }, - {"isSleepAllowed", apt_isSleepAllowed }, + { "setStatus", apt_setStatus }, + { "getStatus", apt_getStatus }, + { "returnToMenu", apt_returnToMenu }, + { "getStatusPower", apt_getStatusPower }, + { "setStatusPower", apt_setStatusPower }, + { "signalReadyForSleep", apt_signalReadyForSleep }, + { "getMenuAppID", apt_getMenuAppID }, + { "setSleepAllowed", apt_setSleepAllowed }, + { "isSleepAllowed", apt_isSleepAllowed }, + { "isNew3DS", apt_isNew3DS }, {NULL, NULL} }; @@ -268,37 +271,45 @@ struct { char *name; int value; } apt_constants[] = { */ {"APTSIGNAL_HOMEBUTTON", APTSIGNAL_HOMEBUTTON }, /*** - @field APTSIGNAL_PREPARESLEEP + @field APTSIGNAL_HOMEBUTTON2 */ - {"APTSIGNAL_PREPARESLEEP", APTSIGNAL_PREPARESLEEP}, + {"APTSIGNAL_HOMEBUTTON2", APTSIGNAL_HOMEBUTTON2 }, /*** - @field APTSIGNAL_ENTERSLEEP + @field APTSIGNAL_SLEEP_QUERY */ - {"APTSIGNAL_ENTERSLEEP", APTSIGNAL_ENTERSLEEP }, + {"APTSIGNAL_SLEEP_QUERY", APTSIGNAL_SLEEP_QUERY }, + /*** + @field APTSIGNAL_SLEEP_CANCEL + */ + {"APTSIGNAL_SLEEP_CANCEL", APTSIGNAL_SLEEP_CANCEL}, + /*** + @field APTSIGNAL_SLEEP_ENTER + */ + {"APTSIGNAL_SLEEP_ENTER", APTSIGNAL_SLEEP_ENTER }, /*** @field APTSIGNAL_WAKEUP */ - {"APTSIGNAL_WAKEUP", APTSIGNAL_WAKEUP }, + {"APTSIGNAL_SLEEP_WAKEUP", APTSIGNAL_SLEEP_WAKEUP}, /*** - @field APTSIGNAL_ENABLE + @field APTSIGNAL_SHUTDOWN */ - {"APTSIGNAL_ENABLE", APTSIGNAL_ENABLE }, + {"APTSIGNAL_SHUTDOWN", APTSIGNAL_SHUTDOWN }, /*** @field APTSIGNAL_POWERBUTTON */ {"APTSIGNAL_POWERBUTTON", APTSIGNAL_POWERBUTTON }, /*** - @field APTSIGNAL_UTILITY + @field APTSIGNAL_POWERBUTTON2 */ - {"APTSIGNAL_UTILITY", APTSIGNAL_UTILITY }, + {"APTSIGNAL_POWERBUTTON2", APTSIGNAL_POWERBUTTON2}, /*** - @field APTSIGNAL_SLEEPSYSTEM + @field APTSIGNAL_TRY_SLEEP */ - {"APTSIGNAL_SLEEPSYSTEM", APTSIGNAL_SLEEPSYSTEM }, + {"APTSIGNAL_TRY_SLEEP", APTSIGNAL_TRY_SLEEP }, /*** - @field APTSIGNAL_ERROR + @field APTSIGNAL_ORDERTOCLOSE */ - {"APTSIGNAL_ERROR", APTSIGNAL_ERROR }, + {"APTSIGNAL_ORDERTOCLOSE", APTSIGNAL_ORDERTOCLOSE}, /*** @field APTHOOK_ONSUSPEND */ diff --git a/source/font.c b/source/font.c index 1110432..615b299 100644 --- a/source/font.c +++ b/source/font.c @@ -18,7 +18,8 @@ The `gfx.font` module u32 textSize = 9; /*** -Load a TTF font. +Load a font. Supported formats: TTF, OTF, TTC, OTC, WOFF, PFA, PFB, PCF, FNT, BDF, PFR, and others. +ctrµLua support all formats supported by FreeType. See here for a more complete list: http://freetype.org/freetype2/docs/index.html @function load @tparam string path path to the file @treturn[1] font the loaded font. From b47971bfcac5d83d052851170ec20a03abd95c0a Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 27 Jun 2016 15:29:55 +0200 Subject: [PATCH 42/46] Added rectangle gradients, triangles, updated sf2dlib Gradients are done with additional parameters to gfx.rectangle, so old code that gave the function too many parameters and expected them to be ignored may break. --- libs/sf2dlib/libsf2d/.gitignore | 1 + libs/sf2dlib/libsf2d/include/sf2d.h | 45 +++++ libs/sf2dlib/libsf2d/include/sf2d_private.h | 2 + libs/sf2dlib/libsf2d/source/sf2d_draw.c | 187 +++++++++++--------- libs/sf2dlib/libsf2d/source/sf2d_texture.c | 29 ++- source/gfx.c | 129 +++++++++++--- 6 files changed, 280 insertions(+), 113 deletions(-) create mode 100644 libs/sf2dlib/libsf2d/.gitignore diff --git a/libs/sf2dlib/libsf2d/.gitignore b/libs/sf2dlib/libsf2d/.gitignore new file mode 100644 index 0000000..e63ddd8 --- /dev/null +++ b/libs/sf2dlib/libsf2d/.gitignore @@ -0,0 +1 @@ +/Default/ diff --git a/libs/sf2dlib/libsf2d/include/sf2d.h b/libs/sf2dlib/libsf2d/include/sf2d.h index 9322ebb..b6f3277 100644 --- a/libs/sf2dlib/libsf2d/include/sf2d.h +++ b/libs/sf2dlib/libsf2d/include/sf2d.h @@ -67,6 +67,14 @@ typedef enum { TEXFMT_ETC1A4 = 13 } sf2d_texfmt; +/** + * @brief Represents a direction for drawing a gradient + */ + +typedef enum { + SF2D_TOP_TO_BOTTOM, + SF2D_LEFT_TO_RIGHT +} sf2d_gradient_dir; /** * @brief Data allocated on the RAM or VRAM @@ -264,6 +272,18 @@ void sf2d_set_clear_color(u32 color); */ void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color); +/** + * @brief Draws a triangle + * @param x1 x coordinate of a vertex of the triangle + * @param y1 y coordinate of a vertex of the triangle + * @param x2 x coordinate of a vertex of the triangle + * @param y2 y coordinate of a vertex of the triangle + * @param x3 x coordinate of a vertex of the triangle + * @param y3 y coordinate of a vertex of the triangle + * @param color the color to draw the triangle + */ +void sf2d_draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, u32 color); + /** * @brief Draws a rotated rectangle * @param x x coordinate of the top left corner of the rectangle @@ -275,6 +295,31 @@ void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color); */ void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad); +/** + * @brief Draws a rectangle + * @param x x coordinate of the top left corner of the rectangle + * @param y y coordinate of the top left corner of the rectangle + * @param w rectangle width + * @param h rectangle height + * @param color1 the color at the start of the gradient + * @param color2 the color at the end of the gradient + * @param left_to_right determines which direction the gradient is in + */ +void sf2d_draw_rectangle_gradient(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction); + +/** + * @brief Draws a rotated rectangle + * @param x x coordinate of the top left corner of the rectangle + * @param y y coordinate of the top left corner of the rectangle + * @param w rectangle width + * @param h rectangle height + * @param color1 the color at the start of the gradient + * @param color2 the color at the end of the gradient + * @param left_to_right determines which direction the gradient is in + * @param rad rotation (in radians) to draw the rectangle + */ +void sf2d_draw_rectangle_gradient_rotate(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction, float rad); + /** * @brief Draws a filled circle * @param x x coordinate of the center of the circle diff --git a/libs/sf2dlib/libsf2d/include/sf2d_private.h b/libs/sf2dlib/libsf2d/include/sf2d_private.h index 76cf70d..07579ca 100644 --- a/libs/sf2dlib/libsf2d/include/sf2d_private.h +++ b/libs/sf2dlib/libsf2d/include/sf2d_private.h @@ -7,6 +7,8 @@ void GPU_SetDummyTexEnv(u8 num); +void sf2d_draw_rectangle_internal(const sf2d_vertex_pos_col *vertices); + // Vector operations void vector_mult_matrix4x4(const float *msrc, const sf2d_vector_3f *vsrc, sf2d_vector_3f *vdst); diff --git a/libs/sf2dlib/libsf2d/source/sf2d_draw.c b/libs/sf2dlib/libsf2d/source/sf2d_draw.c index 59a792e..a28e818 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_draw.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_draw.c @@ -6,6 +6,30 @@ #define M_PI (3.14159265358979323846) #endif +void sf2d_setup_env_internal(const sf2d_vertex_pos_col* vertices) { + GPU_SetTexEnv( + 0, + GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), + GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), + GPU_TEVOPERANDS(0, 0, 0), + GPU_TEVOPERANDS(0, 0, 0), + GPU_REPLACE, GPU_REPLACE, + 0xFFFFFFFF + ); + + GPU_SetAttributeBuffers( + 2, // number of attributes + (u32*)osConvertVirtToPhys(vertices), + GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE), + 0xFFFC, //0b1100 + 0x10, + 1, //number of buffers + (u32[]){0x0}, // buffer offsets (placeholders) + (u64[]){0x10}, // attribute permutations for each buffer + (u8[]){2} // number of attributes for each buffer + ); +} + void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 color) { sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8); @@ -38,31 +62,25 @@ void sf2d_draw_line(float x0, float y0, float x1, float y1, float width, u32 col vertices[2].color = vertices[0].color; vertices[3].color = vertices[0].color; - GPU_SetTexEnv( - 0, - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVOPERANDS(0, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_REPLACE, GPU_REPLACE, - 0xFFFFFFFF - ); - - GPU_SetAttributeBuffers( - 2, // number of attributes - (u32*)osConvertVirtToPhys(vertices), - GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE), - 0xFFFC, //0b1100 - 0x10, - 1, //number of buffers - (u32[]){0x0}, // buffer offsets (placeholders) - (u64[]){0x10}, // attribute permutations for each buffer - (u8[]){2} // number of attributes for each buffer - ); + sf2d_setup_env_internal(vertices); GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4); } +void sf2d_draw_rectangle_internal(const sf2d_vertex_pos_col *vertices) +{ + sf2d_setup_env_internal(vertices); + + GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4); +} + +void sf2d_draw_triangle_internal(const sf2d_vertex_pos_col *vertices) +{ + sf2d_setup_env_internal(vertices); + + GPU_DrawArray(GPU_TRIANGLES, 0, 3); +} + void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color) { sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8); @@ -78,29 +96,23 @@ void sf2d_draw_rectangle(int x, int y, int w, int h, u32 color) vertices[2].color = vertices[0].color; vertices[3].color = vertices[0].color; - GPU_SetTexEnv( - 0, - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVOPERANDS(0, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_REPLACE, GPU_REPLACE, - 0xFFFFFFFF - ); + sf2d_draw_rectangle_internal(vertices); +} - GPU_SetAttributeBuffers( - 2, // number of attributes - (u32*)osConvertVirtToPhys(vertices), - GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE), - 0xFFFC, //0b1100 - 0x10, - 1, //number of buffers - (u32[]){0x0}, // buffer offsets (placeholders) - (u64[]){0x10}, // attribute permutations for each buffer - (u8[]){2} // number of attributes for each buffer - ); +void sf2d_draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, u32 color) +{ + sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(3 * sizeof(sf2d_vertex_pos_col), 8); + if (!vertices) return; - GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4); + vertices[0].position = (sf2d_vector_3f){(float)x1, (float)y1, SF2D_DEFAULT_DEPTH}; + vertices[1].position = (sf2d_vector_3f){(float)x2, (float)y2, SF2D_DEFAULT_DEPTH}; + vertices[2].position = (sf2d_vector_3f){(float)x3, (float)y3, SF2D_DEFAULT_DEPTH}; + + vertices[0].color = color; + vertices[1].color = vertices[0].color; + vertices[2].color = vertices[0].color; + + sf2d_draw_triangle_internal(vertices); } void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad) @@ -131,29 +143,56 @@ void sf2d_draw_rectangle_rotate(int x, int y, int w, int h, u32 color, float rad vertices[i].position = (sf2d_vector_3f){rot[i].x + x + w2, rot[i].y + y + h2, rot[i].z}; } - GPU_SetTexEnv( - 0, - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVOPERANDS(0, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_REPLACE, GPU_REPLACE, - 0xFFFFFFFF - ); + sf2d_draw_rectangle_internal(vertices); +} - GPU_SetAttributeBuffers( - 2, // number of attributes - (u32*)osConvertVirtToPhys(vertices), - GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE), - 0xFFFC, //0b1100 - 0x10, - 1, //number of buffers - (u32[]){0x0}, // buffer offsets (placeholders) - (u64[]){0x10}, // attribute permutations for each buffer - (u8[]){2} // number of attributes for each buffer - ); +void sf2d_draw_rectangle_gradient(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction) +{ + sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8); + if (!vertices) return; - GPU_DrawArray(GPU_TRIANGLE_STRIP, 0, 4); + vertices[0].position = (sf2d_vector_3f){(float)x, (float)y, SF2D_DEFAULT_DEPTH}; + vertices[1].position = (sf2d_vector_3f){(float)x+w, (float)y, SF2D_DEFAULT_DEPTH}; + vertices[2].position = (sf2d_vector_3f){(float)x, (float)y+h, SF2D_DEFAULT_DEPTH}; + vertices[3].position = (sf2d_vector_3f){(float)x+w, (float)y+h, SF2D_DEFAULT_DEPTH}; + + vertices[0].color = color1; + vertices[1].color = (direction == SF2D_LEFT_TO_RIGHT) ? color2 : color1; + vertices[2].color = (direction == SF2D_LEFT_TO_RIGHT) ? color1 : color2; + vertices[3].color = color2; + + sf2d_draw_rectangle_internal(vertices); +} + +void sf2d_draw_rectangle_gradient_rotate(int x, int y, int w, int h, u32 color1, u32 color2, sf2d_gradient_dir direction, float rad) +{ + sf2d_vertex_pos_col *vertices = sf2d_pool_memalign(4 * sizeof(sf2d_vertex_pos_col), 8); + if (!vertices) return; + + int w2 = w/2.0f; + int h2 = h/2.0f; + + vertices[0].position = (sf2d_vector_3f){(float)-w2, (float)-h2, SF2D_DEFAULT_DEPTH}; + vertices[1].position = (sf2d_vector_3f){(float) w2, (float)-h2, SF2D_DEFAULT_DEPTH}; + vertices[2].position = (sf2d_vector_3f){(float)-w2, (float) h2, SF2D_DEFAULT_DEPTH}; + vertices[3].position = (sf2d_vector_3f){(float) w2, (float) h2, SF2D_DEFAULT_DEPTH}; + + vertices[0].color = color1; + vertices[1].color = (direction == SF2D_LEFT_TO_RIGHT) ? color2 : color1; + vertices[2].color = (direction == SF2D_LEFT_TO_RIGHT) ? color1 : color2; + vertices[3].color = color2; + + float m[4*4]; + matrix_set_z_rotation(m, rad); + sf2d_vector_3f rot[4]; + + int i; + for (i = 0; i < 4; i++) { + vector_mult_matrix4x4(m, &vertices[i].position, &rot[i]); + vertices[i].position = (sf2d_vector_3f){rot[i].x + x + w2, rot[i].y + y + h2, rot[i].z}; + } + + sf2d_draw_rectangle_internal(vertices); } void sf2d_draw_fill_circle(int x, int y, int radius, u32 color) @@ -186,27 +225,7 @@ void sf2d_draw_fill_circle(int x, int y, int radius, u32 color) vertices[num_segments + 1].position = vertices[1].position; vertices[num_segments + 1].color = vertices[1].color; - GPU_SetTexEnv( - 0, - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVSOURCES(GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR), - GPU_TEVOPERANDS(0, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_REPLACE, GPU_REPLACE, - 0xFFFFFFFF - ); - - GPU_SetAttributeBuffers( - 2, // number of attributes - (u32*)osConvertVirtToPhys(vertices), - GPU_ATTRIBFMT(0, 3, GPU_FLOAT) | GPU_ATTRIBFMT(1, 4, GPU_UNSIGNED_BYTE), - 0xFFFC, //0b1100 - 0x10, - 1, //number of buffers - (u32[]){0x0}, // buffer offsets (placeholders) - (u64[]){0x10}, // attribute permutations for each buffer - (u8[]){2} // number of attributes for each buffer - ); + sf2d_setup_env_internal(vertices); GPU_DrawArray(GPU_TRIANGLE_FAN, 0, num_segments + 2); } diff --git a/libs/sf2dlib/libsf2d/source/sf2d_texture.c b/libs/sf2dlib/libsf2d/source/sf2d_texture.c index 12469ed..d0b7f09 100644 --- a/libs/sf2dlib/libsf2d/source/sf2d_texture.c +++ b/libs/sf2dlib/libsf2d/source/sf2d_texture.c @@ -8,7 +8,7 @@ #define M_PI (3.14159265358979323846) #endif -#define TEX_MIN_SIZE 8 +#define TEX_MIN_SIZE 32 static unsigned int nibbles_per_pixel(sf2d_texfmt format) { @@ -146,21 +146,40 @@ void sf2d_clear_target(sf2d_rendertarget *target, u32 color) { sf2d_texture_tile32(&(target->texture)); } +void sf2d_texture_tile32_hardware(sf2d_texture *texture, const void *data, int w, int h) +{ + if (texture->tiled) return; + const u32 flags = (GX_TRANSFER_FLIP_VERT(1) | GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_RAW_COPY(0) | + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGBA8) | + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)); + + GSPGPU_FlushDataCache(data, (w*h)<<2); + GX_DisplayTransfer( + (u32*)data, + GX_BUFFER_DIM(w, h), + (u32*)texture->data, + GX_BUFFER_DIM(texture->pow2_w, texture->pow2_h), + flags + ); + gspWaitForPPF(); + GSPGPU_InvalidateDataCache(texture->data, texture->data_size); + texture->tiled = 1; +} + void sf2d_fill_texture_from_RGBA8(sf2d_texture *dst, const void *rgba8, int source_w, int source_h) { // TODO: add support for non-RGBA8 textures - u8 *tmp = linearAlloc(dst->pow2_w * dst->pow2_h * 4); + u8 *tmp = linearAlloc((dst->pow2_w * dst->pow2_h)<<2); int i, j; for (i = 0; i < source_h; i++) { for (j = 0; j < source_w; j++) { - ((u32 *)tmp)[i*dst->pow2_w + j] = ((u32 *)rgba8)[i*source_w + j]; + ((u32 *)tmp)[i*dst->pow2_w + j] = __builtin_bswap32(((u32 *)rgba8)[i*source_w + j]); } } - memcpy(dst->data, tmp, dst->pow2_w*dst->pow2_h*4); + sf2d_texture_tile32_hardware(dst, tmp, dst->pow2_w, dst->pow2_h); linearFree(tmp); - sf2d_texture_tile32(dst); } sf2d_texture *sf2d_create_texture_mem_RGBA8(const void *src_buffer, int src_w, int src_h, sf2d_texfmt pixel_format, sf2d_place place) diff --git a/source/gfx.c b/source/gfx.c index 43a4fb5..3893380 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -192,6 +192,24 @@ static int gfx_vramSpaceFree(lua_State *L) { return 1; } +/*** +Draw a point, a single pixel, on the current screen. +@function point +@tparam integer x point horizontal coordinate, in pixels +@tparam integer y point vertical coordinate, in pixels +@tparam[opt=default color] integer color drawing color +*/ +static int gfx_point(lua_State *L) { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + + u32 color = luaL_optinteger(L, 3, color_default); + + sf2d_draw_rectangle(x, y, 1, 1, color); // well, it looks like a point + + return 0; +} + /*** Draw a line on the current screen. @function line @@ -217,19 +235,57 @@ static int gfx_line(lua_State *L) { } /*** -Draw a point, a single pixel, on the current screen. -@function point -@tparam integer x point horizontal coordinate, in pixels -@tparam integer y point vertical coordinate, in pixels +Draw a triangle on the current screen. +@function triangle +@tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels +@tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels +@tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels @tparam[opt=default color] integer color drawing color */ -static int gfx_point(lua_State *L) { - int x = luaL_checkinteger(L, 1); - int y = luaL_checkinteger(L, 2); +static int gfx_triangle(lua_State *L) { + int x1 = luaL_checkinteger(L, 1); + int y1 = luaL_checkinteger(L, 2); + int x2 = luaL_checkinteger(L, 3); + int y2 = luaL_checkinteger(L, 4); + int x3 = luaL_checkinteger(L, 5); + int y3 = luaL_checkinteger(L, 6); - u32 color = luaL_optinteger(L, 3, color_default); + u32 color = luaL_optinteger(L, 7, color_default); - sf2d_draw_rectangle(x, y, 1, 1, color); // well, it looks like a point + sf2d_draw_triangle(x1, y1, x2, y2, x3, y3, color); + + return 0; +} + +/*** +Draw a triangle on the current screen. +@function triangle +@tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels +@tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y2 vertical coordinate of a vertex of the triangle, in pixels +@tparam integer x3 horizontal coordinate of a vertex of the triangle, in pixels +@tparam integer y3 vertical coordinate of a vertex of the triangle, in pixels +@tparam[opt=1] number lineWidth line's thickness, in pixels +@tparam[opt=default color] integer color drawing color +*/ +static int gfx_linedTriangle(lua_State *L) { + int x1 = luaL_checkinteger(L, 1); + int y1 = luaL_checkinteger(L, 2); + int x2 = luaL_checkinteger(L, 3); + int y2 = luaL_checkinteger(L, 4); + int x3 = luaL_checkinteger(L, 5); + int y3 = luaL_checkinteger(L, 6); + float lineWidth = luaL_optnumber(L, 7, 1.0f); + + u32 color = luaL_optinteger(L, 8, color_default); + + sf2d_draw_line(x1, y1, x2, y2, lineWidth, color); + sf2d_draw_line(x2, y2, x3, y3, lineWidth, color); + sf2d_draw_line(x3, y3, x1, y1, lineWidth, color); return 0; } @@ -243,6 +299,8 @@ Draw a filled rectangle on the current screen. @tparam integer height rectangle height, in pixels @tparam[opt=0] number angle rectangle rotation, in radians @tparam[opt=default color] integer color drawing color +@tparam[opt] integer color2 Second drawing color ; if the argument is not nil, the rectangle will be filled with a gradient from color to color2 +@tparam[opt] integer direction Gradient drawing direction (`gfx.TOP_TO_BOTTOM` or `gfx.LEFT_TO_RIGHT`). This argument is mandatory if a second color was specified. */ static int gfx_rectangle(lua_State *L) { int x = luaL_checkinteger(L, 1); @@ -252,11 +310,23 @@ static int gfx_rectangle(lua_State *L) { float angle = luaL_optnumber(L, 5, 0); u32 color = luaL_optinteger(L, 6, color_default); - - if (angle == 0) - sf2d_draw_rectangle(x, y, width, height, color); - else - sf2d_draw_rectangle_rotate(x, y, width, height, color, angle); + + // Not second color : fill with plain color. + if (lua_isnoneornil(L, 7)) { + if (angle == 0) + sf2d_draw_rectangle(x, y, width, height, color); + else + sf2d_draw_rectangle_rotate(x, y, width, height, color, angle); + // Two colors : fill with a gradient. + } else { + u32 color2 = luaL_checkinteger(L, 7); + u8 direction = luaL_checkinteger(L, 8); + + if (angle == 0) + sf2d_draw_rectangle_gradient(x, y, width, height, color, color2, direction); + else + sf2d_draw_rectangle_gradient_rotate(x, y, width, height, color, color2, direction, angle); + } return 0; } @@ -679,8 +749,10 @@ static const struct luaL_Reg gfx_lib[] = { { "setVBlankWait", gfx_setVBlankWait }, { "waitForVBlank", gfx_waitForVBlank }, { "vramSpaceFree", gfx_vramSpaceFree }, - { "line", gfx_line }, { "point", gfx_point }, + { "line", gfx_line }, + { "triangle", gfx_triangle }, + { "linedTriangle", gfx_linedTriangle }, { "rectangle", gfx_rectangle }, { "linedRectangle", gfx_linedRectangle }, { "circle", gfx_circle }, @@ -709,56 +781,65 @@ static const struct luaL_Reg target_methods[] = { Constants @section constants */ -// Constants struct { char *name; int value; } gfx_constants[] = { /*** Constant used to select the top screen. It is equal to `0`. @field TOP */ - { "TOP", GFX_TOP }, + { "TOP", GFX_TOP }, /*** Constant used to select the bottom screen. It is equal to `1`. @field BOTTOM */ - { "BOTTOM", GFX_BOTTOM }, + { "BOTTOM", GFX_BOTTOM }, /*** Constant used to select the left eye. It is equal to `0`. @field LEFT */ - { "LEFT", GFX_LEFT }, + { "LEFT", GFX_LEFT }, /*** Constant used to select the right eye. It is equal to `1`. @field RIGHT */ - { "RIGHT", GFX_RIGHT }, + { "RIGHT", GFX_RIGHT }, /*** The top screen height, in pixels. It is equal to `240`. @field TOP_HEIGHT */ - { "TOP_HEIGHT", 240 }, + { "TOP_HEIGHT", 240 }, /*** The top screen width, in pixels. It is equal to `400`. @field TOP_WIDTH */ - { "TOP_WIDTH", 400 }, + { "TOP_WIDTH", 400 }, /*** The bottom screen height, in pixels. It is equal to `240`. @field BOTTOM_HEIGHT */ - { "BOTTOM_HEIGHT", 240 }, + { "BOTTOM_HEIGHT", 240 }, /*** The bottom screen width, in pixels. It is equal to `320`. @field BOTTOM_WIDTH */ - { "BOTTOM_WIDTH", 320 }, + { "BOTTOM_WIDTH", 320 }, + /*** + Represents a vertical gradient drawn from the top (first color) to the bottom (second color). + @field TOP_TO_BOTTOM + */ + { "TOP_TO_BOTTOM", SF2D_TOP_TO_BOTTOM }, + /*** + Represents a horizontal gradient drawn from the left (first color) to the right (second color). + @field LEFT_TO_RIGHT + */ + { "LEFT_TO_RIGHT", SF2D_LEFT_TO_RIGHT }, { NULL, 0 } }; From 3303e9783d488362edc21a147cdfdd3630855c2e Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 27 Jun 2016 15:38:26 +0200 Subject: [PATCH 43/46] Fix documentation --- source/gfx.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/gfx.c b/source/gfx.c index 3893380..bf9dc8d 100644 --- a/source/gfx.c +++ b/source/gfx.c @@ -235,7 +235,7 @@ static int gfx_line(lua_State *L) { } /*** -Draw a triangle on the current screen. +Draw a filled triangle on the current screen. @function triangle @tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels @tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels @@ -261,8 +261,8 @@ static int gfx_triangle(lua_State *L) { } /*** -Draw a triangle on the current screen. -@function triangle +Draw a triangle outline on the current screen. +@function linedTriangle @tparam integer x1 horizontal coordinate of a vertex of the triangle, in pixels @tparam integer y1 vertical coordinate of a vertex of the triangle, in pixels @tparam integer x2 horizontal coordinate of a vertex of the triangle, in pixels @@ -770,10 +770,10 @@ static const struct luaL_Reg gfx_lib[] = { // Render target static const struct luaL_Reg target_methods[] = { - { "__index", gfx_target___index }, - {"clear", gfx_target_clear }, - {"destroy", gfx_target_destroy }, - {"__gc", gfx_target_destroy }, + { "__index", gfx_target___index }, + { "clear", gfx_target_clear }, + { "destroy", gfx_target_destroy }, + { "__gc", gfx_target_destroy }, { NULL, NULL } }; @@ -787,7 +787,7 @@ struct { char *name; int value; } gfx_constants[] = { It is equal to `0`. @field TOP */ - { "TOP", GFX_TOP }, + { "TOP", GFX_TOP }, /*** Constant used to select the bottom screen. It is equal to `1`. From 5888ee381060206070dc795c058ece99cd95d8ce Mon Sep 17 00:00:00 2001 From: Reuh Date: Mon, 27 Jun 2016 19:31:07 +0200 Subject: [PATCH 44/46] Fixed UDP sockets, added documentation, cleaning udp:receivefrom arguments and return value changed so it actually works. --- source/socket.c | 149 ++++++++++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/source/socket.c b/source/socket.c index 08d30c8..947a035 100644 --- a/source/socket.c +++ b/source/socket.c @@ -455,7 +455,7 @@ static int socket_send(lua_State *L) { size_t size = 0; char *data = (char*)luaL_checklstring(L, 2, &size); - size_t sent; + ssize_t sent; if (!userdata->isSSL) { sent = send(userdata->socket, data, size, 0); } else { @@ -483,101 +483,114 @@ UDP sockets */ /*** -Receive some data from a server. +Receive a datagram from the UDP object. @function :receivefrom -@tparam number count amount of data to receive -@tparam string host host name -@tparam number port port -@treturn string data +@tparam[opt=8191] number count maximum amount of bytes to receive from the datagram. Must be lower than 8192. +@treturn[1] string data +@treturn[1] string IP address of the sender +@treturn[1] integer port number of the sender +@treturn[2] nil in case of error or no datagram to receive +@treturn[2] string error message */ static int socket_receivefrom(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); - int count = luaL_checkinteger(L, 2); - size_t namesize = 0; - char *hostname = (char*)luaL_optlstring(L, 3, NULL, &namesize); - int port = luaL_optinteger(L, 4, 0); - - struct sockaddr_in from = {0}; - if (hostname != NULL) { // For a server - struct hostent *hostinfo = gethostbyname(hostname); - if (hostinfo == NULL) { - lua_pushnil(L); - return 1; - } - from.sin_addr = *(struct in_addr*)hostinfo->h_addr; - from.sin_port = htons(port); - from.sin_family = AF_INET; + int count = luaL_optinteger(L, 2, 8191); + + struct sockaddr_in from; + socklen_t addr_len; + + char* buffer = calloc(1, count+1); + ssize_t n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr *)&from, &addr_len); + + if (n == 0) { + free(buffer); + lua_pushnil(L); + lua_pushstring(L, "nothing to receive"); + return 2; + + } else if (n < 0) { + free(buffer); + lua_pushnil(L); + lua_pushstring(L, strerror(n)); + return 2; } - char* buffer = malloc(count+1); - int n = recvfrom(userdata->socket, buffer, count, 0, (struct sockaddr*)&from, NULL); - *(buffer+n) = 0x0; - lua_pushstring(L, buffer); - if (hostname != NULL) { - return 1; - } else { - lua_pushstring(L, inet_ntoa(from.sin_addr)); - lua_pushinteger(L, ntohs(from.sin_port)); - return 3; - } + lua_pushstring(L, inet_ntoa(from.sin_addr)); + lua_pushinteger(L, ntohs(from.sin_port)); + + free(buffer); + + return 3; } /*** -Send some data to a server. +Send a datagram to the specified IP and port. @function :sendto @tparam string data data to send -@tparam string host host name -@tparam number port port +@tparam string host IP/hostname of the recipient +@tparam number port port number of the recipient +@treturn[1] boolean true in case of success +@treturn[2] boolean false in case of error +@treturn[2] string error message */ static int socket_sendto(lua_State *L) { socket_userdata *userdata = luaL_checkudata(L, 1, "LSocket"); - size_t datasize = 0; - char *data = (char*)luaL_checklstring(L, 2, &datasize); - size_t namesize = 0; - char *hostname = (char*)luaL_checklstring(L, 3, &namesize); + size_t datasize; + const char *data = luaL_checklstring(L, 2, &datasize); + const char *hostname = luaL_checkstring(L, 3); int port = luaL_checkinteger(L, 4); - + struct hostent *hostinfo = gethostbyname(hostname); if (hostinfo == NULL) { - lua_pushnil(L); - return 1; + lua_pushboolean(L, false); + lua_pushstring(L, "unknown host"); + return 2; } - struct sockaddr_in to = {0}; - to.sin_addr = *(struct in_addr*)hostinfo->h_addr; + + struct sockaddr_in to; + to.sin_addr = *(struct in_addr *)hostinfo->h_addr; to.sin_port = htons(port); to.sin_family = AF_INET; - - sendto(userdata->socket, data, datasize, 0, (struct sockaddr*)&to, sizeof(to)); - - return 0; + + ssize_t n = sendto(userdata->socket, data, datasize, 0, (struct sockaddr *)&to, sizeof(to)); + + if (n < 0) { + lua_pushboolean(L, false); + lua_pushstring(L, strerror(n)); + return 2; + } + + lua_pushboolean(L, true); + + return 1; } -// module functions +// Module functions static const struct luaL_Reg socket_functions[] = { - {"init", socket_init }, - {"shutdown", socket_shutdown }, - {"tcp", socket_tcp }, - {"udp", socket_udp }, - {"addTrustedRootCA", socket_addTrustedRootCA}, + { "init", socket_init }, + { "shutdown", socket_shutdown }, + { "tcp", socket_tcp }, + { "udp", socket_udp }, + { "addTrustedRootCA", socket_addTrustedRootCA }, {NULL, NULL} }; -// object +// Object methods static const struct luaL_Reg socket_methods[] = { - {"accept", socket_accept }, - {"bind", socket_bind }, - {"close", socket_close }, - {"setBlocking", socket_setBlocking}, - {"__gc", socket_close }, - {"connect", socket_connect }, - {"listen", socket_listen }, - {"receive", socket_receive }, - {"receivefrom", socket_receivefrom}, - {"send", socket_send }, - {"sendto", socket_sendto }, - {"getpeername", socket_getpeername}, - {"getsockname", socket_getsockname}, + { "accept", socket_accept }, + { "bind", socket_bind }, + { "close", socket_close }, + { "setBlocking", socket_setBlocking }, + { "__gc", socket_close }, + { "connect", socket_connect }, + { "listen", socket_listen }, + { "receive", socket_receive }, + { "receivefrom", socket_receivefrom }, + { "send", socket_send }, + { "sendto", socket_sendto }, + { "getpeername", socket_getpeername }, + { "getsockname", socket_getsockname }, {NULL, NULL} }; From f118baa37c242a383c4e11ab975eef3ba7585ea6 Mon Sep 17 00:00:00 2001 From: Firew0lf Date: Mon, 27 Jun 2016 20:16:38 +0200 Subject: [PATCH 45/46] README rewrite --- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c8e96c4..fcd4421 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,86 @@ # ctrµLua - -Everything is in the Wiki. +![banner](https://www.dropbox.com/s/cqmtoohyx6t7q7c/banner.png?raw=1) Warning: the 'u' in the repo's name is a 'µ', not a 'u'. +### Users part + +#### How to install + +* Download ctruLua.zip from the [releases](https://github.com/ctruLua/ctruLua/releases) (for something stable) or the [CI server](http://ci.reuh.tk/ctrulua) (for more features) +* Unzip it on your SD card, in a folder inside your homebrews folder; if you use HBL, extract it in `/3ds/ctrulua/` +* Put some scripts wherever you want, just remember where +* Launch CtrµLua from your homebrew launcher +* Use the shell to run your scripts + +### Homebrewers part + #### Builds ![build status](http://ci.reuh.tk/ctrulua.png) -* Most recent working build: [here](http://ci.reuh.tk/ctrulua/builds/latest/artifacts/ctruLua.3dsx) (warning: only tested with Citra, sometimes on real hardware). -* See http://ci.reuh.tk/ctrulua for all the builds. +* Most recent working build: [ctruLua.3dsx](http://ci.reuh.tk/ctrulua/builds/latest/artifacts/ctruLua.3dsx) +* See [http://ci.reuh.tk/ctrulua](http://ci.reuh.tk/ctrulua) for all the builds. + +#### Hello world + +```Lua +local ctr = require("ctr") +local gfx = require("ctr.gfx") +local hid = require("ctr.hid") + +while ctr.run() do + hid.read() + local keys = hid.keys() + if keys.held.start then break end + + gfx.start(gfx.TOP) + gfx.text(2, 2, "Hello, world !") + gfx.stop() + + gfx.render() +end +``` +This script will print "Hello, world !" on the top screen, and will exit if the user presses Start. +This is the "graphical" version; there's also a text-only version, based on the console: +```Lua +local ctr = require("ctr") +local gfx = require("ctr.gfx") +local hid = require("ctr.hid") + +gfx.console() +print("Hello, world !") + +while ctr.run() do + hid.read() + local keys = hid.keys() + if keys.held.start then break end + + gfx.render() +end + +gfx.disableConsole() +``` + +#### Lua API Documentation + +* An online version of the documentation can be found [here](http://reuh.tk/ctrulua) +* To build the documentation, run `make build-doc-html` (requires [LDoc](https://github.com/stevedonovan/LDoc)). + +### Developers part #### Build instructions * Setup your environment as shown here : http://3dbrew.org/wiki/Setting_up_Development_Environment * Clone this repository and run the command `make build-all` to build all the dependencies. -* If you only made changes to ctrµLua, run `make` to rebuild ctµLua without rebuilding all the dependencies. +* If you only made changes to ctrµLua, run `make` to rebuild ctrµLua without rebuilding all the dependencies. May not work under Windows. -#### Lua API Documentation +### Credits -* An online version of the documentation can be found here : http://reuh.tk/ctrulua -* To build the documentation, run `make build-doc` (requires [LDoc](https://github.com/stevedonovan/LDoc)). - -#### Based on ctrulib by smealum: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib) +* __Smealum__ and everyone who worked on the ctrulib: [https://github.com/smealum/ctrulib](https://github.com/smealum/ctrulib) +* __Xerpi__ for the [sf2dlib](https://github.com/xerpi/sf2dlib), [sftdlib](https://github.com/xerpi/sftdlib) and [sfillib](https://github.com/xerpi/sfillib) +* __All the [Citra](https://citra-emu.org/) developers__ +* __Everyone who worked on [DevKitARM](http://devkitpro.org/)__ +* __Nothings__ for the [stb](https://github.com/nothings/stb) libs +* Everyone who worked on the other libs we use + From 9a97ad0bcad99f95d37f30145660ffb16accf617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Sun, 27 Jun 2021 14:27:16 +0200 Subject: [PATCH 46/46] Update links before they break --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fcd4421..75dbf84 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Warning: the 'u' in the repo's name is a 'µ', not a 'u'. #### How to install -* Download ctruLua.zip from the [releases](https://github.com/ctruLua/ctruLua/releases) (for something stable) or the [CI server](http://ci.reuh.tk/ctrulua) (for more features) +* Download ctruLua.zip from the [releases](https://github.com/ctruLua/ctruLua/releases) (for something stable) or the [CI server](https://reuh.eu/ctrulua/ci/ctrulua) (for more features) * Unzip it on your SD card, in a folder inside your homebrews folder; if you use HBL, extract it in `/3ds/ctrulua/` * Put some scripts wherever you want, just remember where * Launch CtrµLua from your homebrew launcher @@ -15,10 +15,10 @@ Warning: the 'u' in the repo's name is a 'µ', not a 'u'. ### Homebrewers part -#### Builds ![build status](http://ci.reuh.tk/ctrulua.png) +#### Builds ![build status](https://reuh.eu/ctrulua/ci/ctrulua.png) -* Most recent working build: [ctruLua.3dsx](http://ci.reuh.tk/ctrulua/builds/latest/artifacts/ctruLua.3dsx) -* See [http://ci.reuh.tk/ctrulua](http://ci.reuh.tk/ctrulua) for all the builds. +* Most recent working build: [ctruLua.3dsx](https://reuh.eu/ctrulua/ci/ctrulua/builds/latest/artifacts/ctruLua.3dsx) +* See [https://reuh.eu/ctrulua/ci/ctrulua](https://reuh.eu/ctrulua/ci/ctrulua) for all the builds. #### Hello world @@ -62,7 +62,7 @@ gfx.disableConsole() #### Lua API Documentation -* An online version of the documentation can be found [here](http://reuh.tk/ctrulua) +* An online version of the documentation can be found [here](https://reuh.eu/ctrulua/latest/html/) * To build the documentation, run `make build-doc-html` (requires [LDoc](https://github.com/stevedonovan/LDoc)). ### Developers part