This commit is contained in:
HKXluo 2025-05-04 21:22:18 +08:00
parent f09c7d77a8
commit 2dbd84e514
No known key found for this signature in database
GPG Key ID: 498DD8DAE5261F38
3 changed files with 285 additions and 724 deletions

View File

@ -56,7 +56,7 @@ luafile = http.get(download_url)
if luafile then if luafile then
print("\nLua file downloaded successfully.") print("\nLua file downloaded successfully.")
-- 保存Lua文件 / Save Lua file -- 保存bimg文件 / Save bimg file
f = fs.open("demo.bimg", "w") f = fs.open("demo.bimg", "w")
f.write(luafile.readAll()) f.write(luafile.readAll())
f.close() f.close()

View File

@ -1,657 +0,0 @@
-- 32vid-player from sanjuuni
-- Licensed under the MIT license
local inflate do
local floor = math.floor
local ceil = math.ceil
local min = math.min
local max = math.max
-- utility functions
local function memoize(f)
local cache = {}
return function(...)
local key = table.concat({...}, "-")
if not cache[key] then
cache[key] = f(...)
end
return cache[key]
end
end
local function int(bytes)
local n = 0
for i = 1, #bytes do
n = 256*n + bytes:sub(i, i):byte()
end
return n
end
int = memoize(int)
local function bint(bits)
return tonumber(bits, 2) or 0
end
bint = memoize(bint)
local function bits(b, width)
local s = ""
if type(b) == "number" then
for i = 1, width do
s = b%2 .. s
b = floor(b/2)
end
else
for i = 1, #b do
s = s .. bits(b:byte(i), 8):reverse()
assert(#s == i * 8, s)
end
end
return s
end
bits = memoize(bits)
local function fill(bytes, len)
return bytes:rep(floor(len / #bytes)) .. bytes:sub(1, len % #bytes)
end
local function zip(t1, t2)
local zipped = {}
for i = 1, max(#t1, #t2) do
zipped[#zipped + 1] = {t1[i], t2[i]}
end
return zipped
end
local function unzip(zipped)
local t1, t2 = {}, {}
for i = 1, #zipped do
t1[#t1 + 1] = zipped[i][1]
t2[#t2 + 1] = zipped[i][2]
end
return t1, t2
end
local function map(f, t)
local mapped = {}
for i = 1, #t do
mapped[#mapped + 1] = f(t[i], i)
end
return mapped
end
local function filter(pred, t)
local filtered = {}
for i = 1, #t do
if pred(t[i], i) then
filtered[#filtered + 1] = t[i]
end
end
return filtered
end
local function find(key, t)
if type(key) == "function" then
for i = 1, #t do
if key(t[i]) then
return i
end
end
return nil
else
return find(function(x) return x == key end, t)
end
end
local function slice(t, i, j, step)
local sliced = {}
for k = i < 1 and 1 or i, i < 1 and #t + i or j or #t, step or 1 do
sliced[#sliced + 1] = t[k]
end
return sliced
end
local function range(i, j)
local r = {}
for k = j and i or 0, j or i - 1 do
r[#r + 1] = k
end
return r
end
-- streams
local function output_stream()
local stream, buffer = {}, {}
local curr = 0
function stream:write(bytes)
for i = 1, #bytes do
buffer[#buffer + 1] = bytes:sub(i, i)
end
curr = curr + #bytes
end
function stream:back_read(offset, n)
local read = {}
for i = curr - offset + 1, curr - offset + n do
read[#read + 1] = buffer[i]
end
return table.concat(read)
end
function stream:back_copy(dist, len)
local start, copied = curr - dist + 1, {}
for i = start, min(start + len, curr) do
copied[#copied + 1] = buffer[i]
end
self:write(fill(table.concat(copied), len))
end
function stream:pos()
return curr
end
function stream:raw()
return table.concat(buffer)
end
return stream
end
local function bit_stream(raw, offset)
local stream = {}
local curr = 0
offset = offset or 0
function stream:read(n, reverse)
local start = floor(curr/8) + offset + 1
local bb = ""
for i = start, start + ceil(n/8) do
local b = raw:byte(i)
for j = 0, 7 do bb = bb .. bit32.extract(b, j, 1) end
end
local b = bb:sub(curr%8 + 1, curr%8 + n)
curr = curr + n
return reverse and b or b:reverse()
end
function stream:seek(n)
if n == "beg" then
curr = 0
elseif n == "end" then
curr = #raw
else
curr = curr + n
end
return self
end
function stream:is_empty()
return curr >= 8*#raw
end
function stream:pos()
return curr
end
return stream
end
-- inflate
local CL_LENS_ORDER = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
local MAX_BITS = 15
local PT_WIDTH = 8
local function cl_code_lens(stream, hclen)
local code_lens = {}
for i = 1, hclen do
code_lens[#code_lens + 1] = bint(stream:read(3))
end
return code_lens
end
local function code_tree(lens, alphabet)
alphabet = alphabet or range(#lens)
local using = filter(function(x, i) return lens[i] and lens[i] > 0 end, alphabet)
lens = filter(function(x) return x > 0 end, lens)
local tree = zip(lens, using)
table.sort(tree, function(a, b)
if a[1] == b[1] then
return a[2] < b[2]
else
return a[1] < b[1]
end
end)
return unzip(tree)
end
local function codes(lens)
local codes = {}
local code = 0
for i = 1, #lens do
codes[#codes + 1] = bits(code, lens[i])
if i < #lens then
code = (code + 1)*2^(lens[i + 1] - lens[i])
end
end
return codes
end
local prefix_table
local function handle_long_codes(codes, alphabet, pt)
local i = find(function(x) return #x > PT_WIDTH end, codes)
local long = slice(zip(codes, alphabet), i)
i = 0
repeat
local prefix = long[i + 1][1]:sub(1, PT_WIDTH)
local same = filter(function(x) return x[1]:sub(1, PT_WIDTH) == prefix end, long)
same = map(function(x) return {x[1]:sub(PT_WIDTH + 1), x[2]} end, same)
pt[prefix] = {rest = prefix_table(unzip(same)), unused = 0}
i = i + #same
until i == #long
end
function prefix_table(codes, alphabet)
local pt = {}
if #codes[#codes] > PT_WIDTH then
handle_long_codes(codes, alphabet, pt)
end
for i = 1, #codes do
local code = codes[i]
if #code > PT_WIDTH then
break
end
local entry = {value = alphabet[i], unused = PT_WIDTH - #code}
if entry.unused == 0 then
pt[code] = entry
else
for i = 0, 2^entry.unused - 1 do
pt[code .. bits(i, entry.unused)] = entry
end
end
end
return pt
end
local function huffman_decoder(lens, alphabet)
local base_codes = prefix_table(codes(lens), alphabet)
return function(stream)
local codes = base_codes
local entry
repeat
entry = codes[stream:read(PT_WIDTH, true)]
stream:seek(-entry.unused)
codes = entry.rest
until not codes
return entry.value
end
end
local function code_lens(stream, decode, n)
local lens = {}
repeat
local value = decode(stream)
if value < 16 then
lens[#lens + 1] = value
elseif value == 16 then
for i = 1, bint(stream:read(2)) + 3 do
lens[#lens + 1] = lens[#lens]
end
elseif value == 17 then
for i = 1, bint(stream:read(3)) + 3 do
lens[#lens + 1] = 0
end
elseif value == 18 then
for i = 1, bint(stream:read(7)) + 11 do
lens[#lens + 1] = 0
end
end
until #lens == n
return lens
end
local function code_trees(stream)
local hlit = bint(stream:read(5)) + 257
local hdist = bint(stream:read(5)) + 1
local hclen = bint(stream:read(4)) + 4
local cl_decode = huffman_decoder(code_tree(cl_code_lens(stream, hclen), CL_LENS_ORDER))
local ll_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hlit)))
local d_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hdist)))
return ll_decode, d_decode
end
local function extra_bits(value)
if value >= 4 and value <= 29 then
return floor(value/2) - 1
elseif value >= 265 and value <= 284 then
return ceil(value/4) - 66
else
return 0
end
end
extra_bits = memoize(extra_bits)
local function decode_len(value, bits)
assert(value >= 257 and value <= 285, "value out of range")
assert(#bits == extra_bits(value), "wrong number of extra bits")
if value <= 264 then
return value - 254
elseif value == 285 then
return 258
end
local len = 11
for i = 1, #bits - 1 do
len = len + 2^(i+2)
end
return floor(bint(bits) + len + ((value - 1) % 4)*2^#bits)
end
decode_len = memoize(decode_len)
local function a(n)
if n <= 3 then
return n + 2
else
return a(n-1) + 2*a(n-2) - 2*a(n-3)
end
end
a = memoize(a)
local function decode_dist(value, bits)
assert(value >= 0 and value <= 29, "value out of range")
assert(#bits == extra_bits(value), "wrong number of extra bits)")
return bint(bits) + a(value - 1)
end
decode_dist = memoize(decode_dist)
function inflate(data)
local stream = bit_stream(data)
local ostream = output_stream()
repeat
local bfinal, btype = bint(stream:read(1)), bint(stream:read(2))
assert(btype == 2, "compression method not supported")
local ll_decode, d_decode = code_trees(stream)
while true do
local value = ll_decode(stream)
if value < 256 then
ostream:write(string.char(value))
elseif value == 256 then
break
else
local len = decode_len(value, stream:read(extra_bits(value)))
value = d_decode(stream)
local dist = decode_dist(value, stream:read(extra_bits(value)))
ostream:back_copy(dist, len)
end
end
until bfinal == 1
return ostream:raw()
end
end
local dfpwm = require "cc.audio.dfpwm"
local hexstr = "0123456789abcdef"
local file, err = fs.open(shell.resolve(...), "rb")
if not file then error(err) end
if file.read(4) ~= "32VD" then file.close() error("Not a 32vid file") end
local width, height, fps, nStreams, flags = ("<HHBBH"):unpack(file.read(8))
local video, audio, subtitles
for _ = 1, nStreams do
local size, nFrames, frameType = ("<IIB"):unpack(file.read(9))
print(("%X"):format(file.seek()), size, nFrames, frameType)
if frameType == 0 and not video then
local data = file.read(size)
if bit32.band(flags, 3) == 2 then data = inflate(data) end
video = {}
local pos = 1
local start = os.epoch "utc"
for i = 1, nFrames do
if i % 100 == 0 or i >= nFrames - 10 then print(i, os.epoch "utc" - start) start = os.epoch "utc" sleep(0) end
local frame = {palette = {}}
local tmp, tmppos = 0, 0
local use5bit, customcompress = bit32.btest(flags, 16), bit32.band(flags, 3) == 3
local codetree = {}
local solidchar, runlen
local function readField(isColor)
if customcompress then
if runlen then
local c = solidchar
runlen = runlen - 1
if runlen == 0 then runlen = nil end
return c
end
if not isColor and solidchar then return solidchar end
-- MARK: Huffman decoding
local node = codetree
while true do
local n = bit32.extract(tmp, tmppos, 1)
tmppos = tmppos - 1
if tmppos < 0 then tmp, pos, tmppos = data:byte(pos), pos + 1, 7 end
if type(node) ~= "table" then error(("Invalid tree state: position %X, frame %d"):format(pos+file.seek()-size-1, i)) end
if type(node[n]) == "number" then
local c = node[n]
if isColor then
if c > 15 then runlen = 2^(c-15)-1 return assert(solidchar)
else solidchar = c end
end
return c
else node = node[n] end
end
else
local n
if tmppos * 5 + 5 > 32 then n = bit32.extract(math.floor(tmp / 0x1000000), tmppos * 5 - 24, 5)
else n = bit32.extract(tmp, tmppos * 5, 5) end
tmppos = tmppos - 1
if tmppos < 0 then tmp, pos = (">I5"):unpack(data, pos) tmppos = 7 end
return n
end
end
if customcompress then
-- MARK: Huffman tree reconstruction
-- read bit lengths
local bitlen = {}
if use5bit then
for j = 0, 15 do
bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)}
end
pos = pos + 16
else
for j = 0, 7 do
tmp, pos = (">I5"):unpack(data, pos)
bitlen[j*8+1] = {s = j*8+1, l = math.floor(tmp / 0x800000000)}
bitlen[j*8+2] = {s = j*8+2, l = math.floor(tmp / 0x40000000) % 32}
for k = 3, 8 do bitlen[j*8+k] = {s = j*8+k, l = bit32.extract(tmp, (8-k)*5, 5)} end
end
end
do
local j = 1
while j <= #bitlen do
if bitlen[j].l == 0 then table.remove(bitlen, j)
else j = j + 1 end
end
end
if #bitlen == 0 then
-- screen is solid character
solidchar = data:byte(pos)
pos = pos + 1
else
-- reconstruct codes from bit lengths
table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end)
bitlen[1].c = 0
for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end
-- create tree from codes
for j = 1, #bitlen do
local c = bitlen[j].c
local node = codetree
for k = bitlen[j].l - 1, 1, -1 do
local n = bit32.extract(c, k, 1)
if not node[n] then node[n] = {} end
node = node[n]
if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end
end
local n = bit32.extract(c, 0, 1)
node[n] = bitlen[j].s
end
-- read first byte
tmp, tmppos, pos = data:byte(pos), 7, pos + 1
end
else readField() end
for y = 1, height do
local line = {"", "", "", {}}
for x = 1, width do
if pos + 5 + 1 >= #data then print(i, pos, x, y) error() end
local n = readField()
line[1] = line[1] .. string.char(128 + (n % 0x20))
line[4][x] = bit32.btest(n, 0x20)
end
frame[y] = line
end
if customcompress then
if tmppos == 7 then pos = pos - 1 end
codetree = {}
-- MARK: Huffman tree reconstruction
-- read bit lengths
local bitlen = {}
for j = 0, 11 do
bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)}
end
pos = pos + 12
do
local j = 1
while j <= #bitlen do
if bitlen[j].l == 0 then table.remove(bitlen, j)
else j = j + 1 end
end
end
if #bitlen == 0 then
-- screen is solid color
solidchar = data:byte(pos)
pos = pos + 1
runlen = math.huge
else
-- reconstruct codes from bit lengths
table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end)
bitlen[1].c = 0
for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end
-- create tree from codes
for j = 1, #bitlen do
local c = bitlen[j].c
local node = codetree
for k = bitlen[j].l - 1, 1, -1 do
local n = bit32.extract(c, k, 1)
if not node[n] then node[n] = {} end
node = node[n]
if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end
end
local n = bit32.extract(c, 0, 1)
node[n] = bitlen[j].s
end
-- read first byte
tmp, tmppos, pos = data:byte(pos), 7, pos + 1
end
for y = 1, height do
local line = frame[y]
for x = 1, width do
local c = readField(true)
line[2] = line[2] .. hexstr:sub(c+1, c+1)
end
end
runlen = nil
for y = 1, height do
local line = frame[y]
for x = 1, width do
local c = readField(true)
line[3] = line[3] .. hexstr:sub(c+1, c+1)
end
end
if tmppos == 7 then pos = pos - 1 end
else
for y = 1, height do
local line = frame[y]
for x = 1, width do
local c = data:byte(pos)
pos = pos + 1
if line[4][x] then c = bit32.bor(bit32.band(bit32.lshift(c, 4), 0xF0), bit32.rshift(c, 4)) end
line[2] = line[2] .. hexstr:sub(bit32.band(c, 15)+1, bit32.band(c, 15)+1)
line[3] = line[3] .. hexstr:sub(bit32.rshift(c, 4)+1, bit32.rshift(c, 4)+1)
end
line[4] = nil
end
end
for n = 1, 16 do frame.palette[n], pos = {data:byte(pos) / 255, data:byte(pos+1) / 255, data:byte(pos+2) / 255}, pos + 3 end
video[i] = frame
end
elseif frameType == 1 and not audio then
audio = {}
if bit32.band(flags, 12) == 0 then
for i = 1, math.ceil(size / 48000) do
local data
if jit then
data = {}
for j = 0, 5 do
local t = {file.read(math.min(size - (i-1) * 48000 - j * 8000, 8000)):byte(1, -1)}
if #t > 0 then for k = 1, #t do data[j*8000+k] = t[k] end end
end
else data = {file.read(math.min(size - (i-1) * 48000, 48000)):byte(1, -1)} end
for j = 1, #data do data[j] = data[j] - 128 end
audio[i] = data
end
elseif bit32.band(flags, 12) == 4 then
local decode = dfpwm.make_decoder()
for i = 1, math.ceil(size / 6000) do
local data = file.read(math.min(size - (i-1)*6000, 6000))
if not data then break end
audio[i] = decode(data)
end
end
elseif frameType == 8 and not subtitles then
subtitles = {}
for _ = 1, nFrames do
local start, length, x, y, color, sz = ("<IIHHBxH"):unpack(file.read(16))
local text = file.read(sz)
local sub = {text, hexstr:sub(bit32.band(color, 15)+1, bit32.band(color, 15)+1):rep(#text),
hexstr:sub(bit32.rshift(color, 4)+1, bit32.rshift(color, 4)+1):rep(#text), x = x, y = y}
for n = start, start + length - 1 do
subtitles[n] = subtitles[n] or {}
subtitles[n][#subtitles[n]+1] = sub
end
end
end
end
file.close()
if not video then error("No video stream found") end
sleep(0)
local speaker = peripheral.find "speaker"
term.clear()
local ok, err = pcall(parallel.waitForAll, function()
local start = os.epoch "utc"
for f, image in ipairs(video) do
for i, v in ipairs(image.palette) do term.setPaletteColor(2^(i-1), table.unpack(v)) end
for y, r in ipairs(image) do
term.setCursorPos(1, y)
term.blit(table.unpack(r))
end
if subtitles and subtitles[f-1] then
for _, v in ipairs(subtitles[f-1]) do
term.setCursorPos(v.x, v.y)
term.blit(table.unpack(v))
end
end
while os.epoch "utc" < start + (f + 1) / fps * 1000 do sleep(1 / fps) end
end
end, function()
if not speaker or not speaker.playAudio or not audio then return end
for _, chunk in ipairs(audio) do
while not speaker.playAudio(chunk) do repeat local ev, sp = os.pullEvent("speaker_audio_empty") until sp == peripheral.getName(speaker) end
end
end)
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.setCursorPos(1, 1)
term.clear()
if not ok then printError(err) end

View File

@ -1,76 +1,294 @@
-- bimg-player from sanjuuni -- bimg_advanced.lua
-- Licensed in the public domain/CC0 -- 增强版 BIMG 图像/动画播放库
-- 功能:
-- 1. 支持文件路径、URL 和直接数据播放
-- 2. 支持循环播放
-- 3. 提供播放控制功能
-- 4. 支持多显示器配置
-- 5. 支持自定义终端对象
local path = ... local bimg = {}
if not path then error("Usage: bimg-player <file.bimg>") end
local file, err = fs.open(shell.resolve(path), "rb") local currentAnimation = nil
if not file then error("Could not open file: " .. err) end local running = false
local img = textutils.unserialize(file.readAll()) local httpEnabled = false
file.close()
local function drawFrame(frame, term) -- 检查并启用HTTP功能
for y, row in ipairs(frame) do if http and http.get then
term.setCursorPos(1, y) httpEnabled = true
term.blit(table.unpack(row))
end
if frame.palette then for i = 0, #frame.palette do
local c = frame.palette[i]
if type(c) == "table" then term.setPaletteColor(2^i, table.unpack(c))
else term.setPaletteColor(2^i, c) end
end end
if img.multiMonitor then term.setTextScale(img.multiMonitor.scale or 0.5) end
end end
if img.multiMonitor then
local width, height = img.multiMonitor.width, img.multiMonitor.height -- 内部函数:绘制单帧
local monitors = settings.get('sanjuuni.multimonitor') local function drawFrame(frame, termObj)
if not monitors or #monitors < height or #monitors[1] < width then for y, row in ipairs(frame) do
termObj.setCursorPos(1, y)
termObj.blit(table.unpack(row))
end
if frame.palette then
for i = 0, #frame.palette do
local c = frame.palette[i]
if type(c) == "table" then
termObj.setPaletteColor(2^i, table.unpack(c))
else
termObj.setPaletteColor(2^i, c)
end
end
end
end
-- 内部函数:加载图像数据
local function loadImageData(data)
if type(data) == "string" then
-- 如果是字符串,尝试反序列化
local success, result = pcall(textutils.unserialize, data)
if success then
return result
else
error("Invalid image data: " .. result)
end
elseif type(data) == "table" then
-- 如果是table直接使用
return data
else
error("Unsupported image data type")
end
end
-- 从文件加载图像
function bimg.loadFromFile(path)
local file, err = fs.open(shell.resolve(path), "rb")
if not file then error("Could not open file: " .. err) end
local data = file.readAll()
file.close()
return loadImageData(data)
end
-- 从URL加载图像
function bimg.loadFromURL(url)
if not httpEnabled then
error("HTTP API is not available")
end
local response = http.get(url)
if not response then
error("Failed to fetch from URL: " .. url)
end
local data = response.readAll()
response.close()
return loadImageData(data)
end
-- 内部函数:校准多显示器
local function calibrateMonitors(width, height)
term.clear() term.clear()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
print('This image needs monitors to be calibrated before being displayed. Please right-click each monitor in order, from the top left corner to the bottom right corner, going right first, then down.') print('This image needs monitors to be calibrated before being displayed.')
monitors = {} print('Please right-click each monitor in order, from the top left corner')
print('to the bottom right corner, going right first, then down.')
local monitors = {}
local names = {} local names = {}
for y = 1, height do for y = 1, height do
monitors[y] = {} monitors[y] = {}
for x = 1, width do for x = 1, width do
local _, oy = term.getCursorPos() local _, oy = term.getCursorPos()
-- 绘制校准界面
for ly = 1, height do for ly = 1, height do
term.setCursorPos(3, oy + ly - 1) term.setCursorPos(3, oy + ly - 1)
term.clearLine() term.clearLine()
for lx = 1, width do term.blit('\x8F ', (lx == x and ly == y) and '00' or '77', 'ff') end for lx = 1, width do
term.blit('\x8F ', (lx == x and ly == y) and '00' or '77', 'ff')
end end
end
term.setCursorPos(3, oy + height) term.setCursorPos(3, oy + height)
term.write('(' .. x .. ', ' .. y .. ')') term.write('(' .. x .. ', ' .. y .. ')')
term.setCursorPos(1, oy) term.setCursorPos(1, oy)
-- 等待用户点击显示器
repeat repeat
local _, name = os.pullEvent('monitor_touch') local _, name = os.pullEvent('monitor_touch')
monitors[y][x] = name monitors[y][x] = name
until not names[name] until not names[name]
names[monitors[y][x]] = true names[monitors[y][x]] = true
sleep(0.25) sleep(0.25)
end end
end end
settings.set('sanjuuni.multimonitor', monitors) settings.set('sanjuuni.multimonitor', monitors)
settings.save() settings.save()
print('Calibration complete. Settings have been saved for future use.') print('Calibration complete. Settings have been saved for future use.')
return monitors
end
-- 播放图像/动画
function bimg.play(source, options)
options = options or {}
local termObj = options.terminal or term
local loop = options.loop or false
local isURL = options.isURL or false
-- 停止当前播放
bimg.stop()
-- 加载图像数据
local img
if type(source) == "string" then
if isURL then
img = bimg.loadFromURL(source)
else
img = bimg.loadFromFile(source)
end end
else
img = loadImageData(source)
end
-- 设置运行标志
running = true
currentAnimation = {
img = img,
term = termObj,
running = true,
loop = loop
}
local function playFrames()
repeat
if img.multiMonitor then
local width, height = img.multiMonitor.width, img.multiMonitor.height
local monitors = settings.get('sanjuuni.multimonitor')
-- 如果需要校准显示器
if not monitors or #monitors < height or #monitors[1] < width then
monitors = calibrateMonitors(width, height)
end
-- 播放多显示器动画
for i = 1, #img, width * height do for i = 1, #img, width * height do
if not currentAnimation or not currentAnimation.running then break end
-- 在所有显示器上绘制当前帧
for y = 1, height do for y = 1, height do
for x = 1, width do for x = 1, width do
drawFrame(img[i + (y-1) * width + x-1], peripheral.wrap(monitors[y][x])) local frameIndex = i + (y-1) * width + x-1
if frameIndex <= #img then
local monitor = peripheral.wrap(monitors[y][x])
if monitor then
drawFrame(img[frameIndex], monitor)
end end
end end
if img.animation then sleep(img[i].duration or img.secondsPerFrame or 0.05)
else read() break end
end end
else end
term.clear()
-- 等待帧间隔
if img.animation then
sleep(img[i].duration or img.secondsPerFrame or 0.05)
else
break
end
end
else
-- 单显示器播放
termObj.clear()
-- 播放每一帧
for _, frame in ipairs(img) do for _, frame in ipairs(img) do
drawFrame(frame, term) if not currentAnimation or not currentAnimation.running then break end
if img.animation then sleep(frame.duration or img.secondsPerFrame or 0.05)
else read() break end drawFrame(frame, termObj)
-- 等待帧间隔
if img.animation then
sleep(frame.duration or img.secondsPerFrame or 0.05)
else
break
end end
term.setBackgroundColor(colors.black) end
term.setTextColor(colors.white) end
term.clear()
term.setCursorPos(1, 1) -- 如果不是循环播放,则退出
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end if not currentAnimation or not currentAnimation.loop then
break
end
until not currentAnimation or not currentAnimation.running
end
-- 播放结束后的清理
local function cleanup()
if currentAnimation and currentAnimation.running then
local termObj = currentAnimation.term
-- 重置终端状态
termObj.setBackgroundColor(colors.black)
termObj.setTextColor(colors.white)
termObj.clear()
termObj.setCursorPos(1, 1)
-- 重置调色板
for i = 0, 15 do
termObj.setPaletteColor(2^i, term.nativePaletteColor(2^i))
end
end
running = false
currentAnimation = nil
end
-- 在新线程中播放
parallel.waitForAny(
function()
local success, err = pcall(playFrames)
if not success then
printError("Error playing animation: " .. err)
end
cleanup()
end,
function()
while currentAnimation and currentAnimation.running do
os.pullEvent("bimg_stop")
currentAnimation.running = false
end
end
)
end end
-- 停止播放
function bimg.stop()
if currentAnimation then
currentAnimation.running = false
os.queueEvent("bimg_stop")
end
running = false
end
-- 检查是否正在播放
function bimg.isPlaying()
return running
end
-- 获取当前播放状态
function bimg.getStatus()
if not currentAnimation then
return {
playing = false,
looping = false,
frameCount = 0,
currentFrame = 0
}
end
return {
playing = currentAnimation.running,
looping = currentAnimation.loop,
frameCount = #currentAnimation.img,
currentFrame = currentAnimation.currentFrame or 0
}
end
return bimg