diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..069d74c --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Marcos Ojeda, generic.cx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c366323 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Swatch + +swatch is a parser for adobe swatch exchange files + +Copyright (c) 2012 Marcos A Ojeda http://generic.cx/ + + +http://iamacamera.org/default.aspx?id=109 by Carl Camera and +http://www.colourlovers.com/ase.phps by Chris Williams + + +swatch.parse reads in an ase file and converts it to a list of colors and +palettes. colors are simple dicts of the form + + { + 'name': u'color name', + 'data': { + 'mode': 'RGB', + 'values': [1.0, 1.0, 1.0] + } + } + +the values provided vary between color mode. For all color modes, the +value is always a list of floats. + +RGB: three floats between [0,1] corresponding to RGB +CMYK: four floats between [0,1] inclusive, corresponding to CMYK +Gray: one float between [0,1] with 1 being white, 0 being black +LAB: three floats. The first L, is ranged from 0,1. Both A and B are +floats ranging from [-128.0,127.0]. I believe illustrator just crops +these to whole values, though. + +Palettes are also dicts, but they have an attribute named swatches which +contains a list of colors contained within the palette. + + { + 'name': u'accent colors', + 'swatches': [ + {color}, {color}, ..., {color} + ] + } + +Because Adobe Illustrator lets swatches exist either inside and outside +of palettes, the output of swatch.parse is a list that may contain +swatches and palettes, i.e. [ [swatch|palette]* ] + +Here's an example: + +>>> import swatch +>>> swatch.parse("simple palette.ase") +[{'data': {'mode': 'RGB', 'values': [1.0, 1.0, 1.0]}, 'name': u'Bright White'}] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9a2b9f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,28 @@ +from distutils.core import setup +import codecs +from swatch import __version__ as VERSION + +README = codecs.open('README.md', encoding='utf-8').read() +LICENSE = codecs.open('LICENSE', encoding='utf-8').read() + +setup( + name='swatch', + version=VERSION, + author='Marcos A Ojeda', + author_email='marcos@generic.cx', + packages=['swatch'], + license=LICENSE, + description='a parser for adobe swatch exchange files' + long_description=README, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Artistic Software', + 'Topic :: Multimedia :: Graphics', + 'Topic :: Multimedia :: Graphics :: Graphics Conversion', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] +) diff --git a/swatch/__init__.py b/swatch/__init__.py new file mode 100644 index 0000000..41aab88 --- /dev/null +++ b/swatch/__init__.py @@ -0,0 +1,77 @@ +# encoding: utf-8 +""" +swatch, a parser for adobe swatch exchange files +Copyright (c) 2012 Marcos A Ojeda http://generic.cx/ + +With notes from +http://iamacamera.org/default.aspx?id=109 by Carl Camera and +http://www.colourlovers.com/ase.phps by Chris Williams + +All Rights Reserved +MIT Licensed, see LICENSE.TXT for details +""" + + +__title__ = 'swatch' +__version__ = '0.1.0' +__author__ = 'Marcos Ojeda' +__license__ = 'MIT' +__copyright__ = 'Copyright 2012 Marcos A Ojeda' + + +from . import parser +import struct + + +def parse(filename): + """parses a .ase file and returns a list of colors and palettes + + swatch.parse reads in an ase file and converts it to a list of colors and + palettes. colors are simple dicts of the form + + { + 'name': u'color name', + 'data': { + 'mode': 'RGB', + 'values': [1.0, 1.0, 1.0] + } + } + + the values provided vary between color mode. For all color modes, the + value is always a list of floats. + + RGB: three floats between [0,1] corresponding to RGB + CMYK: four floats between [0,1] inclusive, corresponding to CMYK + Gray: one float between [0,1] with 1 being white, 0 being black + LAB: three floats. The first L, is ranged from 0,1. Both A and B are + floats ranging from [-128.0,127.0]. I believe illustrator just crops + these to whole values, though. + + Palettes are also dicts, but they have an attribute named swatches which + contains a list of colors contained within the palette. + + { + 'name': u'accent colors', + 'swatches': [ + {color}, {color}, ..., {color} + ] + } + + Because Adobe Illustrator lets swatches exist either inside and outside + of palettes, the output of swatch.parse is a list that may contain + swatches and palettes, i.e. [ [swatch|palette]* ] + + Here's an example: + + >>> import swatch + >>> swatch.parse("simple palette.ase") + [{'data': {'mode': 'RGB', 'values': [1.0, 1.0, 1.0]}, 'name': u'Bright White'}] + + """ + with open(filename, "rb") as data: + header, version_major, version_minor, chunk_count = struct.unpack("!4sHHI", data.read(12)) + + assert header == "ASEF" + assert (version_major, version_minor) == (1, 0) + + return [c for c in parser.parse_chunk(data)] diff --git a/swatch/parser.py b/swatch/parser.py new file mode 100644 index 0000000..2f4c1e0 --- /dev/null +++ b/swatch/parser.py @@ -0,0 +1,73 @@ +# encoding: utf-8 +""" +swatch, a parser for adobe swatch exchange files +Copyright (c) 2012 Marcos A Ojeda http://generic.cx/ + +With notes from +http://iamacamera.org/default.aspx?id=109 and +http://www.colourlovers.com/ase.phps + +All Rights Reserved +MIT Licensed, see LICENSE.TXT for details +""" +import struct +import os + + +def parse_chunk(fd): + chunk_type = fd.read(2) + while chunk_type: + if chunk_type == '\x00\x01': + # a single color + o = dict_for_chunk(fd) + yield o + + elif chunk_type == '\xC0\x01': + # folder/palate + o = dict_for_chunk(fd) + o['swatches'] = [x for x in colors(fd)] + yield o + + elif chunk_type == '\xC0\x02': + # this signals the end of a folder + assert fd.read(4) == '\x00\x00\x00\x00' + pass + + else: + # the file is malformed? + assert chunk_type in [ + '\xC0\x01', '\x00\x01', '\xC0\x02', '\x00\x02'] + pass + + chunk_type = fd.read(2) + + +def colors(fd): + chunk_type = fd.read(2) + while chunk_type in ['\x00\x01', '\x00\x02']: + d = dict_for_chunk(fd) + yield d + chunk_type = fd.read(2) + fd.seek(-2, os.SEEK_CUR) + + +def dict_for_chunk(fd): + chunk_length = struct.unpack(">I", fd.read(4))[0] + data = fd.read(chunk_length) + + title_length = (struct.unpack(">H", data[:2])[0]) * 2 + title = data[2:2 + title_length].decode("utf-16be").strip('\0') + color_data = data[2 + title_length:] + + output = { + 'name': title, + } + + if color_data: + fmt = {'RGB': '!fff', 'Gray': '!f', 'CMYK': '!ffff', 'LAB': '!fff'} + color_type = struct.unpack("!4s", color_data[:4])[0].strip() + color_values = list(struct.unpack(fmt[color_type], color_data[4:-2])) + data = {'mode': color_type, 'values': color_values} + output.update(data=data) + + return output