--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] local table = table local file = file local coroutine = coroutine local surface = surface local UnPredictedCurTime = UnPredictedCurTime local pairs = pairs local color_white = color_white local sui = sui local SUI, NAME = CURRENT_SUI, CURRENT_SUI.name local read_gif = include("sui/libs/gif_loader.lua") local generate_png = include("sui/libs/png_encoder.lua") local images_path = (NAME .. "/images/"):lower() file.CreateDir(images_path) local get_image_path = function(url) return images_path .. (url:gsub("%W", "_") .. ".png") end local Write = file.Write local gif_to_png; do local internal_gif_to_png = function(file_path, chunk) local gif = read_gif(chunk) local frames = gif:get_frames() local w, h = gif.width, gif.height local path = file_path .. "/" file.CreateDir(path) for frame_id = 1, #frames do local frame = frames[frame_id] local data = frame.data local png = generate_png(w, h, data) Write(("%s%d_%d.png"):format(path, frame_id, frame.delay), png) coroutine.yield() end end local delay = 0.01 local next_run = 0 local coroutines = {} local callbacks = {} gif_to_png = function(file_path, data, callback) local co = coroutine.create(internal_gif_to_png) local i = table.insert(coroutines, co) callbacks[i] = callback coroutine.resume(co, file_path, data) next_run = UnPredictedCurTime() end hook.Add("Think", NAME .. "ProcessGIFs", function() local co = coroutines[1] if not co then return end if UnPredictedCurTime() < next_run then return end if coroutine.status(co) == "suspended" then coroutine.resume(co) else callbacks[1]() table.remove(coroutines, 1) table.remove(callbacks, 1) end next_run = UnPredictedCurTime() + delay end) hook.Add(NAME .. "ImagesCleared", "ClearCoroutines", function() table.Empty(coroutines) table.Empty(callbacks) end) end local download_image, is_downloading_image; do -- https://stackoverflow.com/questions/25959386/how-to-check-if-a-file-is-a-valid-image local valid_images = { ["\xff\xd8\xff"] = "jpeg", ["\x89PNG\r\n\x1a\n"] = "png", ["GIF87a"] = "gif", ["GIF89a"] = "gif", } local get_image_type = function(data) for k, v in pairs(valid_images) do if data:StartWith(k) then return v end end return false end local downloading_images = {} local process_callbacks = function(url) local callbacks = downloading_images[url] or {} downloading_images[url] = nil for _, func in ipairs(callbacks) do func() end end download_image = function(url, callback) if downloading_images[url] then table.insert(downloading_images[url], callback) return end downloading_images[url] = {callback} http.Fetch(url, function(data) local image_type = get_image_type(data) if not image_type then downloading_images[url] = nil return end local image_path = get_image_path(url) if image_type == "gif" then gif_to_png(image_path, data, function() process_callbacks(url) end) else file.Write(image_path, data) process_callbacks(url) end end, function(err) print("(SUI) Failed to download an image, error: " .. err) downloading_images[url] = nil end) end is_downloading_image = function(url) return downloading_images[url] ~= nil end hook.Add(NAME .. "ImagesCleared", "ClearDownloadingImages", function() table.Empty(downloading_images) end) end local images_panels = {} local PANEL = {} local err_mat = SUI.Material("error") function PANEL:Init() self:SetMouseInputEnabled(false) self.minus = 0 self.rotation = 0 self.image = err_mat self.image_col = color_white table.insert(images_panels, self) end function PANEL:OnRemove() for k, v in ipairs(images_panels) do if v == self then table.remove(images_panels, k) return end end end function PANEL:SetMinus(minus) self.minus = minus end function PANEL:SetRotation(rotation) self.rotation = rotation end function PANEL:SetImageColor(col) self.image_col = col end local cached_files = {} local get_files = function(image_path) local f = cached_files[image_path] if f then return f end cached_files[image_path] = file.Find(image_path .. "/*", "DATA") return cached_files[image_path] end function PANEL:SetImage(url) self.image = err_mat self.pos = nil self.delay = nil self.images = nil self.delays = nil self.url = url if url:sub(1, 4) ~= "http" then self.image = SUI.Material(url) return end local image_path = get_image_path(url) if not file.Exists(image_path, "DATA") or is_downloading_image(url) then download_image(url, function() if self:IsValid() then self:SetImage(url) end end) return end local is_gif = file.IsDir(image_path, "DATA") if is_gif then local images = {} local delays = {} local files = get_files(image_path) for i = 1, #files do local v = files[i] local id, delay = v:match("(.*)_(.*)%.png") id = tonumber(id) local img_path = "../data/" .. image_path .. "/" .. v images[id] = img_path delays[id] = delay end self.frame = 1 self.delay = (UnPredictedCurTime() * 100) + delays[1] self.images = images self.delays = delays self.max_images = #files else self.image = SUI.Material("../data/" .. image_path) end end local SetMaterial = surface.SetMaterial function PANEL:PaintGIF(w, h, images) local frame = self.frame -- SUI.Material() caches materials by default local mat = SUI.Material(images[frame], true) if not mat then if frame > 1 then mat = SUI.Material(images[frame - 1]) else mat = err_mat end SetMaterial(mat) return end SetMaterial(mat) local curtime = UnPredictedCurTime() * 100 if curtime < self.delay then return end frame = frame + 1 if frame > self.max_images then frame = 1 end self.frame = frame self.delay = curtime + self.delays[frame] end local PaintGIF = PANEL.PaintGIF local SetDrawColor = surface.SetDrawColor local DrawTexturedRectRotated = surface.DrawTexturedRectRotated function PANEL:Paint(w, h) SetDrawColor(self.image_col) local images = self.images if images then PaintGIF(self, w, h, images) else SetMaterial(self.image) end if self.Draw then self:Draw(w, h, true) else local minus = self.minus DrawTexturedRectRotated(w * 0.5, h * 0.5, w - minus, h - minus, self.rotation) end end sui.register("Image", PANEL, "PANEL") function SUI.ClearImages() local files, dirs = file.Find(images_path .. "/*", "DATA") for _, f in ipairs(files) do file.Delete(images_path .. f) end for _, d in ipairs(dirs) do for _, f in ipairs(file.Find(images_path .. d .. "/*", "DATA")) do file.Delete(images_path .. (d .. "/" .. f)) end file.Delete(images_path .. d) end table.Empty(SUI.materials) table.Empty(cached_files) hook.Call(NAME .. "ImagesCleared") for k, v in ipairs(images_panels) do if v.url then v:SetImage(v.url) end end end