diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d88174 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +#compiled vpk +*.vpk \ No newline at end of file diff --git a/README.md b/README.md index 2feda9e..de67e2d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# MikuVU -A homebrew app for the PS Vita that displays mostly SFW images of Hatsune Miku, sourced from Gelbooru. +# MikuVU +A homebrew app for the PS Vita that displays mostly SFW images of Hatsune Miku, sourced from Gelbooru. diff --git a/deps/font/ShinGo-Trimmed.ttf b/deps/font/ShinGo-Trimmed.ttf new file mode 100644 index 0000000..07e9e58 Binary files /dev/null and b/deps/font/ShinGo-Trimmed.ttf differ diff --git a/deps/font/ShinGo.ttf b/deps/font/ShinGo.ttf deleted file mode 100644 index 6b69eb2..0000000 Binary files a/deps/font/ShinGo.ttf and /dev/null differ diff --git a/index.lua b/index.lua index 95e16cc..f9d1ceb 100644 --- a/index.lua +++ b/index.lua @@ -2,19 +2,26 @@ pcall(Network.term) -- Initialize network Network.init() +-- Seed random number generator +local rh,rm,rs = System.getTime() +math.randomseed(rh .. rm .. rs) -- Load JSON parser local json = dofile("app0:/deps/lua/json.lua") -- Load font and set font size -local fnt0 = Font.load("app0:/deps/font/ShinGo.ttf") +local fnt0 = Font.load("app0:/deps/font/ShinGo-Trimmed.ttf") Font.setPixelSizes(fnt0, 25) -- Define colors local white, translucentBlack = Color.new(255,255,255), Color.new(0,0,0,160) +-- Init constants +local dataFolder = "ux0:/data/MikuVU" +local saveFolder = dataFolder .. "/SAVED" -- Init values local currentId = nil -- ID of the currently loaded image, used when saving images local autoNext = 0 -- Auto next variable local seconds = 5 -- Delay in seconds local rating = 1 -- max rating allowed (0=general, 1=sensitive, 2=questionable, 3=explicit) local ratingURL="-rating:questionable+-rating:explicit" -- URL additive related to the rating var above +local fadeImages = false -- Fade in and out images? local response = nil -- Response of function ID (used in main loop) local message = "" -- Callback Message (used in main loop) local status = nil -- Callback Status (used in main loop) @@ -26,10 +33,30 @@ local tmr2 = Timer.new() -- Set timer for delays in main loop Timer.pause(tmr2) -- Pause timer at 0 local buttonDown = false -- Ensures no input lag and no unpredicted calls to functions every loop local menu = false -- If menu is open -local jsonValid = true -- Valid JSON Response (Default True) +local jsonValid = true -- Valid JSON Response (Default true) +local drawMode = false -- The space in between Graphics.initBlend and Graphics.termBlend where anything can happen. +local imageDrawn = false -- Is the image fully shown? +local imgData = {} -- Arguments for the last rendered image +local offlineMode = false -- If the user has no internet, just show a slideshow of saved images +local offlineImgIndex = 0 -- Currently displayed image # in folder +local offlineImgList = {} -- List of all saved images for offline use -- Functions +-- Sets bool when drawMode is active +local old_init = Graphics.initBlend +local old_term = Graphics.termBlend + +function Graphics.initBlend() + drawMode = true + old_init() +end + +function Graphics.termBlend() + drawMode = false + old_term() +end + -- Increases auto next delay function timerIncrease() local id = 1 @@ -72,16 +99,19 @@ end -- Saves image with the ID as name function saveImage() local id = 4 - if System.doesFileExist("ux0:/data/MikuVU/SAVED/" .. currentId .. ".jpg") then + if offlineMode then + return id, "Error | Offline", 2 + end + if System.doesFileExist(saveFolder .. "/" .. currentId .. ".jpg") then return id, "Image Already Saved", 0 elseif img ~= nil then if fullRes then - local new = System.openFile("ux0:/data/MikuVU/SAVED/" .. currentId .. ".jpg", FCREATE) + local new = System.openFile(saveFolder .. "/" .. currentId .. ".jpg", FCREATE) System.writeFile(new, image, size2) -- Image data and Size Loaded in getmiku() System.closeFile(new) else local fullExt = string.lower(string.match(fullUrl,"%.[%a%d]+$")) - Network.downloadFile(fullUrl, "ux0:/data/MikuVU/SAVED/" .. currentId .. fullExt) + Network.downloadFile(fullUrl, saveFolder .. "/" .. currentId .. fullExt) end return id, "Saved | " .. currentId .. ".jpg", 1 else @@ -106,26 +136,67 @@ function changeFilter() return id, "Explicit" end end + +function toggleFade() + local id = 6 + fadeImages = not(fadeImages) + + if fadeImages then + return id, "Fade | On" + else + return id, "Fade | Off" + end +end + +-- Fades in an image at the specified scale in a certain amount of time. +-- 4x fadeSpeed is approximately 1 second on my Vita. +function fadeInImageScale(x, y, img, x_scale, y_scale, fadeSpeed) + local prevDrawMode = drawMode + + if drawMode then + Graphics.termBlend() + Screen.flip() + Screen.waitVblankStart() + end + + for i=1,255,fadeSpeed do + Graphics.initBlend() + --Screen.clear() + Graphics.fillRect(0,960,0,544,Color.new(0,0,0,tonumber(i/4))) + Graphics.drawScaleImage(x, y, img, x_scale, y_scale, Color.new(255,255,255,i)) + Graphics.termBlend() + Screen.flip() + Screen.waitVblankStart() + end + + if prevDrawMode then + Graphics.initBlend() + end +end + -- Gets and loads pictures from decoded JSON function getmiku() ::getmiku:: + -- Clear last loaded image from memory + if img ~= nil then + Graphics.freeImage(img) + img = nil + end + if Network.isWifiEnabled() then - Network.downloadFile("https://gelbooru.com/index.php?limit=1&page=dapi&s=post&q=index&json=1&tags=hatsune_miku+-furry+-animal_ears+sort:random+highres+"..ratingURL, "ux0:/data/MikuVU/post.json") - local file1 = System.openFile("ux0:/data/MikuVU/post.json", FREAD) + Network.downloadFile("https://gelbooru.com/index.php?limit=1&page=dapi&s=post&q=index&json=1&tags=hatsune_miku+-furry+-animal_ears+sort:random+highres+"..ratingURL, dataFolder.."/post.json") + local file1 = System.openFile(dataFolder.."/post.json", FREAD) local size1 = System.sizeFile(file1) local jsonEncoded = System.readFile(file1, size1) -- Encoded JSON file data local pcallStat, jsonDecoded = pcall(json.decode, jsonEncoded) -- Decoded JSON to table System.closeFile(file1) - System.deleteFile("ux0:/data/MikuVU/post.json") + System.deleteFile(dataFolder.."/post.json") if not pcallStat then jsonValid = pcallStat return end jsonValid = pcallStat - if img ~= nil then - Graphics.freeImage(img) - img = nil - end + -- if sample (smaller) image doesn't exist, display full-size image instead url = jsonDecoded["post"][1]["sample_url"] fullUrl = jsonDecoded["post"][1]["file_url"] @@ -141,8 +212,8 @@ function getmiku() end currentId = jsonDecoded["post"][1]["id"] - Network.downloadFile(url, "ux0:/data/MikuVU/MikuVU.jpg") - local file2 = System.openFile("ux0:/data/MikuVU/MikuVU.jpg", FREAD) + Network.downloadFile(url, dataFolder.."/MikuVU.jpg") + local file2 = System.openFile(dataFolder.."/MikuVU.jpg", FREAD) size2 = System.sizeFile(file2) if size2 == 0 then System.closeFile(file2) @@ -150,8 +221,37 @@ function getmiku() end image = System.readFile(file2, size2) System.closeFile(file2) - img = Graphics.loadImage("ux0:/data/MikuVU/MikuVU.jpg") - System.deleteFile("ux0:/data/MikuVU/MikuVU.jpg") + img = Graphics.loadImage(dataFolder.."/MikuVU.jpg") + System.deleteFile(dataFolder.."/MikuVU.jpg") + else + -- if no internet, load images in /saved/ + if System.doesDirExist(saveFolder) and not(offlineMode) then + local savedFiles = System.listDirectory(saveFolder) + local fullExt = "" + for _, image in ipairs(savedFiles) do + fullExt = string.lower(string.match(image["name"],"%.[%a%d]+$")) + if fullExt == ".jpg" or fullExt == ".jpeg" or fullExt == ".png" or fullExt == ".bmp" then + offlineImgList[#offlineImgList+1] = image["name"] + end + end + offlineMode = true + end + + -- if images already loaded, display the next one + if #offlineImgList > 0 then + local lastIndex = offlineImgIndex + ::randomize:: + offlineImgIndex = math.random(1,#offlineImgList-1) + if offlineImgIndex == lastIndex then goto randomize end + + img = Graphics.loadImage(saveFolder.."/"..offlineImgList[offlineImgIndex]) + end + end + + -- if no wifi and no images, then something is wrong and image should NOT be loaded + if #offlineImgList == 0 and not(Network.isWifiEnabled()) then + img = nil + else width = Graphics.getImageWidth(img) height = Graphics.getImageHeight(img) drawWidth = 480 - (width * 544 / height / 2) @@ -159,18 +259,18 @@ function getmiku() if (autoNext == 1) then Timer.setTime(tmr, seconds * 1000) -- Set time in seconds end - else - img = nil + imageDrawn = false end end + -- Check if ux0:/data/MikuVU exists -if not System.doesDirExist("ux0:/data/MikuVU") then - System.createDirectory("ux0:/data/MikuVU") +if not System.doesDirExist(dataFolder) then + System.createDirectory(dataFolder) end -- Check if SAVED folder exists -if not System.doesDirExist("ux0:/data/MikuVU/SAVED") then - System.createDirectory("ux0:/data/MikuVU/SAVED") +if not System.doesDirExist(saveFolder) then + System.createDirectory(saveFolder) end getmiku() @@ -185,7 +285,7 @@ while true do local delaySec = 4000 -- Value used for the delay timer -- Controls - if jsonValid and Network.isWifiEnabled() and img ~= nil then + if jsonValid and img ~= nil then if Controls.check(pad, SCE_CTRL_CROSS) or Controls.check(pad, SCE_CTRL_DOWN) or (autoNext == 1 and time > 0) then getmiku() elseif Controls.check(pad, SCE_CTRL_CIRCLE) or Controls.check(pad, SCE_CTRL_RIGHT) then @@ -213,6 +313,11 @@ while true do response, message = changeFilter() end buttonDown = true + elseif (Controls.check(pad, SCE_CTRL_START)) then + if not buttonDown then + response, message = toggleFade() + end + buttonDown = true else buttonDown = false end @@ -233,28 +338,33 @@ while true do -- Start drawing Graphics.initBlend() - Screen.clear() - if not Network.isWifiEnabled() then - Graphics.debugPrint(5, 220, "Error | Check Network Connection", Color.new(255,255,255)) - elseif not jsonValid then + if not jsonValid then Graphics.debugPrint(5, 220, "Error | Check Network Connection", Color.new(255,255,255)) elseif img == nil then Graphics.debugPrint(5, 220, "Error | Check Network Connection", Color.new(255,255,255)) else - if height > width then - Graphics.drawScaleImage(drawWidth, 0, img, 544 / height, 544 / height) + if height >= width then + imgData = {drawWidth, 0, img, 544 / height, 544 / height, 4} elseif width > height then - Graphics.drawScaleImage(0, drawHeight, img, 960 / width, 960 / width) + imgData = {0, drawHeight, img, 960 / width, 960 / width, 4} + end + + if fadeImages and not(imageDrawn) then + fadeInImageScale(imgData[1], imgData[2], imgData[3], imgData[4], imgData[5], imgData[6]) + imageDrawn = true end + + Screen.clear() + Graphics.drawScaleImage(imgData[1], imgData[2], imgData[3], imgData[4], imgData[5]) end -- "Menu" - if delay < 0 then -- Informational display delay dimer is set, print info by function ID - menu = true -- Set menu visibility to false - if response == 1 or response == 2 then -- timerIncrease()/timerDecrease() + if delay < 0 then -- Informational display delay timer is set, print info by function ID + menu = true -- Set menu visibility to true + if response == 1 or response == 2 then -- timerIncrease()/timerDecrease() Graphics.fillRect(15, 250, 30, 80, translucentBlack) Font.print(fnt0, 20, 30, string.format("Delay | %02ds", seconds), white) - elseif response == 3 then -- toggleAutoNext() + elseif response == 3 then -- toggleAutoNext() Graphics.fillRect(15, 250, 30, 80, translucentBlack) if Timer.isPlaying(tmr) then Font.print(fnt0, 20, 30, string.format("Timer | %02ds", timeSec), white) @@ -272,8 +382,11 @@ while true do end Font.print(fnt0, 20, 30, message, white) elseif response == 5 then - Graphics.fillRect(15, 350, 30, 80, translucentBlack) - Font.print(fnt0, 20, 30, string.format("Max rating: %s",message), white) + Graphics.fillRect(15, 375, 30, 80, translucentBlack) + Font.print(fnt0, 20, 30, string.format("Max Rating | %s",message), white) + elseif response == 6 then + Graphics.fillRect(15, 250, 30, 80, translucentBlack) + Font.print(fnt0, 20, 30, message, white) else menu = false Graphics.fillRect(15, 325, 30, 80, translucentBlack) @@ -282,6 +395,7 @@ while true do else menu = false -- Set menu visibility to false end + -- Finish drawing Graphics.termBlend() Screen.flip() diff --git a/sce_sys/manual/001.png b/sce_sys/manual/001.png index dfcc355..0d68f3c 100644 Binary files a/sce_sys/manual/001.png and b/sce_sys/manual/001.png differ