diff --git a/README.md b/README.md index aff9015..0206571 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ I originally (and still) just want this to be an exercise in Python that results That said, as I develop this, I see lots of room for improvement. Such as: - automatic format detection, no longer requiring a flag to indicate what format input is -- accept more than one color code at a time +- accept more than one color code at a time (-f for file input, -l for multiple (list of) strings - (to expand on ^) accept input from text file, where each line is it's own color code to convert - add flags to control which conversions are output (in the case you only want a single conversion) and how (do you want just the raw values or the whole string for with commas and stuff?) - combine the last two bullet points and boom: now the tool could take a whole list of colors in X format and spit out a file where every color is in Y format @@ -33,6 +33,12 @@ maybe these ideas branch off into separate projects themselves, but: - accept images as output and some sort of color code that gets applied to said image (like a CLI program for applying color filters to images) ## Conversion Sources -Hex<->RGB: https://en.wikipedia.org/wiki/Web_colors +Hex<->RGB: +https://en.wikipedia.org/wiki/Web_colors -RGB<->CMYK: https://thecolorsmeaning.com/rgb-to-cmyk/ +RGB<->CMYK: +https://www.101computing.net/cmyk-to-rgb-conversion-algorithm/ +https://thecolorsmeaning.com/rgb-to-cmyk/ + +RGB<->CMY: +http://colormine.org/convert/rgb-to-cmy diff --git a/color-converter.py b/color-converter.py index 25ab90b..e933209 100644 --- a/color-converter.py +++ b/color-converter.py @@ -1,9 +1,9 @@ -# import sys +import math import argparse # Order of types must match order of arguments defined # (or else arg validation will no longer work properly) -TYPES = ['hex', 'rgb', 'cmyk'] +TYPES = ['hex', 'rgb', 'cmy', 'cmyk'] # look into CMY, HSL, HSV HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f'] @@ -13,11 +13,11 @@ def main(): # Set up arguments parser = argparse.ArgumentParser() - parser.add_argument('-hex', action='store_true', help='convert from hex (accepts hexadecimal input [ffffff] or string ["#ffffff"] (both are case insensitive)') + parser.add_argument('-hex', action='store_true', help='convert from hex (accepts hexadecimal input [ffffff] or string ["#ffffff"] (case insensitive, "#" is optional in string)') parser.add_argument('-rgb', action='store_true', help='convert from RGB (accepts integer input [R G B], or string [\"rgb(R, G, B)\"] (case/whitespace insensitive)') + parser.add_argument('-cmy', action='store_true', help='convert from CMY (accepts integer input [C M Y], or string [\"cmy(C, M, Y)\"] (case/whitespace insensitive)') parser.add_argument('-cmyk', action='store_true', help='convert from CMYK (accepts integer input [C M Y K], or string [\"cmyk(C, M, Y, K)\"] (case/whitespace insensitive)') - - parser.add_argument('color', nargs='*', help='accepts a color in Hex, RGB, CMYK, or HSL and performs format conversions (does not support CMYK profiles, conversions are uncalibrated)') + 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)') args = parser.parse_args() @@ -33,57 +33,47 @@ def main(): # HANDLE HEX INPUT if args.hex : - color = color[0].lower().replace(' ', '').strip('#') - if not validateHex(color) : - return - - convertFromHex(color) + handleHex(color) # HANDLE RGB INPUT if args.rgb : - - convertFromRGB(color) + handleRGB(color) + + if args.cmy : + handleCMY(color) # HANDLE CMYK INPUT if args.cmyk : - convertFromCMYK(color) + handleCMYK(color) ## -# HEX CONVERSION SECTION +# COLOR HANDLERS ## # Takes in valid RGB code and converts it to the other formats -def convertFromHex(hexCode) : +def handleHex(color) : + # cleanse hex code + hexCode = color[0].lower().replace(' ', '').strip('#') + if not validateHex(hexCode) : + return + print('convert hex: ', hexCode, '\n') - + rgbValues = hexToRGB(hexCode) - print("RGB: ", rgbValues) - + cmyValues = rgbToCMY(rgbValues) cmykValues = rgbToCMYK(rgbValues) + + print("RGB: ", rgbValues) + print("CMY: ", cmyValues) print("CMYK: ", cmykValues) - - # convertToHSL('hex', code) -def rgbToHex(rgbValues) : - hexValue = '' - - for rgbValue in rgbValues : - hexValue += hex(int(rgbValue / 16)) - hexValue += hex(int(rgbValue % 16)) - - return hexValue - - - - ## -# RGB CONVERSION SECTION -## + # convertToHSL('hex', code) # Takes in valid RGB code and converts it to the other formats -def convertFromRGB(color) : +def handleRGB(color) : # cleanse any non-numerical stuff if len(color) == 1 : color = color[0].lower().strip('rgb(').strip(')').replace(' ', '').split(',') @@ -92,19 +82,66 @@ def convertFromRGB(color) : if rgbValues is None : return - for i in range(len(rgbValues)) : - rgbValues[i] = int(rgbValues[i]) + # for i in range(len(rgbValues)) : + # rgbValues[i] = int(rgbValues[i]) print('convert RGB: ', rgbValues, '\n') hexCode = rgbToHex(rgbValues) - print('Hex: ', hexCode) - + cmyValues = rgbToCMY(rgbValues) cmykValues = rgbToCMYK(rgbValues) + print('CMYK: ', cmykValues) + print('Hex: ', hexCode) + print('CMY: ', cmyValues) # convertToHSL('rgb', rgbValues) +def handleCMY(color) : + # cleanse any non-numerical stuff + if len(color) == 1 : + color = color[0].lower().strip('cmy(').strip(')').replace('%', '').replace(' ', '').split(',') + cmyValues = validateCMYK(color, False) + if cmyValues is None : + return + + print('convert CMY: ', cmyValues, '\n') + + rgbValues = cmyToRGB(cmyValues) + hexCode = rgbToHex(rgbValues) + cmykValues = rgbToCMYK(rgbValues) + + print('Hex: ', hexCode) + print('RGB: ', rgbValues) + print('CMYK: ', cmykValues) + +def handleCMYK(color) : + # cleanse any non-numerical stuff + if len(color) == 1 : + color = color[0].lower().strip('cmyk(').strip(')').replace('%', '').replace(' ', '').split(',') + cmykValues = validateCMYK(color, True) + if cmykValues is None : + return + + print('convert CMYK: ', cmykValues, '\n') + + rgbValues = cmykToRGB(cmykValues) + hexCode = rgbToHex(rgbValues) + cmyValues = rgbToCMY(rgbValues) + + print('Hex: ', hexCode) + print('RGB: ', rgbValues) + print('CMY: ', cmyValues) + + + + + +## +# CONVERSION SECTION +## + +# Takes in a string, returns a list of 3 integers def hexToRGB(hexCode) : rgbValues = [] @@ -119,65 +156,77 @@ def hexToRGB(hexCode) : return rgbValues -def cmykToRGB(cmykValues) : - x = 1 - (cmykValues[3] / 100) - red = 255 * (1 - (cmykValues[0] / 100)) * x - green = 255 * (1 - (cmykValues[1] / 100)) * x - blue = 255 * (1 - (cmykValues[2] / 100)) * x +# Takes in a list of 3 integers, returns a string +def rgbToHex(rgbValues) : + hexValue = '' + + for rgbValue in rgbValues : + hexValue += hex(rgbValue / 16) + hexValue += hex(rgbValue % 16) - return [int(red), int(green), int(blue)] + return hexValue +# Takes in a list of 3 integers, returns a list of 3 floats (percentages) +def rgbToCMY(rgbValues) : + # Normalize RGB values + normalRed = rgbValues[0] / 255 + normalGreen = rgbValues[1] / 255 + normalBlue = rgbValues[2] / 255 + # Convert + cyan = 1 - normalRed + magenta = 1 - normalGreen + yellow = 1 - normalBlue + return [round(cyan * 100, 2), round(magenta * 100, 2), round(yellow * 100, 2)] -## -# CMYK CONVERSION SECTION -## - -def convertFromCMYK(color) : - # cleanse any non-numerical stuff - if len(color) == 1 : - color = color[0].lower().strip('cmyk(').strip(')').replace('%', '').replace(' ', '').split(',') - - cmykValues = validateCMYK(color) - if cmykValues is None : - return - - print('convert CMYK: ', cmykValues, '\n') - - rgbValues = cmykToRGB(cmykValues) - print('RGB: ', rgbValues) - - hexCode = rgbToHex(rgbValues) - print('Hex: ', hexCode) - +# Takes in a list of 3 integers, returns a list of 4 floats (percentages) def rgbToCMYK(rgbValues) : # Normalize RGB values normalRed = rgbValues[0] / 255 normalGreen = rgbValues[1] / 255 normalBlue = rgbValues[2] / 255 - # Establish black - black = 1 - max(normalRed, normalGreen, normalBlue) - x = 1 - black + black = 1 - max(normalRed, normalGreen, normalBlue) + x = 1 - black # Convert - cyan = (1 - normalRed - black) / x - magenta = (1 - normalGreen - black) / x - yellow = (1 - normalBlue - black) / x + if x == 0 : + return [0, 0, 0, round(black * 100, 2)] + else : + cyan = (1 - normalRed - black) / x + magenta = (1 - normalGreen - black) / x + yellow = (1 - normalBlue - black) / x return [round(cyan * 100, 2), round(magenta * 100, 2), round(yellow * 100, 2), round(black * 100, 2)] +# Takes in a list of 3 floats, returns a list of 3 integers +def cmyToRGB(cmyValues) : + red = (1 - (cmyValues[0] / 100)) * 255 + green = (1 - (cmyValues[1] / 100)) * 255 + blue = (1 - (cmyValues[2] / 100)) * 255 + + return [smartRound(red), smartRound(green), smartRound(blue)] + +# Takes in a list of 4 floats, returns a list of 3 integers +def cmykToRGB(cmykValues) : + x = 1 - (cmykValues[3] / 100) + red = 255 * (1 - (cmykValues[0] / 100)) * x + green = 255 * (1 - (cmykValues[1] / 100)) * x + blue = 255 * (1 - (cmykValues[2] / 100)) * x + + return [smartRound(red), smartRound(green), smartRound(blue)] ## -# INPUT VALIDATION SECTION +# VALIDATION SECTION ## # Takes in a string. Returns True if valid Hex color code. def validateHex(value) : + if len(value) != 6 : - print('ERROR: Improper number of values for hex (should be 6 digits)') + print('ERROR: Improper format for hex code (see --help)') return False for i in range(len(value)): @@ -212,28 +261,32 @@ def validateRGB(values) : return intValues # Takes in a list of strings. Returns same list as integers if valid CMYK values. -def validateCMYK(values) : - intValues = [] +def validateCMYK(values, include_K) : + floatValues = [] - if len(values) != 4 : + if (include_K and len(values) != 4) : print('ERROR: Improper number of values for CMYK (should be 4)') return - + + if (not include_K and len(values) != 3) : + print('ERROR: Improper number of values for CMY (should be 3)') + return + for value in values : - if not value.isnumeric() : - print('ERROR: Improper format for CMYK value(s). All values must be numeric and between 0-100(%)!') + if not value.replace('.', '').isnumeric() : + print('ERROR: Improper format for CMY(K) value(s). All values must be numeric and between 0.0-100.0(%)!') return - value = int(value) - intValues.append(value) + value = float(value) + floatValues.append(value) if (value < 0) or (value > 100) : - print('ERROR: Each CMYK value must be between 0-100(%)') + print('ERROR: Each CMY(K) value must be between 0,0-100.0(%)') return - return intValues + return floatValues # Takes in the program's arguments generated by argparse. Returns True if valid arguments def validateArguments(args) : - # First, check that only one input flag is being used + # ck that only one input flag is being used flagsActive = 0 for flag in range(len(vars(args))-1) : # this uses our TYPES list to key into the args dictionary and determine how many flags are True @@ -243,8 +296,7 @@ def validateArguments(args) : print('ERROR: Currently this tool only supports one input type at a time. Too many flags!') return False - # Second, check that there is a color code to convert - if len(args.color) == 0 : + if (flagsActive == 0) or (len(args.color) == 0) : print('ERROR: Must enter both an input flag and color code (did you forget to wrap color code in quotes?)\nFor more info, use the \'-h\' or \'--help\' flag.') return False @@ -258,6 +310,13 @@ def validateArguments(args) : # GENERAL UTILITIES ## +# Takes in a float, returns the nearest integer +def smartRound(value) : + if (value % 1) > .50 : + return math.ceil(value) + else : + return math.floor(value) + # Takes in a decimal number and converts it to hexadecimal def hex(number) : number = int(number) @@ -268,7 +327,6 @@ def hex(number) : return str(number) return HEX_LETTERS[number % 10] - - +# init if __name__ == '__main__' : main()