mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 05:43:46 +03:00
Upload
This commit is contained in:
334
lua/sui/vgui/sui_image.lua
Normal file
334
lua/sui/vgui/sui_image.lua
Normal file
@@ -0,0 +1,334 @@
|
||||
--[[
|
||||
| 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
|
||||
Reference in New Issue
Block a user