main.lua 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. --[===================================================================[--
  2. Copyright © 2016 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] image.gif")
  60. end
  61. function getopts(args)
  62. -- Parse arguments
  63. local expect_nonopt = false
  64. local nonoptions, options = {}, {}
  65. for i = 2, #args do
  66. local arg = args[i]
  67. if expect_nonopt or arg:sub(1, 1) ~= '-' then
  68. nonoptions[#nonoptions + 1] = arg
  69. elseif arg == '--' then
  70. expect_nonopt = true
  71. else
  72. options[#options+1] = arg
  73. end
  74. end
  75. return options, nonoptions
  76. end
  77. function love.load(args)
  78. love.graphics.setDefaultFilter("nearest", "nearest")
  79. local options, nonoptions = getopts(args)
  80. if #nonoptions ~= 1 then
  81. usage()
  82. love.event.quit()
  83. return
  84. end
  85. local opt = {
  86. flatbg = false;
  87. vsync = false;
  88. autosize = false;
  89. }
  90. for _, v in ipairs(options) do
  91. if v == '--help' then
  92. usage()
  93. love.event.quit()
  94. return
  95. elseif v == '--flatbg' then
  96. opt.flatbg = true
  97. elseif v == '--vsync' then
  98. opt.vsync = true
  99. elseif v == '--autosize' then
  100. opt.autosize = true
  101. else
  102. print('Invalid option: ', v)
  103. usage()
  104. love.event.quit()
  105. return
  106. end
  107. end
  108. -- Paint a Loading... sign while loading the gif
  109. love.window.setMode(120, 20, {vsync = false, resizable = true})
  110. love.graphics.clear()
  111. love.graphics.print("Loading...")
  112. love.graphics.present()
  113. local f = io.open(nonoptions[1], 'rb')
  114. if not f then
  115. error("Can't open file: " .. nonoptions[1])
  116. end
  117. gif = gifnew()
  118. repeat
  119. local s = f:read(524288)
  120. collectgarbage("collect") -- free the string from the previous iteration
  121. if not s or s == '' then
  122. break
  123. end
  124. gif:update(s)
  125. until false
  126. f:close()
  127. gif:done()
  128. if gif.nimages == 0 then
  129. print("No images in GIF file: " .. nonoptions[1])
  130. love.event.quit()
  131. return
  132. end
  133. local fullscreen = false
  134. local _, _, flags = love.window.getMode()
  135. local w, h = love.window.getDesktopDimensions(flags.display)
  136. local gifwidth, gifheight = 1, 1
  137. if opt.autosize then
  138. for i = 1, gif.nimages do
  139. gifwidth = math.max(gifwidth, gif.imgs[i*5-2]:getWidth() + gif.imgs[i*5-1])
  140. gifheight = math.max(gifheight, gif.imgs[i*5-2]:getHeight() + gif.imgs[i*5])
  141. end
  142. else
  143. gifwidth, gifheight = gif.width, gif.height
  144. end
  145. love.window.setTitle(nonoptions[1])
  146. -- Leave margins
  147. if gifwidth < w - 20 and gifheight < h - 60 or gifwidth == w and gifheight == h then
  148. w, h = gifwidth, gifheight
  149. else
  150. fullscreen = true
  151. end
  152. love.window.setMode(w, h, {fullscreen = fullscreen, vsync = opt.vsync, resizable = flags.resizable})
  153. canvas = love.graphics.newCanvas()
  154. if not opt.flatbg then
  155. 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])
  156. checker:setWrap("repeat", "repeat")
  157. chkquad = love.graphics.newQuad(0, 0, w, h, 16, 16)
  158. end
  159. -- Convert ImageData to Image
  160. for i = 1, gif.nimages do
  161. gif.imgs[i*5-2] = love.graphics.newImage(gif.imgs[i*5-2])
  162. end
  163. if gif.imgs[gif.nimages*5-3] == 0 then
  164. -- Tweak last delay to avoid stack overflow on images that have 0 delay in every frame
  165. gif.imgs[gif.nimages*5-3] = 1e-60
  166. end
  167. nloops = 0
  168. nframe = 0
  169. if type(gif.background) == "table" then
  170. love.graphics.setBackgroundColor(gif.background)
  171. end
  172. return twimer:after(1e-60, DoFrame) -- can't be 0 because it would be triggered out of love.draw
  173. end
  174. function love.update(dt)
  175. twimer:update(dt)
  176. end
  177. function love.draw()
  178. if checker then
  179. love.graphics.draw(checker, chkquad)
  180. end
  181. love.graphics.draw(canvas)
  182. if nframe <= gif.nimages then
  183. if gif.imgs[nframe*5-4] == 3 then -- Restore After Drawing disposal mode?
  184. -- If so, it hasn't been drawn because that would pollute the canvas. Draw it now.
  185. love.graphics.draw(gif.imgs[nframe*5-2], gif.imgs[nframe*5-1], gif.imgs[nframe*5])
  186. end
  187. end
  188. end
  189. function love.resize(w, h)
  190. if checker then
  191. chkquad = love.graphics.newQuad(0, 0, w, h, 16, 16)
  192. end
  193. end
  194. function love.keypressed(k)
  195. if k == "escape" then love.event.quit() end
  196. end