Newer
Older
Kristify / src / Basalt / objects / Image.lua
@Sammy Sammy on 4 Sep 2022 7 KB Removed submodule mark
local Object = require("Object")
local xmlValue = require("utils").getValueFromXML

return function(name)
    -- Image
    local base = Object(name)
    local objectType = "Image"
    base:setZIndex(2)
    local image
    local shrinkedImage
    local imageGotShrinked = false

    local function shrink()
        -- shrinkSystem is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/
        local relations = { [0] = { 8, 4, 3, 6, 5 }, { 4, 14, 8, 7 }, { 6, 10, 8, 7 }, { 9, 11, 8, 0 }, { 1, 14, 8, 0 }, { 13, 12, 8, 0 }, { 2, 10, 8, 0 }, { 15, 8, 10, 11, 12, 14 },
                            { 0, 7, 1, 9, 2, 13 }, { 3, 11, 8, 7 }, { 2, 6, 7, 15 }, { 9, 3, 7, 15 }, { 13, 5, 7, 15 }, { 5, 12, 8, 7 }, { 1, 4, 7, 15 }, { 7, 10, 11, 12, 14 } }

        local colourNum, exponents, colourChar = {}, {}, {}
        for i = 0, 15 do
            exponents[2 ^ i] = i
        end
        do
            local hex = "0123456789abcdef"
            for i = 1, 16 do
                colourNum[hex:sub(i, i)] = i - 1
                colourNum[i - 1] = hex:sub(i, i)
                colourChar[hex:sub(i, i)] = 2 ^ (i - 1)
                colourChar[2 ^ (i - 1)] = hex:sub(i, i)

                local thisRel = relations[i - 1]
                for i = 1, #thisRel do
                    thisRel[i] = 2 ^ thisRel[i]
                end
            end
        end

        local function getBestColourMatch(usage)
            local lastCol = relations[exponents[usage[#usage][1]]]

            for j = 1, #lastCol do
                local thisRelation = lastCol[j]
                for i = 1, #usage - 1 do
                    if usage[i][1] == thisRelation then
                        return i
                    end
                end
            end

            return 1
        end

        local function colsToChar(pattern, totals)
            if not totals then
                local newPattern = {}
                totals = {}
                for i = 1, 6 do
                    local thisVal = pattern[i]
                    local thisTot = totals[thisVal]
                    totals[thisVal], newPattern[i] = thisTot and (thisTot + 1) or 1, thisVal
                end
                pattern = newPattern
            end

            local usage = {}
            for key, value in pairs(totals) do
                usage[#usage + 1] = { key, value }
            end

            if #usage > 1 then
                -- Reduce the chunk to two colours:
                while #usage > 2 do
                    table.sort(usage, function(a, b)
                        return a[2] > b[2]
                    end)
                    local matchToInd, usageLen = getBestColourMatch(usage), #usage
                    local matchFrom, matchTo = usage[usageLen][1], usage[matchToInd][1]
                    for i = 1, 6 do
                        if pattern[i] == matchFrom then
                            pattern[i] = matchTo
                            usage[matchToInd][2] = usage[matchToInd][2] + 1
                        end
                    end
                    usage[usageLen] = nil
                end

                -- Convert to character. Adapted from oli414's function:
                -- http://www.computercraft.info/forums2/index.php?/topic/25340-cc-176-easy-drawing-characters/
                local data = 128
                for i = 1, #pattern - 1 do
                    if pattern[i] ~= pattern[6] then
                        data = data + 2 ^ (i - 1)
                    end
                end
                return string.char(data), colourChar[usage[1][1] == pattern[6] and usage[2][1] or usage[1][1]], colourChar[pattern[6]]
            else
                -- Solid colour character:
                return "\128", colourChar[pattern[1]], colourChar[pattern[1]]
            end
        end

        local results, width, height, bgCol = { {}, {}, {} }, 0, #image + #image % 3, base.bgColor or colors.black
        for i = 1, #image do
            if #image[i] > width then
                width = #image[i]
            end
        end

        for y = 0, height - 1, 3 do
            local cRow, tRow, bRow, counter = {}, {}, {}, 1

            for x = 0, width - 1, 2 do
                -- Grab a 2x3 chunk:
                local pattern, totals = {}, {}

                for yy = 1, 3 do
                    for xx = 1, 2 do
                        pattern[#pattern + 1] = (image[y + yy] and image[y + yy][x + xx]) and (image[y + yy][x + xx] == 0 and bgCol or image[y + yy][x + xx]) or bgCol
                        totals[pattern[#pattern]] = totals[pattern[#pattern]] and (totals[pattern[#pattern]] + 1) or 1
                    end
                end

                cRow[counter], tRow[counter], bRow[counter] = colsToChar(pattern, totals)
                counter = counter + 1
            end

            results[1][#results[1] + 1], results[2][#results[2] + 1], results[3][#results[3] + 1] = table.concat(cRow), table.concat(tRow), table.concat(bRow)
        end

        results.width, results.height = #results[1][1], #results[1]

        shrinkedImage = results
    end

    local object = {
        init = function(self)
            self.bgColor = self.parent:getTheme("ImageBG")
        end,
        getType = function(self)
            return objectType
        end;

        loadImage = function(self, path)
            image = paintutils.loadImage(path)
            imageGotShrinked = false
            self:updateDraw()
            return self
        end;


        shrink = function(self)
            shrink()
            imageGotShrinked = true
            self:updateDraw()
            return self
        end;

        setValuesByXMLData = function(self, data)
            base.setValuesByXMLData(self, data)
            if(xmlValue("shrink", data)~=nil)then if(xmlValue("shrink", data))then self:shrink() end end
            if(xmlValue("path", data)~=nil)then self:loadImage(xmlValue("path", data)) end
            return self
        end,

        draw = function(self)
            if (base.draw(self)) then
                if (self.parent ~= nil) then
                    if (image ~= nil) then
                        local obx, oby = self:getAnchorPosition()
                        local w,h = self:getSize()
                        if (imageGotShrinked) then
                            -- this is copy pasted (and slightly changed) from blittle by Bomb Bloke: http://www.computercraft.info/forums2/index.php?/topic/25354-cc-176-blittle-api/
                            local t, tC, bC = shrinkedImage[1], shrinkedImage[2], shrinkedImage[3]
                            for i = 1, shrinkedImage.height do
                                local tI = t[i]
                                if type(tI) == "string" then
                                    self.parent:setText(obx, oby + i - 1, tI)
                                    self.parent:setFG(obx, oby + i - 1, tC[i])
                                    self.parent:setBG(obx, oby + i - 1, bC[i])
                                elseif type(tI) == "table" then
                                    self.parent:setText(obx, oby + i - 1, tI[2])
                                    self.parent:setFG(obx, oby + i - 1, tC[i])
                                    self.parent:setBG(obx, oby + i - 1, bC[i])
                                end
                            end
                        else
                            for yPos = 1, math.min(#image, h) do
                                local line = image[yPos]
                                for xPos = 1, math.min(#line, w) do
                                    if line[xPos] > 0 then
                                        self.parent:drawBackgroundBox(obx + xPos - 1, oby + yPos - 1, 1, 1, line[xPos])
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end,
    }

    return setmetatable(object, base)
end