MineOS/lib/image.lua

450 lines
14 KiB
Lua
Executable File

-------------------------------------------------- Libraries --------------------------------------------------
local color = require("color")
local unicode = require("unicode")
local fs = require("filesystem")
local gpu = require("component").gpu
-------------------------------------------------- Constants --------------------------------------------------
local image = {}
image.formatModules = {}
-------------------------------------------------- Low-level methods --------------------------------------------------
function image.iterationYield(iteration)
if iteration % 603 == 0 then os.sleep(0) end
end
function image.getImageCoordinatesByIndex(index, width)
local integer, fractional = math.modf((index - 2) / (width * 4))
return math.ceil(fractional * width), integer + 1
end
function image.getImageIndexByCoordinates(x, y, width)
return (width * 4) * (y - 1) + x * 4 - 1
end
function image.group(picture, compressColors)
local groupedPicture, x, y, iPlus2, iPlus3, background, foreground = {}, 1, 1
for i = 3, #picture, 4 do
iPlus2, iPlus3 = i + 2, i + 3
if compressColors then
background, foreground = color.to8Bit(picture[i]), color.to8Bit(picture[i + 1])
image.iterationYield(i)
else
background, foreground = picture[i], picture[i + 1]
end
groupedPicture[picture[iPlus2]] = groupedPicture[picture[iPlus2]] or {}
groupedPicture[picture[iPlus2]][picture[iPlus3]] = groupedPicture[picture[iPlus2]][picture[iPlus3]] or {}
groupedPicture[picture[iPlus2]][picture[iPlus3]][background] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background] or {}
groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground] or {}
groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y] or {}
table.insert(groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y], x)
x = x + 1
if x > picture[1] then
x, y = 1, y + 1
end
end
return groupedPicture
end
function image.draw(x, y, picture)
local groupedPicture = image.group(picture)
local _, _, currentBackground, currentForeground, gpuGetBackground, imageX, imageY
for alpha in pairs(groupedPicture) do
for symbol in pairs(groupedPicture[alpha]) do
if not (symbol == " " and alpha == 0xFF) then
for background in pairs(groupedPicture[alpha][symbol]) do
if background ~= currentBackground then
currentBackground = background
gpu.setBackground(background)
end
for foreground in pairs(groupedPicture[alpha][symbol][background]) do
if foreground ~= currentForeground and symbol ~= " " then
currentForeground = foreground
gpu.setForeground(foreground)
end
for yPos in pairs(groupedPicture[alpha][symbol][background][foreground]) do
for xPos = 1, #groupedPicture[alpha][symbol][background][foreground][yPos] do
imageX, imageY = x + groupedPicture[alpha][symbol][background][foreground][yPos][xPos] - 1, y + yPos - 1
if alpha > 0x0 then
_, _, gpuGetBackground = gpu.get(imageX, imageY)
if alpha == 0xFF then
currentBackground = gpuGetBackground
gpu.setBackground(currentBackground)
else
currentBackground = color.blend(gpuGetBackground, background, alpha / 0xFF)
gpu.setBackground(currentBackground)
end
end
gpu.set(imageX, imageY, symbol)
end
end
end
end
end
end
end
end
function image.create(width, height, background, foreground, alpha, symbol, random)
local picture = {width, height}
for i = 1, width * height do
table.insert(picture, random and math.random(0x0, 0xFFFFFF) or (background or 0x0))
table.insert(picture, random and math.random(0x0, 0xFFFFFF) or (foreground or 0x0))
table.insert(picture, alpha or 0x0)
table.insert(picture, random and string.char(math.random(65, 90)) or (symbol or " "))
end
return picture
end
function image.copy(picture)
local newPicture = {}
for i = 1, #picture do
table.insert(newPicture, picture[i])
end
return newPicture
end
function image.optimize(picture)
local iPlus1, iPlus2, iPlus3
for i = 3, #picture, 4 do
iPlus1, iPlus2, iPlus3 = i + 1, i + 2, i + 3
if picture[i] == picture[iPlus1] and (picture[iPlus3] == "" or picture[iPlus3] == "") then
picture[iPlus3] = " "
end
if picture[iPlus3] == " " then
picture[iPlus1] = 0x000000
end
end
return picture
end
-------------------------------------------------- Filesystem related methods --------------------------------------------------
function image.loadFormatModule(path, fileExtension)
local loadSuccess, loadReason = loadfile(path)
if loadSuccess then
local xpcallSuccess, xpcallReason = pcall(loadSuccess, image)
if xpcallSuccess then
image.formatModules[fileExtension] = xpcallReason
else
error("Failed to execute image format module: " .. tostring(xpcallReason))
end
else
error("Failed to load image format module: " .. tostring(loadReason))
end
end
local function getFileExtension(path)
return string.match(path, "^.+(%.[^%/]+)%/?$")
end
local function loadOrSave(methodName, path, ...)
local fileExtension = getFileExtension(path)
if image.formatModules[fileExtension] then
return image.formatModules[fileExtension][methodName](path, ...)
else
error("Failed to open file \"" .. tostring(path) .. "\" as image: format module for extension \"" .. tostring(fileExtension) .. "\" is not loaded")
end
end
function image.save(path, picture, encodingMethod)
return loadOrSave("save", path, image.optimize(picture), encodingMethod)
end
function image.load(path)
if fs.exists(path) then
return loadOrSave("load", path)
else
error("Failed to load image: file \"" .. tostring(path) .. "\" doesn't exists")
end
end
-------------------------------------------------- Image serialization --------------------------------------------------
function image.toString(picture)
local charArray = {
string.format("%02X", picture[1]),
string.format("%02X", picture[2])
}
for i = 3, #picture, 4 do
table.insert(charArray, string.format("%02X", color.to8Bit(picture[i])))
table.insert(charArray, string.format("%02X", color.to8Bit(picture[i + 1])))
table.insert(charArray, string.format("%02X", math.floor(picture[i + 2] * 255)))
table.insert(charArray, picture[i + 3])
image.iterationYield(i)
end
return table.concat(charArray)
end
function image.fromString(pictureString)
local picture = {
tonumber("0x" .. unicode.sub(pictureString, 1, 2)),
tonumber("0x" .. unicode.sub(pictureString, 3, 4))
}
for i = 5, unicode.len(pictureString), 7 do
table.insert(picture, color.to24Bit(tonumber("0x" .. unicode.sub(pictureString, i, i + 1))))
table.insert(picture, color.to24Bit(tonumber("0x" .. unicode.sub(pictureString, i + 2, i + 3))))
table.insert(picture, tonumber("0x" .. unicode.sub(pictureString, i + 4, i + 5)) / 255)
table.insert(picture, unicode.sub(pictureString, i + 6, i + 6))
end
return picture
end
-------------------------------------------------- Image processing --------------------------------------------------
function image.set(picture, x, y, background, foreground, alpha, symbol)
local index = image.getImageIndexByCoordinates(x, y, picture[1])
picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = background, foreground, alpha, symbol
return picture
end
function image.get(picture, x, y)
local index = image.getImageIndexByCoordinates(x, y, picture[1])
return picture[index], picture[index + 1], picture[index + 2], picture[index + 3]
end
function image.getSize(picture)
return picture[1], picture[2]
end
function image.getWidth(picture)
return picture[1]
end
function image.getHeight(picture)
return picture[2]
end
function image.transform(picture, newWidth, newHeight)
local newPicture, stepWidth, stepHeight, background, foreground, alpha, symbol = {newWidth, newHeight}, picture[1] / newWidth, picture[2] / newHeight
local x, y = 1, 1
for j = 1, newHeight do
for i = 1, newWidth do
background, foreground, alpha, symbol = image.get(picture, math.floor(x), math.floor(y))
table.insert(newPicture, background)
table.insert(newPicture, foreground)
table.insert(newPicture, alpha)
table.insert(newPicture, symbol)
x = x + stepWidth
end
x, y = 1, y + stepHeight
end
return newPicture
end
function image.crop(picture, fromX, fromY, width, height)
if fromX >= 1 and fromY >= 1 and fromX + width - 1 <= picture[1] and fromY + height - 1 <= picture[2] then
local newPicture, background, foreground, alpha, symbol = {width, height}
for y = fromY, fromY + height - 1 do
for x = fromX, fromX + width - 1 do
background, foreground, alpha, symbol = image.get(picture, x, y)
table.insert(newPicture, background)
table.insert(newPicture, foreground)
table.insert(newPicture, alpha)
table.insert(newPicture, symbol)
end
end
return newPicture
else
error("Failed to crop image: target coordinates are out of source range")
end
end
function image.flipHorizontally(picture)
local newPicture, background, foreground, alpha, symbol = {picture[1], picture[2]}
for y = 1, picture[2] do
for x = picture[1], 1, -1 do
background, foreground, alpha, symbol = image.get(picture, x, y)
table.insert(newPicture, background)
table.insert(newPicture, foreground)
table.insert(newPicture, alpha)
table.insert(newPicture, symbol)
end
end
return newPicture
end
function image.flipVertically(picture)
local newPicture, background, foreground, alpha, symbol = {picture[1], picture[2]}
for y = picture[2], 1, -1 do
for x = 1, picture[1] do
background, foreground, alpha, symbol = image.get(picture, x, y)
table.insert(newPicture, background)
table.insert(newPicture, foreground)
table.insert(newPicture, alpha)
table.insert(newPicture, symbol)
end
end
return newPicture
end
function image.expand(picture, fromTop, fromBottom, fromLeft, fromRight, background, foreground, alpha, symbol)
local newPicture = image.create(picture[1] + fromRight + fromLeft, picture[2] + fromTop + fromBottom, background, foreground, alpha, symbol)
for y = 1, picture[2] do
for x = 1, picture[1] do
image.set(newPicture, x + fromLeft, y + fromTop, image.get(picture, x, y))
end
end
return newPicture
end
function image.blend(picture, blendColor, transparency)
local newPicture = {picture[1], picture[2]}
for i = 3, #picture, 4 do
table.insert(newPicture, color.blend(picture[i], blendColor, transparency))
table.insert(newPicture, color.blend(picture[i + 1], blendColor, transparency))
table.insert(newPicture, picture[i + 2])
table.insert(newPicture, picture[i + 3])
end
return newPicture
end
function image.blur(picture, radius, strength)
local blurMatrix = {}
local xValue, yValue, step = 1, 1, 1 / radius
for y = 0, radius do
for x = 0, radius do
blurMatrix[y], blurMatrix[-y] = blurMatrix[y] or {}, blurMatrix[-y] or {}
blurMatrix[y][x] = (xValue + yValue) / 2 * strength
blurMatrix[y][-x], blurMatrix[-y][x], blurMatrix[-y][-x] = blurMatrix[y][x], blurMatrix[y][x], blurMatrix[y][x]
xValue = xValue - step
end
xValue, yValue = 1, yValue - step
end
local newPicture, xImage, yImage = image.copy(picture)
for y = 1, image.getHeight(picture) do
for x = 1, image.getWidth(picture) do
local backgroundOld, foregroundOld, alpha, symbol = image.get(picture, x, y)
for yMatrix = -radius, radius do
for xMatrix = -radius, radius do
xImage, yImage = x + xMatrix, y + yMatrix
if xImage >= 1 and xImage <= image.getWidth(picture) and yImage >= 1 and yImage <= image.getHeight(picture) then
local backgroundNew, foregroundNew = image.get(newPicture, xImage, yImage)
image.set(newPicture, xImage, yImage,
color.blend(backgroundOld, backgroundNew, blurMatrix[yMatrix][xMatrix]),
color.blend(foregroundOld, foregroundNew, blurMatrix[yMatrix][xMatrix]),
alpha,
symbol
)
end
end
end
end
if y % 2 == 0 then
os.sleep(0.05)
end
end
return newPicture
end
function image.rotate(picture, angle)
local radAngle = math.rad(angle)
local sin, cos = math.sin(radAngle), math.cos(radAngle)
local pixMap = {}
local xCenter, yCenter = picture[1] / 2, picture[2] / 2
local xMin, xMax, yMin, yMax = math.huge, -math.huge, math.huge, -math.huge
for y = 1, picture[2] do
for x = 1, picture[1] do
local xNew = math.round(xCenter + (x - xCenter) * cos - (y - yCenter) * sin)
local yNew = math.round(yCenter + (y - yCenter) * cos + (x - xCenter) * sin)
xMin, xMax, yMin, yMax = math.min(xMin, xNew), math.max(xMax, xNew), math.min(yMin, yNew), math.max(yMax, yNew)
pixMap[yNew] = pixMap[yNew] or {}
pixMap[yNew][xNew] = {image.get(picture, x, y)}
end
end
local newPicture = image.create(xMax - xMin + 1, yMax - yMin + 1, 0xFF0000, 0x0, 0x0, "#")
for y in pairs(pixMap) do
for x in pairs(pixMap[y]) do
image.set(newPicture, x - xMin + 1, y - yMin + 1, pixMap[y][x][1], pixMap[y][x][2], pixMap[y][x][3], pixMap[y][x][4])
end
end
return newPicture
end
------------------------------------------------------------------------------------------------------------------------
image.loadFormatModule("/lib/ImageFormatModules/OCIF.lua", ".pic")
-- image.loadFormatModule("/lib/ImageFormatModules/RAW.lua", ".rawpic")
------------------------------------------------------------------------------------------------------------------------
-- local picture = image.load("/MineOS/Pictures/Block.pic")
-- gpu.setBackground(0x2D2D2D)
-- gpu.fill(1, 1, 160, 50, " ")
-- image.draw(2, 2, image.rotate(picture, 180))
------------------------------------------------------------------------------------------------------------------------
return image