This commit is contained in:
294
Demo/SanjuuniApi-Demo/lib/bimg/bimg-player.lua
Normal file
294
Demo/SanjuuniApi-Demo/lib/bimg/bimg-player.lua
Normal file
@@ -0,0 +1,294 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user