This module implements the template {{User:Isaacl/Colour convert}}
.
OverviewEdit
Module name: 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('#%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