-------------------------------------------------- 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 == 1) 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 > 0 then _, _, gpuGetBackground = gpu.get(imageX, imageY) if alpha == 1 then currentBackground = gpuGetBackground gpu.setBackground(currentBackground) else currentBackground = color.blend(gpuGetBackground, background, alpha) 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, extension) local success, result = loadfile(path) if success then success, result = pcall(success, image) if success then image.formatModules[extension] = result else error("Failed to execute image format module: " .. tostring(result)) end else error("Failed to load image format module: " .. tostring(result)) end end local function loadOrSave(methodName, path, ...) local extension = fs.extension(path) if image.formatModules[extension] then local success, result = pcall(image.formatModules[extension][methodName], path, ...) if success then return result else return false, "Failed to " .. methodName .. " image file: " .. tostring(result) end else return false, "Failed to " .. methodName .. " image file: format module for extension \"" .. tostring(extension) .. "\" is not loaded" end end function image.save(path, picture, encodingMethod) return loadOrSave("save", path, image.optimize(picture), encodingMethod) end function image.load(path) return loadOrSave("load", path) 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 return false, "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/FormatModules/OCIF.lua", ".pic") ------------------------------------------------------------------------------------------------------------------------ -- 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