2025-05-04 21:24:11 +08:00

294 lines
8.4 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- bimg_advanced.lua
-- 增强版 BIMG 图像/动画播放库
-- 功能:
-- 1. 支持文件路径、URL 和直接数据播放
-- 2. 支持循环播放
-- 3. 提供播放控制功能
-- 4. 支持多显示器配置
-- 5. 支持自定义终端对象
local bimg = {}
local currentAnimation = nil
local running = false
local httpEnabled = false
-- 检查并启用HTTP功能
if http and http.get then
httpEnabled = true
end
-- 内部函数:绘制单帧
local function drawFrame(frame, termObj)
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.setCursorPos(1, 1)
print('This image needs monitors to be calibrated before being displayed.')
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 = {}
for y = 1, height do
monitors[y] = {}
for x = 1, width do
local _, oy = term.getCursorPos()
-- 绘制校准界面
for ly = 1, height do
term.setCursorPos(3, oy + ly - 1)
term.clearLine()
for lx = 1, width do
term.blit('\x8F ', (lx == x and ly == y) and '00' or '77', 'ff')
end
end
term.setCursorPos(3, oy + height)
term.write('(' .. x .. ', ' .. y .. ')')
term.setCursorPos(1, oy)
-- 等待用户点击显示器
repeat
local _, name = os.pullEvent('monitor_touch')
monitors[y][x] = name
until not names[name]
names[monitors[y][x]] = true
sleep(0.25)
end
end
settings.set('sanjuuni.multimonitor', monitors)
settings.save()
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
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
if not currentAnimation or not currentAnimation.running then break end
-- 在所有显示器上绘制当前帧
for y = 1, height do
for x = 1, width do
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
break
end
end
else
-- 单显示器播放
termObj.clear()
-- 播放每一帧
for _, frame in ipairs(img) do
if not currentAnimation or not currentAnimation.running then break end
drawFrame(frame, termObj)
-- 等待帧间隔
if img.animation then
sleep(frame.duration or img.secondsPerFrame or 0.05)
else
break
end
end
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
-- 停止播放
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