Advertisement
daspamer1

CE GIF script + example (fetches from internet)

May 23rd, 2019
214
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.11 KB | None | 0 0
  1. --------------------------------------------------------------------------------------------------------------------------
  2. -- Decoder of GIF-files
  3. --------------------------------------------------------------------------------------------------------------------------
  4. -- This module extracts images from GIF-files.
  5. -- Written in pure Lua.
  6. -- Compatible with Lua 5.1, 5.2, 5.3, LuaJIT.
  7.  
  8. -- Version 1  (2017-05-15)
  9.  
  10. -- require('gif')(filename) opens .gif-file for read-only and returns "GifObject" having the following functions:
  11. --    read_matrix(x, y, width, height)
  12. --       returns current image (one animated frame) as 2D-matrix of colors (as nested Lua tables)
  13. --       by default whole non-clipped picture is returned
  14. --       pixels are numbers: (-1) for transparent color, 0..0xFFFFFF for 0xRRGGBB color
  15. --    get_width_height()
  16. --       returns two integers (width and height are the properties of the whole file)
  17. --    get_file_parameters()
  18. --       returns table with the following fields (these are the properties of the whole file)
  19. --          comment           -- text coment inside gif-file
  20. --          looped            -- boolean
  21. --          number_of_images  -- == 1 for non-animated gifs, > 1 for animated gifs
  22. --    get_image_parameters()
  23. --       returns table with fields image_no and delay_in_ms (these are properties of the current animation frame)
  24. --    next_image(looping_mode)
  25. --       switches to next frame, returns false if failed to switch
  26. --       looping_mode = 'never' (default) - never wrap from the last frame to the first
  27. --                      'always'          - always wrap from the last frame to the first
  28. --                      'play'            - depends on whether or not .gif-file is marked as looped gif
  29. --    close()
  30. --------------------------------------------------------------------------------------------------------------------------
  31. --[[
  32.         script source: https://github.com/Egor-Skriptunoff/pure_lua_GIF
  33.         credit: Egor-Skriptunoff
  34.  
  35.  
  36.         modified to work with CE stream data;
  37.  
  38.         supply a stream with GIF initialized
  39.  
  40. --]]
  41. function btToString(t)
  42.     for k,v in pairs(t) do
  43.         t[k] = string.char(v);
  44.     end
  45.     return table.concat(t);
  46. end
  47. function open_gif(input)
  48.    local gif = {}
  49.    assert(({GIF87a=0,GIF89a=0})[btToString(input.read(6))], 'wrong file format')
  50.    local gif_width, gif_height = input.readWord(),input.readWord();
  51.    assert(gif_width ~= 0 and gif_height ~= 0, 'wrong file format')
  52.    function gif.get_width_height()
  53.       return gif_width, gif_height
  54.    end
  55.    local global_flags = input.readByte();
  56.    input.read(2);
  57.    local global_palette           -- 0-based global palette array (or nil)
  58.    if global_flags >= 0x80 then
  59.       global_palette = {}
  60.       for color_index = 0, 2^(global_flags % 8 + 1) - 1 do
  61.          local R, G, B = unpack(input.read(3));
  62.          global_palette[color_index] = R * 2^16 + G * 2^8 + B
  63.       end
  64.    end
  65.    local first_frame_offset = input.position;
  66.  
  67.    local file_parameters                   -- initially nil, filled after finishing first pass
  68.    local fp_comment, fp_looped_animation   -- for storing parameters before first pass completed
  69.    local fp_number_of_frames = 0
  70.    local fp_last_processed_offset = 0
  71.  
  72.    local function fp_first_pass()
  73.       if not file_parameters then
  74.          local current_offset = input.position;
  75.          if current_offset > fp_last_processed_offset then
  76.             fp_last_processed_offset = current_offset
  77.             return true
  78.          end
  79.       end
  80.    end
  81.  
  82.    local function skip_to_end_of_block()
  83.       repeat
  84.          local size = input.readByte()
  85.          input.read(size)
  86.       until size == 0
  87.    end
  88.  
  89.    local function skip_2C()
  90.       input.read(8);
  91.       local local_flags = input.readByte()
  92.       if local_flags >= 0x80 then
  93.          input.read(3 * 2^(local_flags % 8 + 1))
  94.       end
  95.       input.readByte();
  96.       skip_to_end_of_block()
  97.    end
  98.  
  99.    local function process_blocks(callback_2C, callback_21_F9)
  100.       -- processing blocks of GIF-file
  101.       if (input.size == input.position) then -- end of stream
  102.         return 'EOF';
  103.       end
  104.       local exit_reason
  105.       repeat
  106.          local starter = input.readByte()
  107.          if starter == 0x3B then        -- EOF marker
  108.             if fp_first_pass() then
  109.                file_parameters = {comment = fp_comment, looped = fp_looped_animation, number_of_images = fp_number_of_frames}
  110.             end
  111.             exit_reason = 'EOF'
  112.          elseif starter == 0x2C then    -- image marker
  113.             if fp_first_pass() then
  114.                fp_number_of_frames = fp_number_of_frames + 1
  115.             end
  116.             exit_reason = (callback_2C or skip_2C)()
  117.          elseif starter == 0x21 then
  118.             local fn_no = input.readByte()
  119.             if fn_no == 0xF9 then
  120.                (callback_21_F9 or skip_to_end_of_block)()
  121.             elseif fn_no == 0xFE and not fp_comment then
  122.                fp_comment = {}
  123.                repeat
  124.                   local size = input.readByte()
  125.                   table.insert(fp_comment, btToString(input.read(size)))
  126.                until size == 0
  127.                fp_comment = table.concat(fp_comment)
  128.             elseif fn_no == 0xFF and btToString(input.read(input.readByte())) == 'NETSCAPE2.0' then
  129.                fp_looped_animation = true
  130.                skip_to_end_of_block()
  131.             else
  132.                skip_to_end_of_block()
  133.             end
  134.          else
  135.             error'wrong file format'
  136.          end
  137.       until exit_reason
  138.       return exit_reason
  139.    end
  140.  
  141.    function gif.get_file_parameters()
  142.       if not file_parameters then
  143.          local saved_offset = input.position;
  144.          process_blocks()
  145.          input.position = saved_offset;
  146.       end
  147.       return file_parameters
  148.    end
  149.  
  150.    local loaded_frame_no = 0          --\ frame parameters (frame_no = 1, 2, 3,...)
  151.    local loaded_frame_delay           --/
  152.    local loaded_frame_action_on_background
  153.    local loaded_frame_transparent_color_index
  154.    local loaded_frame_matrix                  -- picture of the frame        \ may be two pointers to the same matrix
  155.    local background_matrix_after_loaded_frame -- background for next picture /
  156.    local background_rectangle_to_erase        -- nil or {left, top, width, height}
  157.  
  158.    function gif.read_matrix(x, y, width, height)
  159.       -- by default whole picture rectangle
  160.       x, y = x or 0, y or 0
  161.       width, height = width or gif_width - x, height or gif_height - y
  162.       assert(x >= 0 and y >= 0 and width >= 1 and height >= 1 and x + width <= gif_width and y + height <= gif_height,
  163.             'attempt to read pixels out of the picture boundary')
  164.       local matrix = {}
  165.       for row_no = 1, height do
  166.          matrix[row_no] = {unpack(loaded_frame_matrix[row_no + y], x + 1, x + width)}
  167.       end
  168.       return matrix
  169.    end
  170.  
  171.    function gif.close()
  172.       loaded_frame_matrix = nil
  173.       background_matrix_after_loaded_frame = nil
  174.    end
  175.  
  176.    function gif.get_image_parameters()
  177.       return {image_no = loaded_frame_no, delay_in_ms = loaded_frame_delay}
  178.    end
  179.    local function callback_2C()
  180.       if background_rectangle_to_erase then
  181.          local left, top, width, height = unpack(background_rectangle_to_erase)
  182.          background_rectangle_to_erase = nil
  183.          for row = top + 1, top + height do
  184.             local line = background_matrix_after_loaded_frame[row]
  185.             for col = left + 1, left + width do
  186.                line[col] = -1
  187.             end
  188.          end
  189.       end
  190.       loaded_frame_action_on_background = loaded_frame_action_on_background or 'combine'
  191.       local left, top, width, height = input.readWord(),input.readWord(),input.readWord(),input.readWord();
  192.       assert(width ~= 0 and height ~= 0 and left + width <= gif_width and top + height <= gif_height, 'wrong file format')
  193.       local local_flags = input.readByte()
  194.       local interlaced = local_flags % 0x80 >= 0x40
  195.       local palette = global_palette          -- 0-based palette array
  196.       if local_flags >= 0x80 then
  197.          palette = {}
  198.          for color_index = 0, 2^(local_flags % 8 + 1) - 1 do
  199.             local R, G, B = unpack(input.read(3))
  200.             palette[color_index] = R * 2^16 + G * 2^8 + B
  201.          end
  202.       end
  203.       assert(palette, 'wrong file format')
  204.       local bits_in_color = input.readByte()  -- number of colors in LZW voc
  205.  
  206.       local bytes_in_current_part_of_stream = 0
  207.       local function read_byte_from_stream()   -- returns next byte or false
  208.          if bytes_in_current_part_of_stream > 0 then
  209.             bytes_in_current_part_of_stream = bytes_in_current_part_of_stream - 1
  210.             return input.readByte()
  211.          else
  212.             bytes_in_current_part_of_stream = input.readByte() - 1
  213.             return bytes_in_current_part_of_stream >= 0 and input.readByte()
  214.          end
  215.       end
  216.  
  217.       local CLEAR_VOC = 2^bits_in_color
  218.       local END_OF_STREAM = CLEAR_VOC + 1
  219.  
  220.       local LZW_voc         -- [code] = {prefix_code, color_index}
  221.       local bits_in_code = bits_in_color + 1
  222.       local next_power_of_two = 2^bits_in_code
  223.       local first_undefined_code, need_completion
  224.  
  225.       local stream_bit_buffer = 0
  226.       local bits_in_buffer = 0
  227.       local function read_code_from_stream()
  228.          while bits_in_buffer < bits_in_code do
  229.             stream_bit_buffer = stream_bit_buffer + assert(read_byte_from_stream(), 'wrong file format') * 2^bits_in_buffer
  230.             bits_in_buffer = bits_in_buffer + 8
  231.          end
  232.          local code = stream_bit_buffer % next_power_of_two
  233.          stream_bit_buffer = (stream_bit_buffer - code) / next_power_of_two
  234.          bits_in_buffer = bits_in_buffer - bits_in_code
  235.          return code
  236.       end
  237.  
  238.       assert(read_code_from_stream() == CLEAR_VOC, 'wrong file format')
  239.  
  240.       local function clear_LZW_voc()
  241.          LZW_voc = {}
  242.          bits_in_code = bits_in_color + 1
  243.          next_power_of_two = 2^bits_in_code
  244.          first_undefined_code = CLEAR_VOC + 2
  245.          need_completion = nil
  246.       end
  247.  
  248.       clear_LZW_voc()
  249.  
  250.       -- Copy matrix background_matrix_after_loaded_frame to loaded_frame_matrix
  251.  
  252.       if loaded_frame_action_on_background == 'combine' or loaded_frame_action_on_background == 'erase' then
  253.          loaded_frame_matrix = background_matrix_after_loaded_frame
  254.       else  -- 'undo'
  255.          loaded_frame_matrix = {}
  256.          for row = 1, gif_height do
  257.             loaded_frame_matrix[row] = {unpack(background_matrix_after_loaded_frame[row])}
  258.          end
  259.       end
  260.  
  261.       -- Decode and apply image delta (window: left, top, width, height) on the matrix loaded_frame_matrix
  262.  
  263.       local pixels_remained = width * height
  264.       local x_inside_window, y_inside_window  -- coordinates inside window
  265.       local function pixel_from_stream(color_index)
  266.          pixels_remained = pixels_remained - 1
  267.          assert(pixels_remained >= 0, 'wrong file format')
  268.          if x_inside_window then
  269.             x_inside_window = x_inside_window + 1
  270.             if x_inside_window == width then
  271.                x_inside_window = 0
  272.                if interlaced then
  273.                   repeat
  274.                      if y_inside_window % 8 == 0 then
  275.                         y_inside_window = y_inside_window < height and y_inside_window + 8 or 4
  276.                      elseif y_inside_window % 4 == 0 then
  277.                         y_inside_window = y_inside_window < height and y_inside_window + 8 or 2
  278.                      elseif y_inside_window % 2 == 0 then
  279.                         y_inside_window = y_inside_window < height and y_inside_window + 4 or 1
  280.                      else
  281.                         y_inside_window = y_inside_window + 2
  282.                      end
  283.                   until y_inside_window < height
  284.                else
  285.                   y_inside_window = y_inside_window + 1
  286.                end
  287.             end
  288.          else
  289.             x_inside_window, y_inside_window = 0, 0
  290.          end
  291.          if color_index ~= loaded_frame_transparent_color_index then
  292.             loaded_frame_matrix[top + y_inside_window + 1][left + x_inside_window + 1]
  293.                = assert(palette[color_index], 'wrong file format')
  294.          end
  295.       end
  296.  
  297.       repeat
  298.          -- LZW_voc: [code] = {prefix_code, color_index}
  299.          -- all the codes (CLEAR_VOC+2)...(first_undefined_code-2) are defined completely
  300.          -- the code (first_undefined_code-1) has defined only its first component
  301.          local code = read_code_from_stream()
  302.          if code == CLEAR_VOC then
  303.             clear_LZW_voc()
  304.          elseif code ~= END_OF_STREAM then
  305.             assert(code < first_undefined_code, 'wrong file format')
  306.             local stack_of_pixels = {}
  307.             local pos = 1
  308.             local first_pixel = code
  309.             while first_pixel >= CLEAR_VOC do
  310.                first_pixel, stack_of_pixels[pos] = unpack(LZW_voc[first_pixel])
  311.                pos = pos + 1
  312.             end
  313.             stack_of_pixels[pos] = first_pixel
  314.             if need_completion then
  315.                need_completion = nil
  316.                LZW_voc[first_undefined_code - 1][2] = first_pixel
  317.                if code == first_undefined_code - 1 then
  318.                   stack_of_pixels[1] = first_pixel
  319.                end
  320.             end
  321.             -- send pixels for phrase "code" to result matrix
  322.             for pos = pos, 1, -1 do
  323.                pixel_from_stream(stack_of_pixels[pos])
  324.             end
  325.             if first_undefined_code < 0x1000 then
  326.                -- create new code
  327.                LZW_voc[first_undefined_code] = {code}
  328.                need_completion = true
  329.                if first_undefined_code == next_power_of_two then
  330.                   bits_in_code = bits_in_code + 1
  331.                   next_power_of_two = 2^bits_in_code
  332.                end
  333.                first_undefined_code = first_undefined_code + 1
  334.             end
  335.          end
  336.       until code == END_OF_STREAM
  337.  
  338.       assert(pixels_remained == 0 and stream_bit_buffer == 0, 'wrong file format')
  339.       local extra_byte = read_byte_from_stream()
  340.       assert(not extra_byte or extra_byte == 0 and not read_byte_from_stream(), 'wrong file format')
  341.  
  342.       -- Modify the matrix background_matrix_after_loaded_frame
  343.       if loaded_frame_action_on_background == 'combine' then
  344.          background_matrix_after_loaded_frame = loaded_frame_matrix
  345.       elseif loaded_frame_action_on_background == 'erase' then
  346.          background_matrix_after_loaded_frame = loaded_frame_matrix
  347.          background_rectangle_to_erase = {left, top, width, height}
  348.       end
  349.       loaded_frame_no = loaded_frame_no + 1
  350.       return 'OK'
  351.    end
  352.  
  353.    local function callback_21_F9()
  354.       local len, flags = unpack(input.read(2))
  355.       local delay = input.readWord()
  356.       local transparent, terminator = unpack(input.read(2))
  357.       assert(len == 4 and terminator == 0, 'wrong file format')
  358.       loaded_frame_delay = delay * 10
  359.       if flags % 2 == 1 then
  360.          loaded_frame_transparent_color_index = transparent
  361.       end
  362.       local method = flags // 4 % 8
  363.       if method == 2 then
  364.          loaded_frame_action_on_background = 'erase'
  365.       elseif method == 3 then
  366.          loaded_frame_action_on_background = 'undo'
  367.       end
  368.    end
  369.    local function load_next_frame()
  370.       -- returns true if next frame was loaded (of false if there is no next frame)
  371.       if loaded_frame_no == 0 then
  372.          background_matrix_after_loaded_frame = {}
  373.          for y = 1, gif_height do
  374.             background_matrix_after_loaded_frame[y] = {}
  375.          end
  376.          background_rectangle_to_erase = {0, 0, gif_width, gif_height}
  377.          input.position = first_frame_offset
  378.       end
  379.       loaded_frame_action_on_background = nil
  380.       loaded_frame_transparent_color_index = nil
  381.       return process_blocks(callback_2C, callback_21_F9) ~= 'EOF'
  382.    end
  383.    assert(load_next_frame(), 'wrong file format')
  384.    function gif.next_image(looping_mode)
  385.       if load_next_frame() then
  386.          return true
  387.       else
  388.          return false
  389.       end
  390.    end
  391.    return gif
  392. end
  393.  
  394.  
  395. --[[
  396.     my script starts here;
  397.     fetches frame using the GIF decoder;
  398.     encodes as a bitmap streams into a table
  399.  
  400.     source: https://en.wikipedia.org/wiki/BMP_file_format;
  401. --]]
  402. local char = string.char;
  403. local insert = table.insert
  404. local unpack = unpack;
  405. local function encodeInteger(n)
  406.     return char(n&0xFF,(n>>8)&0xFF,(n>>16)&0xFF,(n>>24)&0xFF);
  407. end
  408. local function encodePixel(p,...)
  409.     if(not p)then return;end;return char(p&0xFF,p>>8&0xFF,p>>16&0xFF),encodePixel(...);
  410. end
  411. local function table_insert(t,i,...)
  412.     if(i)then insert(t,i);return table_insert(t,...);end;
  413. end
  414. local function encodeBitmap(pd)
  415.     assert(type(pd)=='table'and type(pd[1])=='table','error invalid pixel data');
  416.     local h,w = #pd,#pd[1]; -- height,width of image
  417.     local rowWidth = w*3;
  418.     local rowSize = math.ceil((rowWidth)/4)*4
  419.     local pad = ('\0'):rep(rowSize-rowWidth);
  420.     local bytes = h*rowSize;
  421.     local _3n,_4n = ("\0"):rep(3),("\0"):rep(4);
  422.     local bmp = {'BM',encodeInteger(54+bytes),_4n,"\54",_3n,"\40",_3n,encodeInteger(w),encodeInteger(h),"\1\0\24\0",_4n,encodeInteger(bytes),_4n:rep(4)};
  423.     -- for y,row in pairs(pd) do
  424.     for i=#pd,1,-1 do -- reversed;
  425.         table_insert(bmp,encodePixel(unpack(pd[i])));
  426.         table_insert(bmp,pad);
  427.     end
  428.     bmp = table.concat(bmp);
  429.     return bmp -- returns bitmap string; save it; or load into a stream;
  430. end
  431.  
  432. function extractGIF(gifString)
  433.     if(type(gifString)=='string')then gifString=createStringStream(gifString);end;
  434.     local gif = open_gif(gifString);
  435.     local currentFrame = gif.read_matrix();
  436.     local bitmaps = {};
  437.     local id = 1;
  438.     while (true) do
  439.         local bitmap = encodeBitmap(currentFrame);
  440.         local bitmapStream = createStringStream(bitmap); -- creates a bitmap stream, so we could load using
  441.         table.insert(bitmaps,bitmapStream);
  442.         if (not gif.next_image()) then -- will try to load next frame;
  443.             break;
  444.         end
  445.         id = id + 1;
  446.         currentFrame = gif.read_matrix(); -- fetch next frame matrix;
  447.     end
  448.     return bitmaps,gif; -- table with bitmap streams; gif object -> use this to obtain information (such as delay and etc.);
  449. end
  450.  
  451. -- EXAMPLE HERE
  452. local _internet
  453. function getImageString(url)
  454.     _internet = (_internet or getInternet());
  455.     return _internet.getURL(url);
  456. end
  457.  
  458. local f = createForm();
  459. -- get gif information and extract it;
  460. local gifString = getImageString('https://media.tenor.com/images/e885b897e6928712233f6e9ce7b8ff67/tenor.gif')
  461. -- local gifString = getImageString('https://media.tenor.com/images/dfb11ec7ee94e551ceeac653ce636d21/tenor.gif')
  462. bitmapStreams,gif = extractGIF(gifString);
  463. local i = createImage(f);
  464. i.width,i.height = gif.get_width_height(); -- set width && height
  465. f.autosize = true; -- auto size to image size
  466.  
  467. -- animate the image!
  468. t = createTimer(f);
  469. t.interval = gif.get_image_parameters().delay_in_ms; -- based on first frame delay
  470. local id = 1;
  471. t.onTimer = function(sender)
  472.     id = (id > #bitmapStreams and 1 or id);
  473.     local stream = bitmapStreams[id];
  474.     stream.position = 0;
  475.     i.Picture.loadFromStream(stream);
  476.     id = id + 1;
  477. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement