Module:Sandbox/isaacl/ColourSpace

local me = { }

local bc = require('Module:BaseConvert')
local Tuple = require('Module:Sandbox/isaacl/ColourSpace/Tuple')
local Formats = require('Module:Sandbox/isaacl/ColourSpace/Formats')

local infoFor = {
    sRGB = {
        colourSpace = 'sRGB',
        formatType = 'float',
        defaultConversion = 'sRGB24bit',
        parseInput = function(args)
            local digitPattern = '^([%.%d]+)%%?$'
            local red = string.match(args[1], digitPattern)
            local green = string.match(args[2], digitPattern)
            local blue = string.match(args[3], digitPattern)
            return { red, green, blue }
        end,
        isInputFormat = function(args)
            local sRGBPattern = '^[%.%d]+%%$'
            if ( args[3] ~= nil and
                string.match(args[1], sRGBPattern) and
                string.match(args[2], sRGBPattern) and
                string.match(args[3], sRGBPattern) ) then
                return true
            end
            return false
        end,  -- end of isInputFormat function
        display = function(self, separator)
            local red   = self[1] .. '%'
            local green = self[2] .. '%'
            local blue  = self [3] .. '%'
            return Tuple.display({ red, green, blue }, separator)
        end,
        mapParametersFrom = {
            sRGB24bit =
                function( colourValue )
                    local red = colourValue[1] / 255 * 100
                    local green = colourValue[2] / 255 * 100
                    local blue = colourValue[3] / 255 * 100
                    return { red, green, blue }
                end,
        },  -- end of mapping functions

    },  -- info for sRGB

    sRGB24bit = {
        colourSpace = 'sRGB',
        formatType = '24bit',
        defaultConversion = 'sRGB',
        isInputFormat = function(args)
            local digitPattern = '^%d+$'
            if ( args[3] ~= nil and
                 string.match(args[1], digitPattern) and
                 string.match(args[2], digitPattern) and
                 string.match(args[3], digitPattern)
                 -- for some reason, tonumber() is required for range checking to work
                 and (tonumber(args[1]) <= 255)
                 and (tonumber(args[2]) <= 255)
                 and (tonumber(args[3]) <= 255)
            ) then
                 return true
            end
            return false
        end,
        display = function(self, separator)
            return Tuple.display(self, separator)
        end,
        mapParametersFrom = {
            sRGB = function(colourValue)
                local red   = math.floor(colourValue[1] * 255 / 100 + 0.5)
                local green = math.floor(colourValue[2] * 255 / 100 + 0.5)
                local blue  = math.floor(colourValue[3] * 255 / 100 + 0.5)
                return { red, green, blue }
            end,
            sRGB24bitHexString = function(colourValue)
                return colourValue
            end,
        },  -- end of mapping functions

    },  -- info for sRGB24bit

    sRGB24bitHexString = {
        colourSpace = 'sRGB',
        formatType = '24bit',
        defaultConversion = 'sRGB24bit',
        parseInput = function(args)
            local red
            local green
            local blue
            local hexString = args[1]
            local hexCharPattern = '^#?(%x%x)(%x%x)(%x%x)$'
            local fDoubleChar = false
            if ( #hexString == 3 or #hexString == 4 ) then
                hexCharPattern = '^#?(%x)(%x)(%x)$'
                fDoubleChar = true
            end
            red, green, blue = string.match(hexString, hexCharPattern)
            if ( fDoubleChar ) then
                red = red .. red;
                green = green .. green;
                blue = blue .. blue;
            end
    
            red = bc.convert({n = red, base = 10, from = 16})
            green = bc.convert({n = green, base = 10, from = 16})
            blue = bc.convert({n = blue, base = 10, from = 16})
            return { red, green, blue }
        end,
        isInputFormat = function(args)
            if (   string.match(args[1], '^#%x%x%x$')
                or string.match(args[1], '^#%x%x%x%x%x%x$' ) ) then
                return true
            end
            return false
        end,
        display = function(self, separator)
            local red   = string.format('&#35;%02X', self[1])
            local green = string.format('%02X', self[2])
            local blue  = string.format('%02X', self[3])
            return Tuple.display({ red, green, blue }, '')
        end,
        mapParametersFrom = {
            sRGB24bit = function( colourValue )
                return colourValue
            end,
        },  -- end of mapping functions

    },  -- info for sRGB24bitHexString

}  -- data for formats

function me.buildColourTuple(args, parameters)
    local result = Tuple.clone(args)
    result.format = parameters.format
    result.colourSpace = parameters.colourSpace
    result.defaultConversion = parameters.defaultConversion
    result.fValid = true
    result.display = function(self, separator)
        return parameters.displayFunc(self, separator)
    end
    return result
end -- function buildColourTuple

local options = {
    separator = ', ',
    displayPrefix = '',
    displaySuffix = '',
}

local formatTypeFor = { }

local checkInputFormatFor = { }

me.create = { }

local createFromParsedInput = { }

me.mapTo = { }

local colourSpaceFor = { }

local commonFormatForColourSpace = {
    sRGB = {
        andFormatType = {
            float = 'sRGB',
            ['24bit'] = 'sRGB24bit',
        },
    },
}

local function createInvalidColourValue(errorMsg)
    local invalidColourValue = {
        -1, -1, -1,
        fValid = false,
        errorMessage = errorMsg,
        display = function(self, separator)
            return 'InvalidValue ' .. self.errorMessage
        end,
    }
    return invalidColourValue
end

me.configureFormatInfo = function(infoFor)
    for format, info in pairs(infoFor) do
        -- If basic information for the format has not been defined
        -- already, configure it
        if ( me.create[format] == nil ) then
            createFromParsedInput[format] = function(parsedArgs)
                return me.buildColourTuple(parsedArgs, {
                      format = format,
                      colourSpace = info.colourSpace,
                      defaultConversion = info.defaultConversion,
                      displayFunc = info.display,
                })
            end  -- function createFromParsedInput[format]
            me.create[format] = function (args)
                local parsedArgs
                if ( info.parseInput ~= nil ) then
                    parsedArgs = info.parseInput(args)
                else
                    parsedArgs = args 
                end
                if ( parsedArgs == nil ) then
                    return createInvalidColourValue('badInputValues')
                end

                return createFromParsedInput[format](parsedArgs)
            end  -- function me.create[format]

            formatTypeFor[format] = info.formatType
            colourSpaceFor[format] = info.colourSpace

            if ( info.isInputFormat ~= nil ) then
                checkInputFormatFor[format] = info.isInputFormat
            end
        end  -- if me.create[format] == nil, configure basic info for format

        -- Define mapping functions from other formats to the
        -- current format being configured.
        for startFormat, mapper in pairs(info.mapParametersFrom) do
            if ( me.mapTo[format] == nil ) then
                me.mapTo[format] = { from = { } }
            end
            me.mapTo[format].from[startFormat] =
                function(parameters)
                    local copy = Tuple.clone(parameters)
                    local mappedParameters = mapper(copy)

                    if ( mappedParameters == nil ) then
                        return createInvalidColourValue('conversionError '
                            .. parameters:display()
                            )
                    end
                    
                    return createFromParsedInput[format]( mappedParameters )
                end
        end  -- loop over info.mapParametersFrom
    end  -- loop over infoFor table
end

me.configureFormatInfo(infoFor)

for idx=1, #Formats do
    local formatInfo = require('Module:Sandbox/isaacl/ColourSpace/Formats/' .. Formats[idx])
    me.configureFormatInfo(formatInfo.infoFor)
end

function me.loadFormatInfo(format)
    -- try to load the required module for the format
    local formatInfo = require('Module:Sandbox/isaacl/ColourSpace/Formats/'
        .. format)
    if ( formatInfo ~= nil ) then
        me.configureFormatInfo(formatInfo.infoFor)
        return format
    end
    return nil
end

function me.determineInputFormat(frame)
    local args = frame.args
    local fromFormat = frame.args["from"]
    if (fromFormat ~= nil) then
        if ( me.create[fromFormat] ~= nil ) then
            return fromFormat
        else
            -- try to load the required module for the format
            return me.loadFormatInfo(fromFormat)
        end
    end

    for format, isInputFormat in pairs(checkInputFormatFor) do
        if ( isInputFormat(args) ) then
            return format
        end
    end

    -- unable to deduce format
    return nil
end  -- function determineInputFormat()

local function determineOutputFormat(frame, startValue)
    local toFormat = frame.args["to"]
    if (toFormat ~= nil) then
        if ( me.create[toFormat] ~= nil ) then
            return toFormat
        else
            -- try to load the required module for the format
            return me.loadFormatInfo(toFormat)
        end
    end
    -- use default conversion
    return startValue.defaultConversion
end  -- function determineOutputFormat()

local function convertBetweenFormats(colourValue, listOfFormats)
    local convertedValue = colourValue
    for idx, nextFormat in ipairs(listOfFormats) do
        if (convertedValue.format ~= nextFormat) then
            if ( me.mapTo[nextFormat].from[convertedValue.format] == nil ) then
                return createInvalidColourValue('noConversionAvailable from '
                    .. convertedValue.format .. ' to ' .. nextFormat)
            end
            convertedValue = me.mapTo[nextFormat].from[convertedValue.format](convertedValue)
            if (not convertedValue.fValid) then
                -- error in conversion; return immediately with the invalidValue
                return convertedValue
            end
        end
    end  -- loop over list of formats to convert between
    return convertedValue
end  -- function convertBetweenFormats

function me.convertColour(frame)
    if ( frame.args[1] == nil ) then
        return ''
    end

    if ( frame.args.separator ~= nil ) then
        options.separator = frame.args.separator
    end

    local startFormat = me.determineInputFormat(frame)
    if ( startFormat == nil ) then
        return 'badInputFormat'
    end

    local startValue = me.create[startFormat](frame.args)

    if ( not startValue.fValid ) then
        return startValue:display()
    end

    local endFormat = determineOutputFormat(frame, startValue)

    if ( endFormat == nil ) then
        return 'badOutputFormat'
    end

    if ( startFormat == endFormat ) then
        return startValue:display(options.separator)
    end

    local result = { }

    -- If a direct conversion exists, use it
    if (me.mapTo[endFormat].from[startFormat] ~= nil) then
        result = me.mapTo[endFormat].from[startFormat](startValue)
        return result:display(options.separator)
    end

    local listOfFormats = { }

    -- If the start and end formats are in the same colour space:
    -- first, convert to the common format for the starting colour space and format type
    -- second, convert to the common format for the ending colour space and format type
    -- third, convert to the ending format type

    if (colourSpaceFor[startFormat] == colourSpaceFor[endFormat]) then
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[startFormat]].andFormatType[formatTypeFor[startFormat]] )
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[endFormat]].andFormatType[formatTypeFor[endFormat]] )
        table.insert(listOfFormats, endFormat)
    
        result = convertBetweenFormats(startValue, listOfFormats)
    else
        -- if the start and end formats are in different colour spaces:
        -- first, convert to the common format for the starting colour space and format type
        -- second, convert to the common floating point format for the starting colour space
        -- third, convert to the common floating point format for the ending colour space
        -- fourth, convert to the common format for the ending colour space and format type
        -- fifth, convert to the ending format type
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[startFormat]].andFormatType[formatTypeFor[startFormat]] )
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[startFormat]].andFormatType.float )
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[endFormat]].andFormatType.float )
        table.insert(listOfFormats,
            commonFormatForColourSpace[colourSpaceFor[endFormat]].andFormatType[formatTypeFor[endFormat]] )
        table.insert(listOfFormats, endFormat)
    
        result = convertBetweenFormats(startValue, listOfFormats)
    end

    return result:display(options.separator)
end  -- function convertColour()

function me.convertColour_fromTemplate(frame)
    return me.convertColour(frame:getParent())
end  -- function templateConvertColour()

return me

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.