CMY now fully supported, plus refactoring + organization

This commit is contained in:
2024-12-01 18:22:59 -05:00
parent 2982d65162
commit 810552638d
2 changed files with 156 additions and 92 deletions
+9 -3
View File
@@ -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: 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 - 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 - (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?) - 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 - 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) - 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 ## 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
+143 -85
View File
@@ -1,9 +1,9 @@
# import sys import math
import argparse import argparse
# Order of types must match order of arguments defined # Order of types must match order of arguments defined
# (or else arg validation will no longer work properly) # (or else arg validation will no longer work properly)
TYPES = ['hex', 'rgb', 'cmyk'] TYPES = ['hex', 'rgb', 'cmy', 'cmyk']
# look into CMY, HSL, HSV # look into CMY, HSL, HSV
HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f'] HEX_LETTERS = ['a', 'b', 'c', 'd', 'e', 'f']
@@ -13,11 +13,11 @@ def main():
# Set up arguments # Set up arguments
parser = argparse.ArgumentParser() 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('-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('-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, CMY, 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, CMYK, or HSL and performs format conversions (does not support CMYK profiles, conversions are uncalibrated)')
args = parser.parse_args() args = parser.parse_args()
@@ -33,57 +33,47 @@ def main():
# HANDLE HEX INPUT # HANDLE HEX INPUT
if args.hex : if args.hex :
color = color[0].lower().replace(' ', '').strip('#') handleHex(color)
if not validateHex(color) :
return
convertFromHex(color)
# HANDLE RGB INPUT # HANDLE RGB INPUT
if args.rgb : if args.rgb :
handleRGB(color)
convertFromRGB(color) if args.cmy :
handleCMY(color)
# HANDLE CMYK INPUT # HANDLE CMYK INPUT
if args.cmyk : if args.cmyk :
convertFromCMYK(color) handleCMYK(color)
## ##
# HEX CONVERSION SECTION # COLOR HANDLERS
## ##
# Takes in valid RGB code and converts it to the other formats # 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') print('convert hex: ', hexCode, '\n')
rgbValues = hexToRGB(hexCode) rgbValues = hexToRGB(hexCode)
print("RGB: ", rgbValues) cmyValues = rgbToCMY(rgbValues)
cmykValues = rgbToCMYK(rgbValues) cmykValues = rgbToCMYK(rgbValues)
print("RGB: ", rgbValues)
print("CMY: ", cmyValues)
print("CMYK: ", cmykValues) print("CMYK: ", cmykValues)
# convertToHSL('hex', code) # 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
##
# Takes in valid RGB code and converts it to the other formats # Takes in valid RGB code and converts it to the other formats
def convertFromRGB(color) : def handleRGB(color) :
# cleanse any non-numerical stuff # cleanse any non-numerical stuff
if len(color) == 1 : if len(color) == 1 :
color = color[0].lower().strip('rgb(').strip(')').replace(' ', '').split(',') color = color[0].lower().strip('rgb(').strip(')').replace(' ', '').split(',')
@@ -92,19 +82,66 @@ def convertFromRGB(color) :
if rgbValues is None : if rgbValues is None :
return return
for i in range(len(rgbValues)) : # for i in range(len(rgbValues)) :
rgbValues[i] = int(rgbValues[i]) # rgbValues[i] = int(rgbValues[i])
print('convert RGB: ', rgbValues, '\n') print('convert RGB: ', rgbValues, '\n')
hexCode = rgbToHex(rgbValues) hexCode = rgbToHex(rgbValues)
print('Hex: ', hexCode) cmyValues = rgbToCMY(rgbValues)
cmykValues = rgbToCMYK(rgbValues) cmykValues = rgbToCMYK(rgbValues)
print('CMYK: ', cmykValues) print('CMYK: ', cmykValues)
print('Hex: ', hexCode)
print('CMY: ', cmyValues)
# convertToHSL('rgb', rgbValues) # 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) : def hexToRGB(hexCode) :
rgbValues = [] rgbValues = []
@@ -119,65 +156,77 @@ def hexToRGB(hexCode) :
return rgbValues return rgbValues
def cmykToRGB(cmykValues) : # Takes in a list of 3 integers, returns a string
x = 1 - (cmykValues[3] / 100) def rgbToHex(rgbValues) :
red = 255 * (1 - (cmykValues[0] / 100)) * x hexValue = ''
green = 255 * (1 - (cmykValues[1] / 100)) * x
blue = 255 * (1 - (cmykValues[2] / 100)) * x
return [int(red), int(green), int(blue)] for rgbValue in rgbValues :
hexValue += hex(rgbValue / 16)
hexValue += hex(rgbValue % 16)
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) : def rgbToCMYK(rgbValues) :
# Normalize RGB values # Normalize RGB values
normalRed = rgbValues[0] / 255 normalRed = rgbValues[0] / 255
normalGreen = rgbValues[1] / 255 normalGreen = rgbValues[1] / 255
normalBlue = rgbValues[2] / 255 normalBlue = rgbValues[2] / 255
# Establish black black = 1 - max(normalRed, normalGreen, normalBlue)
black = 1 - max(normalRed, normalGreen, normalBlue) x = 1 - black
x = 1 - black
# Convert # Convert
cyan = (1 - normalRed - black) / x if x == 0 :
magenta = (1 - normalGreen - black) / x return [0, 0, 0, round(black * 100, 2)]
yellow = (1 - normalBlue - black) / x 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)] 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. # Takes in a string. Returns True if valid Hex color code.
def validateHex(value) : def validateHex(value) :
if len(value) != 6 : 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 return False
for i in range(len(value)): for i in range(len(value)):
@@ -212,28 +261,32 @@ def validateRGB(values) :
return intValues return intValues
# Takes in a list of strings. Returns same list as integers if valid CMYK values. # Takes in a list of strings. Returns same list as integers if valid CMYK values.
def validateCMYK(values) : def validateCMYK(values, include_K) :
intValues = [] floatValues = []
if len(values) != 4 : if (include_K and len(values) != 4) :
print('ERROR: Improper number of values for CMYK (should be 4)') print('ERROR: Improper number of values for CMYK (should be 4)')
return 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 : for value in values :
if not value.isnumeric() : if not value.replace('.', '').isnumeric() :
print('ERROR: Improper format for CMYK value(s). All values must be numeric and between 0-100(%)!') print('ERROR: Improper format for CMY(K) value(s). All values must be numeric and between 0.0-100.0(%)!')
return return
value = int(value) value = float(value)
intValues.append(value) floatValues.append(value)
if (value < 0) or (value > 100) : 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
return intValues return floatValues
# Takes in the program's arguments generated by argparse. Returns True if valid arguments # Takes in the program's arguments generated by argparse. Returns True if valid arguments
def validateArguments(args) : def validateArguments(args) :
# First, check that only one input flag is being used # ck that only one input flag is being used
flagsActive = 0 flagsActive = 0
for flag in range(len(vars(args))-1) : 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 # 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!') print('ERROR: Currently this tool only supports one input type at a time. Too many flags!')
return False return False
# Second, check that there is a color code to convert if (flagsActive == 0) or (len(args.color) == 0) :
if 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.') 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 return False
@@ -258,6 +310,13 @@ def validateArguments(args) :
# GENERAL UTILITIES # 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 # Takes in a decimal number and converts it to hexadecimal
def hex(number) : def hex(number) :
number = int(number) number = int(number)
@@ -268,7 +327,6 @@ def hex(number) :
return str(number) return str(number)
return HEX_LETTERS[number % 10] return HEX_LETTERS[number % 10]
# init
if __name__ == '__main__' : if __name__ == '__main__' :
main() main()