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