local component = require("component") local unicode = require("unicode") local color = require("color") local image = require("image") -------------------------------------------------------------------------------------------------------------- local buffer = { GPUProxy = component.getPrimary("gpu"), currentFrame = {}, newFrame = {}, drawLimit = {}, } -------------------------------------------------------------------------------------------------------------- function buffer.getCoordinatesByIndex(index) local integer, fractional = math.modf(index / (buffer.tripleWidth)) return math.ceil(fractional * buffer.width), integer + 1 end function buffer.getIndexByCoordinates(x, y) return buffer.tripleWidth * (y - 1) + x * 3 - 2 end -------------------------------------------------------------------------------------------------------------- function buffer.setDrawLimit(x1, y1, x2, y2) buffer.drawLimit.x1, buffer.drawLimit.y1, buffer.drawLimit.x2, buffer.drawLimit.y2 = x1, y1, x2, y2 end function buffer.resetDrawLimit() buffer.drawLimit.x1, buffer.drawLimit.y1, buffer.drawLimit.x2, buffer.drawLimit.y2 = 1, 1, buffer.width, buffer.height end function buffer.getDrawLimit() return buffer.drawLimit.x1, buffer.drawLimit.y1, buffer.drawLimit.x2, buffer.drawLimit.y2 end -------------------------------------------------------------------------------------------------------------- function buffer.flush(width, height) if not width or not height then width, height = buffer.GPUProxy.getResolution() end buffer.currentFrame, buffer.newFrame = {}, {} buffer.width = width buffer.height = height buffer.tripleWidth = width * 3 buffer.resetDrawLimit() for y = 1, buffer.height do for x = 1, buffer.width do table.insert(buffer.currentFrame, 0x010101) table.insert(buffer.currentFrame, 0xFEFEFE) table.insert(buffer.currentFrame, " ") table.insert(buffer.newFrame, 0x010101) table.insert(buffer.newFrame, 0xFEFEFE) table.insert(buffer.newFrame, " ") end end end function buffer.setResolution(width, height) buffer.GPUProxy.setResolution(width, height) buffer.flush(width, height) end function buffer.bindScreen(...) buffer.GPUProxy.bind(...) buffer.flush(buffer.GPUProxy.getResolution()) end function buffer.bindGPU(address) buffer.GPUProxy = component.proxy(address) buffer.flush(buffer.GPUProxy.getResolution()) end -------------------------------------------------------------------------------------------------------------- function buffer.rawSet(index, background, foreground, symbol) buffer.newFrame[index], buffer.newFrame[index + 1], buffer.newFrame[index + 2] = background, foreground, symbol end function buffer.rawGet(index) return buffer.newFrame[index], buffer.newFrame[index + 1], buffer.newFrame[index + 2] end function buffer.get(x, y) local index = buffer.getIndexByCoordinates(x, y) if x >= 1 and y >= 1 and x <= buffer.width and y <= buffer.height then return buffer.rawGet(index) else return 0x000000, 0x000000, " " end end function buffer.set(x, y, background, foreground, symbol) local index = buffer.getIndexByCoordinates(x, y) if x >= buffer.drawLimit.x1 and y >= buffer.drawLimit.y1 and x <= buffer.drawLimit.x2 and y <= buffer.drawLimit.y2 then buffer.rawSet(index, background, foreground or 0x0, symbol or " ") end end function buffer.square(x, y, width, height, background, foreground, symbol, transparency) local index, indexStepOnEveryLine, indexPlus1 = buffer.getIndexByCoordinates(x, y), (buffer.width - width) * 3 for j = y, y + height - 1 do if j >= buffer.drawLimit.y1 and j <= buffer.drawLimit.y2 then for i = x, x + width - 1 do if i >= buffer.drawLimit.x1 and i <= buffer.drawLimit.x2 then indexPlus1 = index + 1 if transparency then buffer.newFrame[index], buffer.newFrame[indexPlus1] = color.blend(buffer.newFrame[index], background, transparency), color.blend(buffer.newFrame[indexPlus1], background, transparency) else buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[index + 2] = background, foreground, symbol end end index = index + 3 end index = index + indexStepOnEveryLine else index = index + buffer.tripleWidth end end end function buffer.clear(color, transparency) buffer.square(1, 1, buffer.width, buffer.height, color or 0x0, 0x000000, " ", transparency) end function buffer.copy(x, y, width, height) local copyArray = { width = width, height = height } local index for j = y, y + height - 1 do for i = x, x + width - 1 do if i >= 1 and j >= 1 and i <= buffer.width and j <= buffer.height then index = buffer.getIndexByCoordinates(i, j) table.insert(copyArray, buffer.newFrame[index]) table.insert(copyArray, buffer.newFrame[index + 1]) table.insert(copyArray, buffer.newFrame[index + 2]) else table.insert(copyArray, 0x0) table.insert(copyArray, 0x0) table.insert(copyArray, " ") end end end return copyArray end function buffer.paste(x, y, copyArray) local index, arrayIndex if not copyArray or #copyArray == 0 then error("Массив области экрана пуст.") end for j = y, y + copyArray.height - 1 do for i = x, x + copyArray.width - 1 do if i >= buffer.drawLimit.x1 and j >= buffer.drawLimit.y1 and i <= buffer.drawLimit.x2 and j <= buffer.drawLimit.y2 then --Рассчитываем индекс массива основного изображения index = buffer.getIndexByCoordinates(i, j) --Копипаст формулы, аккуратнее! --Рассчитываем индекс массива вставочного изображения arrayIndex = (copyArray.width * (j - y) + (i - x + 1)) * 3 - 2 --Вставляем данные buffer.newFrame[index] = copyArray[arrayIndex] buffer.newFrame[index + 1] = copyArray[arrayIndex + 1] buffer.newFrame[index + 2] = copyArray[arrayIndex + 2] end end end end function buffer.rasterizeLine(x1, y1, x2, y2, method) local inLoopValueFrom, inLoopValueTo, outLoopValueFrom, outLoopValueTo, isReversed, inLoopValueDelta, outLoopValueDelta = x1, x2, y1, y2, false, math.abs(x2 - x1), math.abs(y2 - y1) if inLoopValueDelta < outLoopValueDelta then inLoopValueFrom, inLoopValueTo, outLoopValueFrom, outLoopValueTo, isReversed, inLoopValueDelta, outLoopValueDelta = y1, y2, x1, x2, true, outLoopValueDelta, inLoopValueDelta end if outLoopValueFrom > outLoopValueTo then outLoopValueFrom, outLoopValueTo = outLoopValueTo, outLoopValueFrom inLoopValueFrom, inLoopValueTo = inLoopValueTo, inLoopValueFrom end local outLoopValue, outLoopValueCounter, outLoopValueTriggerIncrement = outLoopValueFrom, 1, inLoopValueDelta / outLoopValueDelta local outLoopValueTrigger = outLoopValueTriggerIncrement for inLoopValue = inLoopValueFrom, inLoopValueTo, inLoopValueFrom < inLoopValueTo and 1 or -1 do if isReversed then method(outLoopValue, inLoopValue) else method(inLoopValue, outLoopValue) end outLoopValueCounter = outLoopValueCounter + 1 if outLoopValueCounter > outLoopValueTrigger then outLoopValue, outLoopValueTrigger = outLoopValue + 1, outLoopValueTrigger + outLoopValueTriggerIncrement end end end function buffer.line(x1, y1, x2, y2, background, foreground, alpha, symbol) buffer.rasterizeLine(x1, y1, x2, y2, function(x, y) buffer.set(x, y, background, foreground, alpha, symbol) end) end function buffer.text(x, y, textColor, text, transparency) if y >= buffer.drawLimit.y1 and y <= buffer.drawLimit.y2 then local charIndex, bufferIndex = 1, buffer.getIndexByCoordinates(x, y) + 1 for charIndex = 1, unicode.len(text) do if x >= buffer.drawLimit.x1 and x <= buffer.drawLimit.x2 then if transparency then buffer.newFrame[bufferIndex] = color.blend(buffer.newFrame[bufferIndex - 1], textColor, transparency) else buffer.newFrame[bufferIndex] = textColor end buffer.newFrame[bufferIndex + 1] = unicode.sub(text, charIndex, charIndex) end x, bufferIndex = x + 1, bufferIndex + 3 end end end function buffer.formattedText(x, y, text) if y >= buffer.drawLimit.y1 and y <= buffer.drawLimit.y2 then local charIndex, bufferIndex, textColor, char, number = 1, buffer.getIndexByCoordinates(x, y) + 1, 0xFFFFFF while charIndex <= unicode.len(text) do if x >= buffer.drawLimit.x1 and x <= buffer.drawLimit.x2 then char = unicode.sub(text, charIndex, charIndex) if char == "#" then local number = tonumber("0x" .. unicode.sub(text, charIndex + 1, charIndex + 6)) if number then textColor, charIndex = number, charIndex + 7 else buffer.newFrame[bufferIndex], buffer.newFrame[bufferIndex + 1], x, charIndex, bufferIndex = textColor, char, x + 1, charIndex + 1, bufferIndex + 3 end else buffer.newFrame[bufferIndex], buffer.newFrame[bufferIndex + 1], x, charIndex, bufferIndex = textColor, char, x + 1, charIndex + 1, bufferIndex + 3 end else x, charIndex, bufferIndex = x + 1, charIndex + 1, bufferIndex + 3 end end end end function buffer.image(x, y, picture, blendForeground) local xPos, xEnd, bufferIndexStepOnReachOfImageWidth = x, x + picture[1] - 1, (buffer.width - picture[1]) * 3 local bufferIndex, bufferIndexPlus1, imageIndexPlus1, imageIndexPlus2, imageIndexPlus3 = buffer.getIndexByCoordinates(x, y) for imageIndex = 3, #picture, 4 do if xPos >= buffer.drawLimit.x1 and y >= buffer.drawLimit.y1 and xPos <= buffer.drawLimit.x2 and y <= buffer.drawLimit.y2 then bufferIndexPlus1, imageIndexPlus1, imageIndexPlus2, imageIndexPlus3 = bufferIndex + 1, imageIndex + 1, imageIndex + 2, imageIndex + 3 if picture[imageIndexPlus2] == 0 then buffer.newFrame[bufferIndex], buffer.newFrame[bufferIndexPlus1] = picture[imageIndex], picture[imageIndexPlus1] elseif picture[imageIndexPlus2] > 0 and picture[imageIndexPlus2] < 1 then buffer.newFrame[bufferIndex] = color.blend(buffer.newFrame[bufferIndex], picture[imageIndex], picture[imageIndexPlus2]) if blendForeground then buffer.newFrame[bufferIndexPlus1] = color.blend(buffer.newFrame[bufferIndexPlus1], picture[imageIndexPlus1], picture[imageIndexPlus2]) else buffer.newFrame[bufferIndexPlus1] = picture[imageIndexPlus1] end elseif picture[imageIndexPlus2] == 1 and picture[imageIndexPlus3] ~= " " then buffer.newFrame[bufferIndexPlus1] = picture[imageIndexPlus1] end buffer.newFrame[bufferIndex + 2] = picture[imageIndexPlus3] end xPos, bufferIndex = xPos + 1, bufferIndex + 3 if xPos > xEnd then xPos, y, bufferIndex = x, y + 1, bufferIndex + bufferIndexStepOnReachOfImageWidth end end end function buffer.frame(x, y, width, height, color) local stringUp, stringDown, x2 = "┌" .. string.rep("─", width - 2) .. "┐", "└" .. string.rep("─", width - 2) .. "┘", x + width - 1 buffer.text(x, y, color, stringUp); y = y + 1 for i = 1, height - 2 do buffer.text(x, y, color, "│") buffer.text(x2, y, color, "│") y = y + 1 end buffer.text(x, y, color, stringDown) end -------------------------------------------------------------------------------------------------------------- function buffer.semiPixelRawSet(index, color, yPercentTwoEqualsZero) local upperPixel, lowerPixel, bothPixel, indexPlus1, indexPlus2 = "▀", "▄", " ", index + 1, index + 2 local background, foreground, symbol = buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] if yPercentTwoEqualsZero then if symbol == upperPixel then if color == foreground then buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = color, foreground, bothPixel else buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = color, foreground, symbol end elseif symbol == bothPixel then if color ~= background then buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = background, color, lowerPixel end else buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = background, color, lowerPixel end else if symbol == lowerPixel then if color == foreground then buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = color, foreground, bothPixel else buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = color, foreground, symbol end elseif symbol == bothPixel then if color ~= background then buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = background, color, upperPixel end else buffer.newFrame[index], buffer.newFrame[indexPlus1], buffer.newFrame[indexPlus2] = background, color, upperPixel end end end function buffer.semiPixelSet(x, y, color) local yFixed = math.ceil(y / 2) if x >= buffer.drawLimit.x1 and yFixed >= buffer.drawLimit.y1 and x <= buffer.drawLimit.x2 and yFixed <= buffer.drawLimit.y2 then buffer.semiPixelRawSet(buffer.getIndexByCoordinates(x, yFixed), color, y % 2 == 0) end end function buffer.semiPixelSquare(x, y, width, height, color) -- for j = y, y + height - 1 do for i = x, x + width - 1 do buffer.semiPixelSet(i, j, color) end end local index, indexStepForward, indexStepBackward, jPercentTwoEqualsZero, jFixed = buffer.getIndexByCoordinates(x, math.ceil(y / 2)), (buffer.width - width) * 3, width * 3 for j = y, y + height - 1 do jPercentTwoEqualsZero = j % 2 == 0 for i = x, x + width - 1 do jFixed = math.ceil(j / 2) -- if x >= buffer.drawLimit.x1 and jFixed >= buffer.drawLimit.y1 and x <= buffer.drawLimit.x2 and jFixed <= buffer.drawLimit.y2 then buffer.semiPixelRawSet(index, color, jPercentTwoEqualsZero) -- end index = index + 3 end if jPercentTwoEqualsZero then index = index + indexStepForward else index = index - indexStepBackward end end end function buffer.semiPixelLine(x1, y1, x2, y2, color) buffer.rasterizeLine(x1, y1, x2, y2, function(x, y) buffer.semiPixelSet(x, y, color) end) end function buffer.semiPixelCircle(xCenter, yCenter, radius, color) local function insertPoints(x, y) buffer.semiPixelSet(xCenter + x, yCenter + y, color) buffer.semiPixelSet(xCenter + x, yCenter - y, color) buffer.semiPixelSet(xCenter - x, yCenter + y, color) buffer.semiPixelSet(xCenter - x, yCenter - y, color) end local x, y = 0, radius local delta = 3 - 2 * radius; while (x < y) do insertPoints(x, y); insertPoints(y, x); if (delta < 0) then delta = delta + (4 * x + 6) else delta = delta + (4 * (x - y) + 10) y = y - 1 end x = x + 1 end if x == y then insertPoints(x, y) end end -------------------------------------------------------------------------------------------------------------- local function getPointTimedPosition(firstPoint, secondPoint, time) return { x = firstPoint.x + (secondPoint.x - firstPoint.x) * time, y = firstPoint.y + (secondPoint.y - firstPoint.y) * time } end local function getConnectionPoints(points, time) local connectionPoints = {} for point = 1, #points - 1 do table.insert(connectionPoints, getPointTimedPosition(points[point], points[point + 1], time)) end return connectionPoints end local function getMainPointPosition(points, time) if #points > 1 then return getMainPointPosition(getConnectionPoints(points, time), time) else return points[1] end end function buffer.semiPixelBezierCurve(points, color, precision) local linePoints = {} for time = 0, 1, precision or 0.01 do table.insert(linePoints, getMainPointPosition(points, time)) end for point = 1, #linePoints - 1 do buffer.semiPixelLine(math.floor(linePoints[point].x), math.floor(linePoints[point].y), math.floor(linePoints[point + 1].x), math.floor(linePoints[point + 1].y), color) end end -------------------------------------------------------------------------------------------------------------- function buffer.button(x, y, width, height, background, foreground, text) local textLength = unicode.len(text) if textLength > width - 2 then text = unicode.sub(text, 1, width - 2) end local textPosX = math.floor(x + width / 2 - textLength / 2) local textPosY = math.floor(y + height / 2) buffer.square(x, y, width, height, background, foreground, " ") buffer.text(textPosX, textPosY, foreground, text) return x, y, (x + width - 1), (y + height - 1) end function buffer.adaptiveButton(x, y, xOffset, yOffset, background, foreground, text) local width = xOffset * 2 + unicode.len(text) local height = yOffset * 2 + 1 buffer.square(x, y, width, height, background, 0xFFFFFF, " ") buffer.text(x + xOffset, y + yOffset, foreground, text) return x, y, (x + width - 1), (y + height - 1) end function buffer.framedButton(x, y, width, height, backColor, buttonColor, text) buffer.square(x, y, width, height, backColor, buttonColor, " ") buffer.frame(x, y, width, height, buttonColor) x = math.floor(x + width / 2 - unicode.len(text) / 2) y = math.floor(y + height / 2) buffer.text(x, y, buttonColor, text) end function buffer.scrollBar(x, y, width, height, countOfAllElements, currentElement, backColor, frontColor) local sizeOfScrollBar = math.ceil(height / countOfAllElements) local displayBarFrom = math.floor(y + height * ((currentElement - 1) / countOfAllElements)) buffer.square(x, y, width, height, backColor, 0xFFFFFF, " ") buffer.square(x, displayBarFrom, width, sizeOfScrollBar, frontColor, 0xFFFFFF, " ") sizeOfScrollBar, displayBarFrom = nil, nil end function buffer.horizontalScrollBar(x, y, width, countOfAllElements, currentElement, background, foreground) local pipeSize = math.ceil(width / countOfAllElements) local displayBarFrom = math.floor(x + width * ((currentElement - 1) / countOfAllElements)) buffer.text(x, y, background, string.rep("▄", width)) buffer.text(displayBarFrom, y, foreground, string.rep("▄", pipeSize)) end function buffer.customImage(x, y, pixels) x = x - 1 y = y - 1 for i=1, #pixels do for j=1, #pixels[1] do if pixels[i][j][3] ~= "#" then buffer.set(x + j, y + i, pixels[i][j][1], pixels[i][j][2], pixels[i][j][3]) end end end return (x + 1), (y + 1), (x + #pixels[1]), (y + #pixels) end -------------------------------------------------------------------------------------------------------------- local function info(...) local args = {...} local text = {} for i = 1, #args do table.insert(text, tostring(args[i])) end local b = buffer.GPUProxy.getBackground() local f = buffer.GPUProxy.getForeground() buffer.GPUProxy.setBackground(0x0) buffer.GPUProxy.setForeground(0xFFFFFF) buffer.GPUProxy.fill(1, buffer.height, buffer.width, 1, " ") buffer.GPUProxy.set(2, buffer.height, table.concat(text, ", ")) buffer.GPUProxy.setBackground(b) buffer.GPUProxy.setForeground(f) require("event").pull("touch") end local function calculateDifference(index, indexPlus1, indexPlus2) local somethingIsChanged = buffer.currentFrame[index] ~= buffer.newFrame[index] or buffer.currentFrame[indexPlus1] ~= buffer.newFrame[indexPlus1] or buffer.currentFrame[indexPlus2] ~= buffer.newFrame[indexPlus2] buffer.currentFrame[index] = buffer.newFrame[index] buffer.currentFrame[indexPlus1] = buffer.newFrame[indexPlus1] buffer.currentFrame[indexPlus2] = buffer.newFrame[indexPlus2] return somethingIsChanged end function buffer.draw(force) local changes, index, indexStepOnEveryLine, indexPlus1, indexPlus2, sameCharArray, x, xCharCheck, indexCharCheck, indexCharCheckPlus1, indexCharCheckPlus2, currentForeground = {}, buffer.getIndexByCoordinates(buffer.drawLimit.x1, buffer.drawLimit.y1), (buffer.width - buffer.drawLimit.x2 + buffer.drawLimit.x1 - 1) * 3 for y = buffer.drawLimit.y1, buffer.drawLimit.y2 do x = buffer.drawLimit.x1 while x <= buffer.drawLimit.x2 do indexPlus1, indexPlus2 = index + 1, index + 2 if calculateDifference(index, indexPlus1, indexPlus2) or force then sameCharArray = { buffer.currentFrame[indexPlus2] } xCharCheck, indexCharCheck = x + 1, index + 3 while xCharCheck <= buffer.drawLimit.x2 do indexCharCheckPlus1, indexCharCheckPlus2 = indexCharCheck + 1, indexCharCheck + 2 if buffer.currentFrame[index] == buffer.newFrame[indexCharCheck] and ( buffer.newFrame[indexCharCheckPlus2] == " " or buffer.currentFrame[indexPlus1] == buffer.newFrame[indexCharCheckPlus1] ) then calculateDifference(indexCharCheck, indexCharCheckPlus1, indexCharCheckPlus2) table.insert(sameCharArray, buffer.currentFrame[indexCharCheckPlus2]) else break end indexCharCheck, xCharCheck = indexCharCheck + 3, xCharCheck + 1 end changes[buffer.currentFrame[index]] = changes[buffer.currentFrame[index]] or {} changes[buffer.currentFrame[index]][buffer.currentFrame[indexPlus1]] = changes[buffer.currentFrame[index]][buffer.currentFrame[indexPlus1]] or {} table.insert(changes[buffer.currentFrame[index]][buffer.currentFrame[indexPlus1]], x) table.insert(changes[buffer.currentFrame[index]][buffer.currentFrame[indexPlus1]], y) table.insert(changes[buffer.currentFrame[index]][buffer.currentFrame[indexPlus1]], table.concat(sameCharArray)) x, index = x + #sameCharArray - 1, index + #sameCharArray * 3 - 3 end x, index = x + 1, index + 3 end index = index + indexStepOnEveryLine end for background in pairs(changes) do buffer.GPUProxy.setBackground(background) for foreground in pairs(changes[background]) do if currentForeground ~= foreground then buffer.GPUProxy.setForeground(foreground) currentForeground = foreground end for i = 1, #changes[background][foreground], 3 do buffer.GPUProxy.set(changes[background][foreground][i], changes[background][foreground][i + 1], changes[background][foreground][i + 2]) end end end end ------------------------------------------------------------------------------------------------------ buffer.start = buffer.flush buffer.rectangle = buffer.square buffer.flush() ------------------------------------------------------------------------------------------------------ -- buffer.formattedText(2, 2, "Hello world #FFDB40meow! #FF4940This is colored #44FF44text") -- buffer.draw(true) ------------------------------------------------------------------------------------------------------ return buffer