main.lua 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. --[===================================================================[--
  2. Copyright © 2016, 2018 Pedro Gimeno Fortea. All rights reserved.
  3. Permission is hereby granted to everyone to copy and use this file,
  4. for any purpose, in whole or in part, free of charge, provided this
  5. single condition is met: The above copyright notice, together with
  6. this permission grant and the disclaimer below, should be included
  7. in all copies of this software or of a substantial portion of it.
  8. THIS SOFTWARE COMES WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED.
  9. --]===================================================================]--
  10. -- Command-line GIF viewer
  11. -- We've disabled these in conf.lua - re-enable them here
  12. require 'love.window'
  13. require 'love.graphics'
  14. if arg[#arg]=="-debug"then local m=require"mobdebug"m.start()m.coro()table.remove(arg)end
  15. local gifnew = require 'gifload.gifload'
  16. local twimer = require 'twimer.twimer'()
  17. local gif, nframe, nloops, canvas, checker, chkquad
  18. local function DoFrame()
  19. local prevframe = nframe
  20. nframe = nframe + 1
  21. if nframe > gif.nimages then
  22. if not gif.loop then
  23. return
  24. end
  25. if gif.loop ~= 0 then
  26. nloops = nloops + 1
  27. if nloops >= gif.loop then
  28. return
  29. end
  30. end
  31. nframe = 1
  32. end
  33. local dispose = prevframe == 0 and 0 or gif.imgs[prevframe*5-4]
  34. local delay = gif.imgs[nframe*5-3]
  35. if gif.imgs[nframe*5-4] ~= 3 then -- Paint if CURRENT dispose is not 'undo'
  36. love.graphics.setCanvas(canvas)
  37. -- Clear if PREVIOUS dispose is not 'combine'
  38. --[[if dispose == 0 then
  39. love.graphics.clear()
  40. end]]
  41. if dispose == 2 then
  42. local r, g, b = 0, 0, 0
  43. if type(gif.background) == "table" then
  44. r, g, b = unpack(gif.background)
  45. end
  46. love.graphics.setBlendMode("replace")
  47. love.graphics.setColor(r, g, b, 0)
  48. --print("fill", gif.imgs[prevframe*5-1], gif.imgs[prevframe*5], gif.imgs[prevframe*5-2]:getDimensions())
  49. love.graphics.rectangle("fill", gif.imgs[prevframe*5-1], gif.imgs[prevframe*5], gif.imgs[prevframe*5-2]:getDimensions())
  50. love.graphics.setColor(255, 255, 255)
  51. love.graphics.setBlendMode("alpha")
  52. end
  53. love.graphics.draw(gif.imgs[nframe*5-2], gif.imgs[nframe*5-1], gif.imgs[nframe*5])
  54. love.graphics.setCanvas()
  55. end
  56. return twimer:after(delay, DoFrame)
  57. end
  58. local function usage()
  59. print("Usage: love vgif.love [--flatbg] [--vsync] [--autosize] [--progressive] image.gif")
  60. end
  61. function getopts(args)
  62. -- Parse arguments
  63. local expect_nonopt = false
  64. local nonoptions, options = {}, {}
  65. -- Start in args[1] if fused, otherwise in args[2] (to skip .love or dir arg)
  66. for i = ((love._version_major ~= 0 or love.filesystem.isFused()) and 1 or 2), #args do
  67. local arg = args[i]
  68. if expect_nonopt or arg:sub(1, 1) ~= '-' then
  69. nonoptions[#nonoptions + 1] = arg
  70. elseif arg == '--' then
  71. expect_nonopt = true
  72. else
  73. options[#options+1] = arg
  74. end
  75. end
  76. return options, nonoptions
  77. end
  78. function love.load(args)
  79. love.graphics.setDefaultFilter("nearest", "nearest")
  80. local options, nonoptions = getopts(args)
  81. if #nonoptions ~= 1 then
  82. usage()
  83. love.event.quit()
  84. return
  85. end
  86. local opt = {
  87. flatbg = false;
  88. vsync = false;
  89. autosize = false;
  90. progressive = false;
  91. }
  92. for _, v in ipairs(options) do
  93. if v == '--help' then
  94. usage()
  95. love.event.quit()
  96. return
  97. elseif v == '--flatbg' then
  98. opt.flatbg = true
  99. elseif v == '--vsync' then
  100. opt.vsync = true
  101. elseif v == '--autosize' then
  102. opt.autosize = true
  103. elseif v == "--progressive" then
  104. opt.progressive = true
  105. else
  106. print('Invalid option: ', v)
  107. usage()
  108. love.event.quit()
  109. return
  110. end
  111. end
  112. -- Paint a Loading... sign while loading the gif
  113. love.window.setMode(120, 20, {vsync = false, resizable = true})
  114. love.graphics.clear()
  115. love.graphics.print("Loading...")
  116. love.graphics.present()
  117. local f = io.open(nonoptions[1], 'rb')
  118. if not f then
  119. error("Can't open file: " .. nonoptions[1])
  120. end
  121. gif = gifnew()
  122. gif.progressive = opt.progressive
  123. repeat
  124. local s = f:read(524288)
  125. collectgarbage("collect") -- free the string from the previous iteration
  126. if not s or s == '' then
  127. break
  128. end
  129. gif:update(s)
  130. until false
  131. f:close()
  132. gif:done()
  133. if gif.nimages == 0 then
  134. print("No images in GIF file: " .. nonoptions[1])
  135. love.event.quit()
  136. return
  137. end
  138. local fullscreen = false
  139. local _, _, flags = love.window.getMode()
  140. local w, h = love.window.getDesktopDimensions(flags.display)
  141. local gifwidth, gifheight = 1, 1
  142. if opt.autosize then
  143. for i = 1, gif.nimages do
  144. gifwidth = math.max(gifwidth, gif.imgs[i*5-2]:getWidth() + gif.imgs[i*5-1])
  145. gifheight = math.max(gifheight, gif.imgs[i*5-2]:getHeight() + gif.imgs[i*5])
  146. end
  147. else
  148. gifwidth, gifheight = gif.width, gif.height
  149. end
  150. love.window.setTitle(nonoptions[1])
  151. -- Leave margins
  152. if gifwidth < w - 20 and gifheight < h - 60 or gifwidth == w and gifheight == h then
  153. w, h = gifwidth, gifheight
  154. else
  155. fullscreen = true
  156. end
  157. love.window.setMode(w, h, {fullscreen = fullscreen, vsync = opt.vsync, resizable = flags.resizable})
  158. canvas = love.graphics.newCanvas()
  159. if not opt.flatbg then
  160. checker = love.graphics.newImage(gifnew():update("GIF87a\16\0\16\0\240\1\0fff\153\153\153,\0\0\0\0\16\0\16\0\0\2\31\140o\160\171\136\204\220\129K&\10l\192\217r\253y\28\6\146\34U\162'\148\178k\244VV\1\0;"):done().imgs[3])
  161. checker:setWrap("repeat", "repeat")
  162. chkquad = love.graphics.newQuad(0, 0, w, h, 16, 16)
  163. end
  164. -- Convert ImageData to Image
  165. for i = 1, gif.nimages do
  166. gif.imgs[i*5-2] = love.graphics.newImage(gif.imgs[i*5-2])
  167. end
  168. if gif.imgs[gif.nimages*5-3] == 0 then
  169. -- Tweak last delay to avoid stack overflow on images that have 0 delay in every frame
  170. gif.imgs[gif.nimages*5-3] = 1e-60
  171. end
  172. nloops = 0
  173. nframe = 0
  174. if type(gif.background) == "table" then
  175. love.graphics.setBackgroundColor(gif.background)
  176. end
  177. return twimer:after(1e-60, DoFrame) -- can't be 0 because it would be triggered out of love.draw
  178. end
  179. function love.update(dt)
  180. twimer:update(dt)
  181. end
  182. function love.draw()
  183. if checker then
  184. love.graphics.draw(checker, chkquad)
  185. end
  186. love.graphics.draw(canvas)
  187. if nframe <= gif.nimages then
  188. if gif.imgs[nframe*5-4] == 3 then -- Restore After Drawing disposal mode?
  189. -- If so, it hasn't been drawn because that would pollute the canvas. Draw it now.
  190. love.graphics.draw(gif.imgs[nframe*5-2], gif.imgs[nframe*5-1], gif.imgs[nframe*5])
  191. end
  192. end
  193. end
  194. function love.resize(w, h)
  195. if checker then
  196. chkquad = love.graphics.newQuad(0, 0, w, h, 16, 16)
  197. end
  198. end
  199. function love.keypressed(k)
  200. if k == "escape" then love.event.quit() end
  201. end