overhauled format handler functions to be more generic / easily

expandable. all coversions functions have been verified. file input
sanitation now more robust.
This commit is contained in:
2025-01-20 13:35:03 -05:00
parent 22838a24da
commit 0d0f27b944
+238 -225
View File
@@ -1,30 +1,24 @@
import math import math
import argparse import argparse
# Order of types must match order of arguments defined
# (or else arg validation will no longer work properly)
TYPES = ['hex', 'rgb', 'cmy', 'cmyk', 'hsl', 'hsv'] TYPES = ['hex', 'rgb', 'cmy', 'cmyk', 'hsl', 'hsv']
# look into LAB # look into LAB
HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f'] HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f']
OUTPUT = 'stdout' OUTPUT = 'stdout'
APPEND = False
VERBOSE = False VERBOSE = False
def main(): def main():
# Set up arguments ''' ESTABLISH ARGS '''
parser = argparse.ArgumentParser(prog='color-converter', parser = argparse.ArgumentParser(prog='color-converter', description='Color code converting utility written in Python.', epilog='Hope this helps :)')
description='Color code converting utility written in Python.',
epilog='Hope this helps :)')
parser.add_argument('-hex', action='store_true', help='convert/output to Hex') parser.add_argument('-hex', action='store_true', help='convert/output to Hex')
parser.add_argument('-rgb', action='store_true', help='convert/output to RGB') parser.add_argument('-rgb', action='store_true', help='convert/output to RGB')
parser.add_argument('-cmy', action='store_true', help='convert/output to CMY') parser.add_argument('-cmy', action='store_true', help='convert/output to CMY')
parser.add_argument('-cmyk', action='store_true', help='convert/output to CMYK') parser.add_argument('-cmyk', action='store_true', help='convert/output to CMYK (CMYK conversions are uncalibrated)')
parser.add_argument('-hsl', action='store_true', help='convert/output to HSL') parser.add_argument('-hsl', action='store_true', help='convert/output to HSL')
parser.add_argument('-hsv', action='store_true', help='convert/output to HSV') parser.add_argument('-hsv', action='store_true', help='convert/output to HSV')
@@ -36,19 +30,15 @@ def main():
parser.add_argument('-isHsv', action='store_true', help='indicate that inputted value(s) will be sets of HSL codes') parser.add_argument('-isHsv', action='store_true', help='indicate that inputted value(s) will be sets of HSL codes')
parser.add_argument('--input', '-i', help='name of the input file containing color codes to process') parser.add_argument('--input', '-i', help='name of the input file containing color codes to process')
parser.add_argument('--output', '-o', help='the name of the file to store output in (will create file if doesn\'t exist, will OVERWRITE existing file\'s contents)') parser.add_argument('--output', '-o', help='the name of a file to store output in (will create file if doesn\'t exist, will OVERWRITE existing file\'s contents)')
parser.add_argument('--append', '-a', action='store_true', help='append rather than overwrite file output') parser.add_argument('--append', '-a', action='store_true', help='append rather than overwrite when outputting to file rather than stdout')
parser.add_argument('--verbose', '-v', action='store_true', help='print when performing conversions') parser.add_argument('--verbose', '-v', action='store_true', help='print when performing conversions')
parser.add_argument('color', nargs='*', help='accepts a color in Hex, RGB, CMY, CMYK, or HSL and performs format conversions (does not support CMYK profiles, conversions are uncalibrated)') parser.add_argument('color', nargs='*', help='a color in Hex, RGB, CMY, CMYK, HSL or HSV format')
args = parser.parse_args() args = parser.parse_args()
# debug print ''' PROCESS ARGS '''
# print(vars(args))
''' ARGS PROCESSING '''
# determine which conversions to perform # determine which conversions to perform
outputFormats = [] outputFormats = []
flagsActive = 0 flagsActive = 0
@@ -62,13 +52,14 @@ def main():
for colorFormat in TYPES : for colorFormat in TYPES :
outputFormats.append(colorFormat) outputFormats.append(colorFormat)
# if output should be written to file, set global vars for reference later # determine if output should be (over?)written to file
if args.output : if args.output :
global OUTPUT global OUTPUT
OUTPUT = args.output OUTPUT = args.output
if args.append : if not args.append :
global APPEND output_file = open(OUTPUT, 'w')
APPEND = True output_file.write('')
output_file.close()
# print conversion updates? # print conversion updates?
if args.verbose : if args.verbose :
@@ -76,20 +67,20 @@ def main():
VERBOSE = True VERBOSE = True
''' PARSE INPUTTED COLORS ''' ''' PARSE INPUTTED COLORS '''
colorCodes = [] colorCodes = []
# if provided a file of values # if provided a file of values
if args.input : if args.input :
with open(args.input, 'r', encoding='utf-8') as file : with open(args.input, 'r', encoding='utf-8') as file :
for line in file : for line in file :
colorCodes.append(line.strip()) line = line.strip()
if line != '' :
colorCodes.append(line)
# else grab from stdin # else grab from stdin
else : else :
colorCodes = args.color colorCodes = args.color
''' PROCESS COLORS ''' ''' PROCESS COLORS '''
for color in colorCodes : for color in colorCodes :
# Try to automatically handle value # Try to automatically handle value
@@ -112,180 +103,169 @@ def main():
else : else :
print('ERROR: Could not detect inputted color format and no fallback flag was specified. see --help for more information on usage.') print('ERROR: Could not detect inputted color format and no fallback flag was specified. see --help for more information on usage.')
return return
return return
'''
##
# FORMAT HANDLERS # FORMAT HANDLERS
## '''
# ARGS # ARGS
# color: string containing hex code # color: string containing hex code
# outputFormats: list indicating which conversions to perform # outputFormats: list indicating which conversions to perform
def handleHex(color, outputFormats) : def handleHex(color, outputFormats) :
hexCode = validateHex(color) # validate/sanitize input
if hexCode is None : hexcode = validateHex(color)
if hexcode is None :
return return
# collect conversion results
results = {}
if VERBOSE : if VERBOSE :
print('CONVERTING HEX: ', hexCode) results['verbose-msg'] = 'CONVERTING HEX: ' + hexcode
outputs = {} rgbValues = HEXtoRGB(hexcode)
rgbValues = HEXtoRGB(hexCode) for colorFormat in TYPES :
if 'hex' in outputFormats : if colorFormat in outputFormats and colorFormat == 'hex' :
outputs['hex'] = hexCode results['hex'] = hexcode
if 'rgb' in outputFormats : elif colorFormat in outputFormats and colorFormat == 'rgb' :
outputs['rgb'] = rgbValues results['rgb'] = rgbValues
if 'cmy' in outputFormats : elif colorFormat in outputFormats :
outputs['cmy'] = RGBtoCMY(rgbValues) results[colorFormat] = rgbTo(colorFormat, rgbValues)
if 'cmyk' in outputFormats :
outputs['cmyk'] = RGBtoCMYK(rgbValues)
if 'hsl' in outputFormats :
outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl')
if 'hsv' in outputFormats :
outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv')
printConversions(outputs) printConversions(results)
# ARGS # ARGS
# color: string containing RGB code # color: string containing RGB code
# outputFormats: list indicating which conversions to perform # outputFormats: list indicating which conversions to perform
def handleRGB(color, outputFormats) : def handleRGB(color, outputFormats) :
# validate/sanitize input
rgbValues = validateRGB(color) rgbValues = validateRGB(color)
if rgbValues is None : if rgbValues is None :
return return
# collect conversion results
results = {}
if VERBOSE : if VERBOSE :
print('CONVERTING RGB: ', rgbValues) results['verbose-msg'] = 'CONVERTING RGB: ' + str(rgbValues)
outputs = {} for colorFormat in TYPES :
if 'hex' in outputFormats : if colorFormat in outputFormats and colorFormat == 'rgb' :
outputs['hex'] = RGBtoHEX(rgbValues) results['rgb'] = rgbValues
if 'rgb' in outputFormats : elif colorFormat in outputFormats :
outputs['rgb'] = rgbValues results[colorFormat] = rgbTo(colorFormat, rgbValues)
if 'cmy' in outputFormats :
outputs['cmy'] = RGBtoCMY(rgbValues)
if 'cmyk' in outputFormats :
outputs['cmyk'] = RGBtoCMYK(rgbValues)
if 'hsl' in outputFormats :
outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl')
if 'hsv' in outputFormats :
outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv')
printConversions(outputs) printConversions(results)
# ARGS # ARGS
# color: string containing CMY code # color: string containing CMY code
# outputFormats: list indicating which conversions to perform # outputFormats: list indicating which conversions to perform
def handleCMY(color, outputFormats) : def handleCMY(color, outputFormats) :
# validate/sanitize input
cmyValues = validateCMYorCMYK(color, False) cmyValues = validateCMYorCMYK(color, False)
if cmyValues is None : if cmyValues is None :
return return
# collect conversion results
results = {}
if VERBOSE : if VERBOSE :
print('CONVERTING CMY: ', cmyValues) results['verbose-msg'] = 'CONVERTING CMY: ' + str(cmyValues)
outputs = {}
rgbValues = CMYtoRGB(cmyValues) rgbValues = CMYtoRGB(cmyValues)
if 'hex' in outputFormats : for colorFormat in TYPES :
outputs['hex'] = RGBtoHEX(rgbValues) if colorFormat in outputFormats and colorFormat == 'rgb' :
if 'rgb' in outputFormats : results['rgb'] = rgbValues
outputs['rgb'] = rgbValues elif colorFormat in outputFormats and colorFormat == 'cmy' :
if 'cmy' in outputFormats : results['cmy'] = cmyValues
outputs['cmy'] = cmyValues elif colorFormat in outputFormats :
if 'cmyk' in outputFormats : results[colorFormat] = rgbTo(colorFormat, rgbValues)
outputs['cmyk'] = RGBtoCMYK(rgbValues)
if 'hsl' in outputFormats :
outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl')
if 'hsv' in outputFormats :
outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv')
printConversions(outputs) printConversions(results)
# ARGS # ARGS
# color: string containing CMYK code # color: string containing CMYK code
# outputFormats: list indicating which conversions to perform # outputFormats: list indicating which conversions to perform
def handleCMYK(color, outputFormats) : def handleCMYK(color, outputFormats) :
# validate/sanitize input
cmykValues = validateCMYorCMYK(color, True) cmykValues = validateCMYorCMYK(color, True)
if cmykValues is None : if cmykValues is None :
return return
# collect conversion results
results = {}
if VERBOSE : if VERBOSE :
print('CONVERTING CMYK: ', cmykValues) results['verbose-msg'] = 'CONVERTING CMYK: ' + str(cmykValues)
outputs = {}
rgbValues = CMYKtoRGB(cmykValues) rgbValues = CMYKtoRGB(cmykValues)
if 'hex' in outputFormats : for colorFormat in TYPES :
outputs['hex'] = RGBtoHEX(rgbValues) if (colorFormat in outputFormats) and (colorFormat == 'rgb') :
if 'rgb' in outputFormats : results['rgb'] = rgbValues
outputs['rgb'] = rgbValues elif (colorFormat in outputFormats) and (colorFormat == 'cmyk') :
if 'cmy' in outputFormats : results['cmyk'] = cmykValues
outputs['cmy'] = RGBtoCMY(rgbValues) elif colorFormat in outputFormats :
if 'cmyk' in outputFormats : results[colorFormat] = rgbTo(colorFormat, rgbValues)
outputs['cmyk'] = cmykValues
if 'hsl' in outputFormats :
outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl')
if 'hsv' in outputFormats :
outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv')
printConversions(outputs) printConversions(results)
# ARGS # ARGS
# color: string containing CMY code # color: string containing CMY code
# outputFormats: list indicating which conversions to perform # outputFormats: list indicating which conversions to perform
# handle: string ('hsl' or 'hsv'), indicating which format to handle # handle: string ('hsl' or 'hsv'), indicating which format to handle
def handleHSVorHSL(color, handle, outputFormats) : def handleHSVorHSL(color, handle, outputFormats) :
# collect conversion results
validated = validateHSLorHSV(color) validated = validateHSLorHSV(color)
if validated is None : if validated is None :
return return
# collect conversion results
results = {}
if VERBOSE : if VERBOSE :
if handle == 'hsl' : if handle == 'hsl' :
print('CONVERTING HSL: ', color) results['verbose-msg'] = 'CONVERTING HSL: ' + str(validated)
else : else :
print('CONVERTING HSV: ', color) results['verbose-msg'] = 'CONVERTING HSV: ' + str(validated)
outputs = {}
rgbValues = HSLorHSVToRGB(validated, handle) rgbValues = HSLorHSVToRGB(validated, handle)
if 'hex' in outputFormats : for colorFormat in TYPES :
outputs['hex'] = RGBtoHEX(rgbValues) if (colorFormat in outputFormats) and (colorFormat == 'rgb') :
if 'rgb' in outputFormats : results['rgb'] = rgbValues
outputs['rgb'] = rgbValues elif (colorFormat in outputFormats) and (colorFormat == 'hsl') and (handle == 'hsl') :
if 'cmy' in outputFormats : results['hsl'] = validated
outputs['cmy'] = RGBtoCMY(rgbValues) elif (colorFormat in outputFormats) and (colorFormat == 'hsv') and (handle == 'hsv'):
if 'cmyk' in outputFormats : results['hsv'] = validated
outputs['cmyk'] = RGBtoCMYK(rgbValues) elif colorFormat in outputFormats :
if 'hsl' in outputFormats : results[colorFormat] = rgbTo(colorFormat, rgbValues)
outputs['hsl'] = RGBtoHSVorHSL(rgbValues, 'hsl')
if 'hsv' in outputFormats :
outputs['hsv'] = RGBtoHSVorHSL(rgbValues, 'hsv')
printConversions(outputs) printConversions(results)
'''
# CONVERSION FUNCTIONS
'''
## ##
# CONVERSION FUNCTIONS # RGB -> OTHER FORMATS SECTION
## ###
# Takes in a string, returns a list of 3 integers # rgbTo() acts as an interface between format handlers and RGB conversions (for easier addition of formats later)
def HEXtoRGB(hexCode) : # ARGS
rgbValues = [] # colorFormat: a string containing a color format to convert to
# rgbValues: the rgbValues to convert
tempSum = 0 # RETURNS
i = 0 # the converted color values in the specified format
while i < 6 : def rgbTo(colorFormat, rgbValues) :
tempSum += int(hexCode[i], 16) * 16 if colorFormat == 'hex' :
tempSum += int(hexCode[i+1], 16) return RGBtoHEX(rgbValues)
rgbValues.append(tempSum) if colorFormat == 'cmy' :
i = i + 2 return RGBtoCMY(rgbValues)
tempSum = 0 if colorFormat == 'cmyk' :
return RGBtoCMYK(rgbValues)
return rgbValues if colorFormat == 'hsl' :
return RGBtoHSVorHSL(rgbValues, 'hsl')
if colorFormat == 'hsv' :
return RGBtoHSVorHSL(rgbValues, 'hsv')
# Takes in a list of 3 integers, returns a string # Takes in a list of 3 integers, returns a string
def RGBtoHEX(rgbValues) : def RGBtoHEX(rgbValues) :
@@ -371,6 +351,24 @@ def RGBtoHSVorHSL(rgbValues, convertTo) :
return [hue, saturation * 100, value * 100] return [hue, saturation * 100, value * 100]
##
# OTHER FORMATS -> RGB SECTION
##
# Takes in a string, returns a list of 3 integers
def HEXtoRGB(hexCode) :
rgbValues = []
tempSum = 0
i = 0
while i < 6 :
tempSum += int(hexCode[i], 16) * 16
tempSum += int(hexCode[i+1], 16)
rgbValues.append(tempSum)
i = i + 2
tempSum = 0
return rgbValues
# Takes in a list of 3 floats, returns a list of 3 integers # Takes in a list of 3 floats, returns a list of 3 integers
def CMYtoRGB(cmyValues) : def CMYtoRGB(cmyValues) :
red = (1 - (cmyValues[0] / 100)) * 255 red = (1 - (cmyValues[0] / 100)) * 255
@@ -434,15 +432,100 @@ def HSLorHSVToRGB(values, convertFrom) :
R = chrome + m R = chrome + m
G = 0 + m G = 0 + m
B = x + m B = x + m
return [smartRound(R * 255), smartRound(G * 255), smartRound(B * 255)] return [int(R * 255), int(G * 255), int(B * 255)]
else : else :
print('RGB to HSV/HSL conversion failed') print('RGB to HSV/HSL conversion failed')
return return
##
# INPUT VALIDATION
##
'''
# INPUT VALIDATION
'''
# Takes in a string.
# If string contains a valid Hex color code, it gets returned as a string.
def validateHex(value) :
# attempt to extract hex code
hexcode = extractValues(value, 1, True)[0]
if not hexcode :
print('ERROR: Improper format for hex code (see --help)')
return
return hexcode
# Takes in a string. Returns same list as integers if valid RGB values.
def validateRGB(color) :
# extract 3 numbers from the provided values
rgbValues = extractValues(color, 3)
if not rgbValues :
return
intValues = []
for value in rgbValues :
# TODO see if i should smartround here instead of int cast
value = smartRound(value)
if (value < 0) or (value > 255) :
print('ERROR: Each RBG value must be between 0-255')
return
intValues.append(value)
return intValues
# Takes in a list of 3 or 4 strings. Returns same list as integers if valid CMYK values.
def validateCMYorCMYK(color, include_K) :
if include_K :
values = extractValues(color, 4)
else :
values = extractValues(color, 3)
if not values :
return
floatValues = []
for value in values :
value = float(value)
if (value < 0) or (value > 100) :
print('ERROR: Each CMY(K) value must be between 0,0-100.0')
return
floatValues.append(value)
return floatValues
# Takes in a list of 3 strings. Returns same list as 1 integer and 2 floats
def validateHSLorHSV(color) :
color = extractValues(color, 3)
if color is None :
return
for i in range(3) :
if i == 0 :
color[i] = smartRound(color[i])
else :
color[i] = float(color[i])
if (color[0] < 0) or (color[0] > 255) :
print('ERROR: Invalid hue value (should be 0-255)')
return
if (color[1] < 0) or (color[1] > 100) :
print('ERROR: Invalid saturation value (should be 0.0-100.0)')
return
if (color[2] < 0) or (color[2] > 100) :
print('ERROR: Invalid lightness/value value (should be 0.0-100.0)')
return
return [color[0], color[1], color[2]]
'''
# GENERAL UTILITIES
'''
# ARGS
# color: a string containing a color value in an unknown format
# outputFormats: a list containing information necessary for handling conversions, used if format is detected
# RETURNS
# truth if success, false if failure to detect a format
def detectColorFormat(color, outputFormats) : def detectColorFormat(color, outputFormats) :
if '#' in color : if '#' in color :
handleHex(color, outputFormats) handleHex(color, outputFormats)
@@ -466,13 +549,13 @@ def detectColorFormat(color, outputFormats) :
return False return False
# Extracts numerical values from a string. # Extracts numerical (hex or decimal) values from a string.
# Parameters: # ARGS
# color: string containing value(s) # color: string containing value(s)
# numValues: determines how many values to extract (e.g. 3 for RGB, 4 for CMYK) # numValues: determines how many values to extract (e.g. 3 for RGB, 4 for CMYK)
# isHex: determines if we're looking for hex rather than decimal # isHex: determines if we're looking for hex rather than decimal
# RETURNS
# returns: list of extracted color/number values in string form # a list of extracted color/number values in string form (or false if faulure to extract)
def extractValues(color, numValues, isHex = False) : def extractValues(color, numValues, isHex = False) :
i = 0 i = 0
tempValue = '' tempValue = ''
@@ -519,90 +602,16 @@ def extractValues(color, numValues, isHex = False) :
return extractedValues return extractedValues
# formats and delivers color values to STDOUT or a specified output file
# Takes in a string. # ARGS
# If string contains a valid Hex color code, it gets returned as a string. # convertedValues: a map of the results from the color translations that were performed on a single color value,
def validateHex(value) : # where the key indicates the format and the value is a list of raw numeric values
# attempt to extract hex code
hexcode = extractValues(value, 1, True)[0]
if not hexcode :
print('ERROR: Improper format for hex code (see --help)')
return
return hexcode
# Takes in a string. Returns same list as integers if valid RGB values.
def validateRGB(color) :
# extract 3 numbers from the provided values
rgbValues = extractValues(color, 3)
if not rgbValues :
return
intValues = []
for value in rgbValues :
# TODO see if i should smartround here instead of int cast
value = smartRound(value)
if (value < 0) or (value > 255) :
print('ERROR: Each RBG value must be between 0-255')
return
intValues.append(value)
return intValues
# Takes in a list of 3 or 4 strings. Returns same list as integers if valid CMYK values.
def validateCMYorCMYK(color, include_K) :
if include_K :
values = extractValues(color, 4)
else :
values = extractValues(color, 3)
if not values :
return
floatValues = []
for value in values :
value = float(value)
if (value < 0) or (value > 100) :
print('ERROR: Each CMY(K) value must be between 0,0-100.0(%)')
return
floatValues.append(value)
return floatValues
# Takes in a list of 3 strings. Returns same list as 1 integer and 2 floats
def validateHSLorHSV(color) :
color = extractValues(color, 3)
if color is None :
return
for i in range(3) :
if i == 0 :
color[i] = smartRound(color[i])
else :
color[i] = float(color[i])
if (color[0] < 0) or (color[0] > 255) :
print('ERROR: Invalid hue value (should be 0-255)')
return
if (color[1] < 0) or (color[1] > 100) :
print('ERROR: Invalid saturation value (should be 0.0-100.0)')
return
if (color[2] < 0) or (color[2] > 100) :
print('ERROR: Invalid lightness/value value (should be 0.0-100.0)')
return
return [color[0], color[1], color[2]]
##
# GENERAL UTILITIES
##
# takes in a map of color values and either prints their values to STDOUT or into a specified file
def printConversions(convertedValues) : def printConversions(convertedValues) :
# format converted values into better output strings
output = [] output = []
if VERBOSE :
output.append(convertedValues['verbose-msg'])
# format converted values into better output strings
for colorFormat in TYPES : for colorFormat in TYPES :
if convertedValues.get(colorFormat, None) is None : if convertedValues.get(colorFormat, None) is None :
continue continue
@@ -623,18 +632,22 @@ def printConversions(convertedValues) :
colorCode += str(value) + ', ' colorCode += str(value) + ', '
output.append(colorCode) output.append(colorCode)
# determine how to deliver output # deliver formatted output
if OUTPUT != 'stdout' : if OUTPUT != 'stdout' :
if APPEND :
with open(OUTPUT, 'a', encoding='utf-8') as file : '''
for color in output : TODO FOR EFFICIENCY:
file.write(color + '\n')
file.write('\n') right now, unless i'm mistaken and there's some hidden optimization going on, this is
else : going to open and close the file to write the conversions of each inputted color code.
with open(OUTPUT, 'w', encoding='utf-8') as file : if there's a huge file being used as input, this may get slow, so perhaps the best move is
for color in output : to actually open the file back in main() at the point when we evaluate args, and then close
file.write(color + '\n') it after everything in main() has ran... food for thought.
file.write('\n') '''
with open(OUTPUT, 'a', encoding='utf-8') as file :
for color in output :
file.write(color + '\n')
file.write('\n')
else : else :
for color in output : for color in output :
print(color) print(color)