Sorry for the downtime, welcome back!!

Spriter - a chart generator for Mac

Forum and Craft Related Topics...
ScienceDad
Rank 4 - Raccoon Mario
Rank 4 - Raccoon Mario
Posts: 105
Joined: Thu Sep 23, 2010 10:41 pm

Re: Spriter - a chart generator for Mac

Post by ScienceDad »

Eliste wrote:So... What would happen if you DID put in a picture that would use more than 72 colors?
The thread colour legends spill off the page :grin:

It's a problem I will fix in an upcoming revision. I've also been thinking about your feature requests, and I'm seriously considering writing my own user interface with image previews so that Spriter can do its own image re-scaling (i.e., "make this 70 pixels wide") and colour reduction (i.e., "I don't want to use more than 50 thread colours"). Apparently it's not too difficult to make your own Python interface in Apple XCode.

Besides, I'm about to go on parental leave so I may actually have some time for coding :-)

- sd.
pixelcraft, a free and open-source web app for making craft patterns from sprites:
http://www.theoryandwings.com/software/pixelcraft

User avatar
Eliste
Rank 10 - Cape Mario
Rank 10 - Cape Mario
Posts: 1007
Joined: Wed Mar 16, 2011 3:41 pm
Contact:

Re: Spriter - a chart generator for Mac

Post by Eliste »

Cool. I was considering a really big project, but I can leave it on my future possibility list rather than adding it to the giant halfway-done get-a-move-on why-havent-you-finished-this-yet pile until the next update at least!
Image

ScienceDad
Rank 4 - Raccoon Mario
Rank 4 - Raccoon Mario
Posts: 105
Joined: Thu Sep 23, 2010 10:41 pm

Re: Spriter - a chart generator for Mac

Post by ScienceDad »

UPDATE!

New features and bug fixes in version 0.3.3:
- can now read indexed PNGs
- can now read PNGs with alpha channel (transparency)
- fixed bug that added extra blank pages when the number of rows or columns was an exact multiple of 60
- white (B5200) and black are now properly reserved colours (with respect to annotation)
- page-wise legends are now sorted by thread counts on that page
- total legend is now sorted by DMC numbers
- enabled multiple-page total legend

As usual, I'm replacing the attachment at the top of this thread. If anyone wants an older version of Spriter, please let me know on the forum and I'll get it to you somehow. :grin:

- sd.
pixelcraft, a free and open-source web app for making craft patterns from sprites:
http://www.theoryandwings.com/software/pixelcraft

User avatar
Lord Libidan
Rank 10 - Cape Mario
Rank 10 - Cape Mario
Posts: 1176
Joined: Wed Apr 07, 2010 1:44 am
Contact:

Re: Spriter - a chart generator for Mac

Post by Lord Libidan »

Whoa! "beta testing by Lord Libidan" I only just saw that. Wicked!

Anyways, it works well, there are a few things I would possibly implement though.

Firstly, I put in an image with a white background, and seeing where the 10 block marks are is quite hard. Is there any way you could either get it to auto check the background for a dark/light colour, or add an option to the possible interface so you could select one of a few colours.

Secondly, white. You just implemented B5200, but most people prefer BLANC. I know my local store has like 1000 BLANCs, but only 10 B5200. Reason being the B5200 is bleached, and so reacts to sunlight badly, going brown. Possible selection on the interface to include B5200? Not sure how else you could deal with it apart from removing B5200... I think some online pattern makers remove B5200...

Thirdly, closing it is an issue. I think this might be due to you using python, but I'm not sure.
Untitled.png
Untitled.png (3.5 KiB) Viewed 5858 times
You currently have to close it using your dock and not the top bar of the program.

Fourthly, could you make it so once the post script file has been closed it auto-deletes? Or somehow make it so the .ps saves as a pdf then deletes? Once the .ps is made its pretty useless, you have to save it as a pdf for any use. I don't know if its possible though, unless the .ps is opened and converted into a pdf in the spriter program itself. I think I'm understanding that correctly?

Otherwise, its coming along nicely. So nicely in fact that since 0.2 I haven't used another program. Oh! I know what I was going to say! If you're adding an interface for size variation, a suggestion would be to specify pixels and actual measurements based on count sizes. So you could specify count 14 at 10cm, and it would do the calculation for you. And then you could put into into the end page saying how big the pattern will be, even if they only specified the pixels.

Thought of two more things...
KG chart and other pattern makers state how many cells there are in the pattern. Something I never use, but noticed you didn't have. Maybe see who else uses them, and possibly put them in. No point putting things in when people don't use them though, is there? :P
I was talking to someone the other day IRL about spriter and they said they used anchor threads (They were english like me). Not sure if it really calls for adding another set of colours, but I said I would tell you what they said.
Is it me or is spriter sounding more and more like KG Chart and less like a simple drag and drop? :P
Image

ScienceDad
Rank 4 - Raccoon Mario
Rank 4 - Raccoon Mario
Posts: 105
Joined: Thu Sep 23, 2010 10:41 pm

Re: Spriter - a chart generator for Mac

Post by ScienceDad »

Re: white backgrounds - would it help if I darkened the border around white blocks?

Man - I wish I had known there were issues with B5200 thread before I started using it for everything :-(

Re: quitting - I always just use Command-Q to quit - but whenever I get around to coding my own interface I can rectify this.
Still dithering about whether to implement a GUI in Cocoa (XCode), or to do it all in Python.

Re: getting rid of the .ps file - I tried doing this by writing the *.ps file into the /tmp directory, but while this worked fine with a Python script I ran into permissions problems with the packaged .app version. Also, I'm loathe to put in a system command to delete a file, even if it's absolute path is fully specified.

I'm having second thoughts about putting in image rescaling. The Python library that I'm currently using to handle PNG images doesn't have much functionality for image transformation, but it's nice and compact. The current standard for manupulating PNGs in Python is a rather hefty library called PIL (Python Imaging Library). I'm not sure whether I want to muck about with it just yet :-)

I have no idea what "cells" are in this context :-)

Re: anchor threads - I'd have to see what the demand is. If a few people would like to see Anchor in Spriter then certainly I can work on it!
It's just a matter of finding a good RGB to Anchor map, and then extending the interface..

Again, I'm going to take a break from coding for a bit - this last update was a solid night of coding followed by my youngest waking everyone up the entire night from teething pains. Equals zero sleep :-(

- sd
pixelcraft, a free and open-source web app for making craft patterns from sprites:
http://www.theoryandwings.com/software/pixelcraft

User avatar
Lord Libidan
Rank 10 - Cape Mario
Rank 10 - Cape Mario
Posts: 1176
Joined: Wed Apr 07, 2010 1:44 am
Contact:

Re: Spriter - a chart generator for Mac

Post by Lord Libidan »

ScienceDad wrote:Re: white backgrounds - would it help if I darkened the border around white blocks?
I think that should do the trick.
ScienceDad wrote:Man - I wish I had known there were issues with B5200 thread before I started using it for everything :-(
Yeh, not many people know about it. I started off with one of these kits, and wondered why it BLANC and not perfect white, so I did some research.
ScienceDad wrote:Re: quitting - I always just use Command-Q to quit - but whenever I get around to coding my own interface I can rectify this.
Still dithering about whether to implement a GUI in Cocoa (XCode), or to do it all in Python.
Whats the difference between the two?
ScienceDad wrote:Re: getting rid of the .ps file - I tried doing this by writing the *.ps file into the /tmp directory, but while this worked fine with a Python script I ran into permissions problems with the packaged .app version. Also, I'm loathe to put in a system command to delete a file, even if it's absolute path is fully specified.
Couldn't it make the file and open it without saving it? Actually I don't think that would work, would it? hm...
ScienceDad wrote:I'm having second thoughts about putting in image rescaling. The Python library that I'm currently using to handle PNG images doesn't have much functionality for image transformation, but it's nice and compact. The current standard for manupulating PNGs in Python is a rather hefty library called PIL (Python Imaging Library). I'm not sure whether I want to muck about with it just yet :-)
I think its definitely something you should put in, but there is no rush. You could do everything else first, then fiddle with it at the end...
ScienceDad wrote:I have no idea what "cells" are in this context :-)
Uh, the actual cross stitches. In KG Chart it also specifies the non stitched bits in the background. In that case, maybe this isn't of any great use... Ignore :D
ScienceDad wrote:Again, I'm going to take a break from coding for a bit - this last update was a solid night of coding followed by my youngest waking everyone up the entire night from teething pains. Equals zero sleep :-(
Its a tough job being a science dad. :P
Image

User avatar
Lord Libidan
Rank 10 - Cape Mario
Rank 10 - Cape Mario
Posts: 1176
Joined: Wed Apr 07, 2010 1:44 am
Contact:

Re: Spriter - a chart generator for Mac

Post by Lord Libidan »

I was helping someone with a pattern today and it had some 320 colours in it and it took ages to process. Not a problem, but I wasn't sure how long it was going to take/if it had crashed, so I was wondering, could you add some type of completion bar or 100% sign somewhere? Just an idea to add to the massive list :P
Image

ScienceDad
Rank 4 - Raccoon Mario
Rank 4 - Raccoon Mario
Posts: 105
Joined: Thu Sep 23, 2010 10:41 pm

Re: Spriter - a chart generator for Mac

Post by ScienceDad »

Lord Libidan wrote:I was helping someone with a pattern today and it had some 320 colours in it and it took ages to process. Not a problem, but I wasn't sure how long it was going to take/if it had crashed, so I was wondering, could you add some type of completion bar or 100% sign somewhere? Just an idea to add to the massive list :P
:shock:

Uh - I've never attempted to process an image that complex. I'll have to do some runs on the script and figure out where the code spends the most time. My guess is that you're working with a very large image and it just takes a long time to write out the PostScript (the downside of writing raw PostScript is that it is quite inefficient - however, once it gets processed into a PDF it is much more compact and stays sharp).

Again, whenever I get around to designing the interface myself (I currently use Platypus - http://www.sveinbjorn.org/platypus- to package my Python script into a Mac .app), I can add a feature like this.

As for the difference between Cocoa and native Python interfaces:

- Cocoa is the current user interface API for OS X. XCode (ships with all OS X's) comes with a nice application called Interface Builder that makes putting together an interface a cinch. It's also possible to wrap Python in Objective-C and Cocoa, which is in fact what Platypus automates for you. The plus side is that the application is native OS X and looks nice. The minus side, for me personally, is that there is a steep learning curve to working with Cocoa. It has a LOT of functionality built-in, but in order to use it you have to invest a good chunk of time learning the ropes. I've never had a good reason to sit down and go through with it, not when I can whip up what I need in Python or even C in a tiny fraction of the time - because I've never had to distribute code with a GUI (though I've contributed to open-source projects where someone else dealt with it).

- Python - there are a lot of libraries for building GUIs in Python. The upside is that I eat, sleep and breathe Python. (Ew. That doesn't sound so great, does it?) The downside is that the GUI tends not to look nor feel like a native OS X application.

- sd.
pixelcraft, a free and open-source web app for making craft patterns from sprites:
http://www.theoryandwings.com/software/pixelcraft

tbsp
Rank 1 - Big Mario
Rank 1 - Big Mario
Posts: 18
Joined: Fri Jun 03, 2011 5:20 pm

Re: Spriter - a chart generator for Mac

Post by tbsp »

Your original post mentioned Spriter might be open-source, is this still your intention? I wanted to take a look at how you're doing thread color matching, since sometimes I'm getting results not quite in line with what I'd expect.

I know I can just stitch the pattern with a different thread, but I'm more curious as to why it's making the selections it is.

Edit:

After looking at it further (and assuming you likely just use the "nearest point" in the RGB space), it seems the program I'm using to do color reduction before running the image through Spriter (Irfanview) is tweaking the colors just enough to make the colors I don't want the nearest matches. I've tried two other programs, but they're doing even worse. The variation in results when reducing the same image from 57 to 34 colors in various programs is quite interesting.

Unfortunately my best results still seem to be with Stitches (OSX), which is doing such a good job (imo) at reduction/matching that I suspect it's somehow reducing colors with the DMC thread color space taken into consideration. I say unfortunately because that software carries an $80 price tag and doesn't produce very readable charts.

ScienceDad
Rank 4 - Raccoon Mario
Rank 4 - Raccoon Mario
Posts: 105
Joined: Thu Sep 23, 2010 10:41 pm

Re: Spriter - a chart generator for Mac

Post by ScienceDad »

tbsp wrote:Your original post mentioned Spriter might be open-source, is this still your intention? I wanted to take a look at how you're doing thread color matching, since sometimes I'm getting results not quite in line with what I'd expect.
Absolutely, I intend Spriter to be an open source project - only I became convinced that no one was interested in the source code :grin:

So here it is:

Code: Select all

# version 0.3 beta

# revision history
# 0.3.1 -> 0.3.2	Fixed a bug that prevented code from reserving white and
#					black colours.
# 0.3.2 -> 0.3.3	Enabled alpha channel parsing.
#					Now handles indexed PNG and alpha channels.
#					Added version string
#					Fixed bug that added extra pages when number of pixels
#						per row/column was an exact multiple of 60.
#					Legends are now organized by thread count (page-wise)
#						or by DMC number (total legend)
#					Now enabled multi-page legend.

_version = '0.3.3'

import png, sys, math, subprocess


# generate RGB-DMC dictionary from file obtained from http://www.xstitchtreasures.com/DMCFloss-RGBvalues.html
infile = open('dmc2rgb.csv', 'rU')
lines = infile.readlines()
infile.close()

#Floss#,Description,Red,Green,Blue,RGB code,Row
#3713,Salmon Very Light,255,226,226,FFE2E2,row 01-01
rgb2dmc = {}

for line in lines[1:]:
	dmc_code, dmc_name, red, green, blue, hex, rowinfo = line.strip('\n').split(',')
	rgb2dmc.update ( { (int(red), int(green), int(blue)) : (dmc_code, dmc_name) } )



# function to pick closest DMC colour based on RGB values
# use Euclidean distance for now - but human eye might be more sensitive to
# certain colour differences - check xkcd chart

def find_colour (rgb):
	min_dist = 442 # maximum possible distance in 256^3 colour space
	best_key = None
	for key in rgb2dmc.keys():
		euc_dist = math.sqrt( sum( [ (rgb[i]-key[i])**2 for i in range(3) ] ) )
		if euc_dist < min_dist:
			best_key = key
			min_dist = euc_dist
	dmc, name = rgb2dmc[best_key]
	return best_key, dmc, name




# add a small feature to distinguish block from other blocks of
# similar DMC colour
def add_accent (rgb, annot, left, top, size):
	half = size / 2
	res = ''
	# draw annotation in black or white?
	if sum(rgb) > 255:
		# darken 
		res += '%1.3f %1.3f %1.3f setrgbcolor\n' % (rgb[0]/3./255., rgb[1]/3./255., rgb[2]/3./255.)
	else:
		# lighten - note that values > 1 will be treated as 1.0
		res += '%1.3f %1.3f %1.3f setrgbcolor\n' % (rgb[0]*3./255., rgb[1]*3./255., rgb[2]*3./255.)
	
	res += 'newpath\n'
	
	if annot == 1: # -
		res += '%d %d moveto\n' % (left+1, top+half)
		res += '%d %d lineto\n' % (left+size-1, top+half)
		res += 'closepath\nstroke\n'
	elif annot == 2: # |
		res += '%d %d moveto\n' % (left+half, top+1)
		res += '%d %d lineto\n' % (left+half, top+size-1)
		res += 'closepath\nstroke\n'
	elif annot == 3: # \
		res += '%d %d moveto\n' % (left+1, top+1)
		res += '%d %d lineto\n' % (left+size-1, top+size-1)
		res += 'closepath\nstroke\n'
	elif annot == 4: # /
		res += '%d %d moveto\n' % (left+1, top+size-1)
		res += '%d %d lineto\n' % (left+size-1, top+1)
		res += 'closepath\nstroke\n'
	elif annot == 5: # +
		res += '%d %d moveto\n' % (left+1, top+half)
		res += '%d %d lineto\n' % (left+size-1, top+half)
		res += 'closepath\nstroke\n'
		res += 'newpath\n'
		res += '%d %d moveto\n' % (left+half, top+1)
		res += '%d %d lineto\n' % (left+half, top+size-1)
		res += 'closepath\nstroke\n'
	elif annot == 6: # X
		res += '%d %d moveto\n' % (left+2, top+2)
		res += '%d %d lineto\n' % (left+size-2, top+size-2)
		res += 'closepath\nstroke\n'
		res += 'newpath\n'
		res += '%d %d moveto\n' % (left+2, top+size-2)
		res += '%d %d lineto\n' % (left+size-2, top+2)
		res += 'closepath\nstroke\n'
	elif annot == 7: # o filled
		res += '%d %d moveto\n' % (left+half, top+half)
		res += '%d %d 2 0 360 arc\n' % (left+half, top+half)
		res += 'closepath\nfill\n'
	elif annot == 8: # o open
		res += '%d %d moveto\n' % (left+half+2, top+half)
		res += '%d %d 2 0 360 arc\n' % (left+half, top+half)
		res += 'closepath\nstroke\n'
	elif annot == 9: # top-left filled quadrant
		res += '%d %d moveto\n' % (left, top)
		res += '%d %d lineto\n' % (left+half, top)
		res += '%d %d lineto\n' % (left+half, top+half)
		res += '%d %d lineto\n' % (left, top+half)
		res += '%d %d lineto\n' % (left, top)
		res += 'closepath\nfill\n'
	elif annot == 9: # top-right filled quadrant
		res += '%d %d moveto\n' % (left+half, top)
		res += '%d %d lineto\n' % (left+size, top)
		res += '%d %d lineto\n' % (left+size, top+half)
		res += '%d %d lineto\n' % (left+half, top+half)
		res += '%d %d lineto\n' % (left, top)
		res += 'closepath\nfill\n'
	elif annot == 10: # bottom-left filled quadrant
		res += '%d %d moveto\n' % (left, top+half)
		res += '%d %d lineto\n' % (left+half, top+half)
		res += '%d %d lineto\n' % (left+half, top+size)
		res += '%d %d lineto\n' % (left, top+size)
		res += '%d %d lineto\n' % (left, top+half)
		res += 'closepath\nfill\n'
	elif annot == 11: # bottom-right filled quadrant
		res += '%d %d moveto\n' % (left+half, top)
		res += '%d %d lineto\n' % (left+size, top)
		res += '%d %d lineto\n' % (left+size, top+half)
		res += '%d %d lineto\n' % (left+half, top+half)
		res += '%d %d lineto\n' % (left+half, top)
		res += 'closepath\nfill\n'
	elif annot == 12: # semi-circle
		res += '%d %d moveto\n' % (left+half, top+half)
		res += '%d %d 2 0 180 arc\n' % (left+half, top+half)
		res += 'closepath\nfill\n'
	elif annot == 13:
		res += '%d %d moveto\n' % (left+half, top+half)
		res += '%d %d 2 90 270 arc\n' % (left+half, top+half)
		res += 'closepath\nfill\n'
	elif annot == 14:
		res += '%d %d moveto\n' % (left+half, top+half)
		res += '%d %d 2 180 360 arc\n' % (left+half, top+half)
		res += 'closepath\nfill\n'
	elif annot == 15:
		res += '%d %d moveto\n' % (left+half, top+half)
		res += '%d %d 2 270 90 arc\n' % (left+half, top+half)
		res += 'closepath\nfill\n'
	else:
		print 'ERROR: unrecognized annotation code ' + str(annot)
		sys.exit()
		
	return res


# =============================================

def main (argv=None):
	if argv == None:
		argv = sys.argv
	
	if len(argv) < 2:
		print 'Usage: python Spriter.py [PNG file]'
		sys.exit()
	
	#outfilename = '/tmp/'+argv[1].replace('.png', '.ps')
	outfilename = argv[1].replace('.png', '.ps')
	
	#block_size = int(sys.argv[2])
	#gap_size = int(sys.argv[3])
	#margin = int(sys.argv[4])
	
	"""
	for debugging
	
	infile = open('kimpine.png')
	"""
	block_size = 8
	gap_size = 1	# how much we skip between blocks
	gap10_size = 2 	# how much we skip between every 10 blocks
	
	legend_width = 200
	page_height = 612
	page_width = 792
	margin = 24
	inset = 8
	font_size = 10
	
	
	# import PNG information
	pr = png.Reader(argv[1])
	
	# using asRGBA() rather than read() seems to be robust
	# to alpha channel and indexed colour palettes
	ncol, nrow, imap, stats = pr.asRGBA()
	
	
	# generate color palette and load RGB info into Python lists
	#	for later processing
	palette = {}
	map = []
	
	for vec in imap:
		temp = []
		for i in range(0, len(vec), 4):
			rgb = tuple(vec[i:(i+3)].tolist())
			alpha = vec[i+3]
			if alpha == 0:
				# full transparency, assign as white
				rgb = (255, 255, 255)
			
			temp.extend(rgb)
			if palette.has_key(rgb):
				palette[rgb]['total_count'] += 1
			else:
				palette.update ( { rgb : {'dmc':find_colour(rgb), 'total_count':1, 'page_count':0} } )
				
		map.append(temp)
	
	
	# sort palette for generating legends
	sorted_palette = []
	done_dmc = []
	for v in palette.itervalues():
		if v['dmc'] not in done_dmc:
			try:
				sorted_palette.append([int(v['dmc'][1]), v['dmc'], v['total_count']])
			except:
				sorted_palette.append([v['dmc'][1], v['dmc'], v['total_count']])
			done_dmc.append(v['dmc'])
	
	sorted_palette.sort(reverse=False)
	
	
	# find nearest neighbours in colour space
	threshold = 85
	values = [v['dmc'] for v in palette.itervalues()]
	network = [[0 for i in range(len(palette))] for j in range(len(palette))]
	
	for node1 in range(len(values)):
		for node2 in range(len(values)):
			if node1 == node2:
				continue
			# calculate Euclidean distance in RGB colour space
			if math.sqrt(sum( [ (values[node1][0][k] - values[node2][0][k])**2 for k in range(3) ] )) < threshold:
				network[node1][node2] = 1
	
	
	
	#	black and white should be reserved (no annotation)
	#   by deleting entry in degrees list
	reserved = []
	for i in range(len(values)):
		rgb, dmc_code, dmc_name = values[i]
		if rgb in [ (0, 0, 0), (255, 255, 255) ]:
			for col in range(len(network)):
				network[i][col] = 0
	
	# calculate network degree of each colour
	degrees = [ sum(row) for row in network ]
	
	# iteratively mark colours for annotation, starting with
	#	highest network degree
	
	annotations = {}
	rank = 1
	while sum(degrees) > 0:
		max_degree = max(degrees)
		which_max = degrees.index(max_degree)	
		
		# mark node for annotation
		this_rgb = values[which_max][0]
		if not annotations.has_key(this_rgb):		
			annotations.update ( { this_rgb : rank } )
			rank += 1
			if rank > 15:
				rank = 1
		
		# update network
		network[which_max] = [0 for i in range(len(network))]
		for i in range(len(network)):
			network[i][which_max] = 0
		
		degrees = [ sum(row) for row in network ]
	
	
	# ncol is the x (horizontal)
	# nrow is the y (vertical)
	
	# dimensions of each 8.5" x 11" page output:
	#	792 x 612 pixels (landscape)
	#	32 pixel margin all around (0.5 inch)
	#	548 x 548 pixels square field for image + labels
	# 	180 x 548 pixels field for legend
	#	490 x 490 (?) square field for image
	
	# dimension of image field in number of sprite pixels 
	field_psize = 60
	field_size = field_psize * (block_size+gap_size)
	
	# how many pages?
	xfields = int(math.ceil(ncol / float(field_psize)))
	yfields = int(math.ceil(nrow / float(field_psize)))
	n_pages = xfields * yfields
	
	
	# start the PostScript output string
	ps = ''
	ps += '%%!PS\n<< /PageSize [%d %d] >> setpagedevice\n' % (page_width, page_height)
	ps += '/Helvetica findfont\n'
	ps += '%d scalefont\n' % font_size
	ps += 'setfont\n'
	
	# PostScript origin is at bottom-left corner of page by default
	# first entry in PyPNG list is top row of image
	
	page_num = 1
	
	for xf in range(xfields):
		# figure out the top-left corner of the current quadrant of the sprite
		xorig = xf * field_psize
		
		for yf in range(yfields):
			yorig = yf * field_psize
			
			# prepare a new page
			ps += '%d %d translate\n' % (margin+inset+10, margin+inset)
			
			# reset palette page counts
			for key in palette.iterkeys():
				palette[key]['page_count'] = 0
			
			
			for row in range(yorig, yorig + field_psize):
				try:
					vec = map[row]
				except IndexError:
					break
				except:
					raise
				
				my_top = (block_size+gap_size) * (field_psize - (row-yorig)) - gap10_size * ( (row-yorig)/10 )
				
				if (row-yorig)%10 == 9:
					ps += '0 0 0 setrgbcolor\n'
					ps += '-24 %d moveto\n' % my_top
					ps += '(%d) show\n' % (row+1)
				
				
				# loop over columns
				for col in range(xorig, xorig + field_psize):
					pixel = tuple (vec[(3*col):(3*col+3)])
					if not pixel:
						break
					
					
					rgb, dmc_code, dmc_name = palette[pixel]['dmc']
					palette[pixel]['page_count'] += 1
					
					
					my_left = (block_size+gap_size) * (col-xorig) + gap10_size * ( (col-xorig)/10 )
					
					if (col-xorig)%10 == 9 and row == yorig:
						ps += '0 0 0 setrgbcolor\n'
						ps += '%d %d moveto\n' % (my_left+8, field_size+12)
						ps += '90 rotate\n'
						ps += '(%d) show\n' % (col+1)
						ps += '-90 rotate\n'
					
					
					ps += '%f %f %f setrgbcolor\n' % (rgb[0]/255., rgb[1]/255., rgb[2]/255.)
					
					ps += 'newpath\n'
					ps += '%d %d moveto\n' % (my_left, my_top)
					ps += '%d %d lineto\n' % (my_left+block_size, my_top)
					ps += '%d %d lineto\n' % (my_left+block_size, my_top+block_size)
					ps += '%d %d lineto\n' % (my_left, my_top+block_size)
					ps += '%d %d lineto\n' % (my_left, my_top)
					
					# add grey outline around white block
					if rgb == (255, 255, 255):
						ps += 'gsave\n'
						ps += '0.8 0.8 0.8 setrgbcolor\n'
						ps += 'stroke\n'
						ps += 'grestore\n'
					
					ps += 'fill\n'
					
					
					if annotations.has_key(rgb):
						ps += add_accent (rgb, annotations[rgb], my_left, my_top, block_size)
					
				row += 1
			
			# add legend
			ps += '%d %d translate\n' % (page_width-legend_width-margin, 0)
			
			temp = [ (v['page_count'], v['dmc']) for v in palette.itervalues() if v['page_count'] > 0 ]
			temp.sort(reverse=False)
			
			i = 0
			done_rgb = []
			for page_count, dmc in temp:
				rgb, dmc_code, dmc_name = dmc
				
				# avoid duplicates
				if rgb in done_rgb:
					continue
				else:
					done_rgb.append(rgb)
				
				my_top = i
				my_left = 0
				
				ps += '%f %f %f setrgbcolor\n' % (rgb[0]/255., rgb[1]/255., rgb[2]/255.)
				ps += 'newpath\n'
				ps += '%d %d moveto\n' % (my_left, my_top)
				ps += '%d %d lineto\n' % (my_left+block_size, my_top)
				ps += '%d %d lineto\n' % (my_left+block_size, my_top+block_size)
				ps += '%d %d lineto\n' % (my_left, my_top+block_size)
				ps += '%d %d lineto\n' % (my_left, my_top)
				ps += 'fill\n'
				
				if annotations.has_key(rgb):
					ps += add_accent (rgb, annotations[rgb], my_left, my_top, block_size)
				
				
				ps += '0 0 0 setrgbcolor\n'
				#ps += '%d %d moveto\n' % (my_left + 150, my_top)
				#ps += '(%d) show\n' % (v['total_count'])
				#ps += '%d %d moveto\n' % (my_left + 180, my_top)
				#ps += '(%d) show\n' % (v['page_count'])
				ps += 'newpath\n'
				ps += '%d %d moveto\n' % (block_size+gap_size, i)
				ps += '( '+dmc_code+' '+dmc_name+') show\n'
				i += font_size + 2
				
			
			ps += '%d %d moveto\n' % (block_size+gap_size, i)
			ps += '(DMC # and Name) show\n'
			#ps += '%d %d moveto\n' % (150, i)
			#ps += '(Total) show\n'
			#ps += '%d %d moveto\n' % (180, i)
			#ps += '(Page) show\n'
			
			ps += '%d %d moveto\n' % (block_size+gap_size, i+font_size+2+6)
			ps += '(Created by Spriter v'+_version+' beta) show\n'
			
			ps += '%d %d moveto\n' % (block_size+gap_size, i+2*(font_size+2)+6)
			ps += '(Page %d of %d) show\n' % (page_num, n_pages)
			
			# output the current page
			ps += 'showpage\n'
			page_num += 1
	
	
	# =====================================
	# do final legend page
	# add legend
	ps += '%d %d translate\n' % (54, page_height-54)
	ps += '/Helvetica findfont\n'
	ps += '12 scalefont\n'
	ps += 'setfont\n'
	
	# how many columns should we draw?
	# let's have 27 colours per columns
	colours_per_column = 24
	max_num_colours = len(palette)
	num_columns = int(math.ceil(float(max_num_colours) / colours_per_column))
	if num_columns > 3:
		num_columns = 3
	column_width = 240
	
	for nc in range(num_columns):
		ps += '%d %d moveto\n' % (nc * column_width + block_size+gap_size, 0)
		ps += '(DMC # and Name) show\n'
		ps += '%d %d moveto\n' % (nc * column_width + 180, 0)
		ps += '(Count) show\n'
	
	
	done_rgb = []
	for discard, dmc, total_count in sorted_palette:
		rgb, dmc_code, dmc_name = dmc
		# avoid duplicates
		if rgb in done_rgb:
			continue
		
		# which row and column are we in?
		legend_column = (len(done_rgb)%72) / colours_per_column
		legend_row = (len(done_rgb)%72) % colours_per_column
		
		# print dmc_name + ' %d %d' % (legend_column, legend_row)
		
		my_top = (-1) * legend_row * 16 - 16
		my_left = legend_column * column_width
		
		ps += '%f %f %f setrgbcolor\n' % (rgb[0]/255., rgb[1]/255., rgb[2]/255.)
		ps += 'newpath\n'
		ps += '%d %d moveto\n' % (my_left, my_top)
		ps += '%d %d lineto\n' % (my_left+block_size, my_top)
		ps += '%d %d lineto\n' % (my_left+block_size, my_top+block_size)
		ps += '%d %d lineto\n' % (my_left, my_top+block_size)
		ps += '%d %d lineto\n' % (my_left, my_top)
		ps += 'fill\n'
		
		if annotations.has_key(rgb):
			ps += add_accent (rgb, annotations[rgb], my_left, my_top, block_size)
		
		
		ps += '0 0 0 setrgbcolor\n'
		ps += '%d %d moveto\n' % (my_left + 180, my_top)
		ps += '(%d) show\n' % total_count
		#ps += '%d %d moveto\n' % (my_left + 180, my_top)
		#ps += '(%d) show\n' % (v['page_count'])
		ps += 'newpath\n'
		ps += '%d %d moveto\n' % (my_left + block_size + gap_size, my_top)
		ps += '( '+dmc_code+' '+dmc_name+') show\n'
		
		done_rgb.append(rgb)
		
		if len(done_rgb) > 0 and len(done_rgb) % 72 == 0:
			ps += '0 -420 moveto\n'
			ps += '(Spriter v'+_version+' beta) show\n'
			ps += '0 -436 moveto\n'
			ps += '(created by ScienceDad) show\n'
			ps += '0 -452 moveto\n'
			ps += '(beta testing by Lord Libidan) show\n'
			ps += '24 -480 moveto\n'
			ps += '(Spriter is written in Python and uses the open source library pypng, http://code.google.com/p/pypng/) show\n'
			ps += '24 -496 moveto\n'
			ps += '(DMC to RGB colour space values were obtained from http://www.xstitchtreasures.com/DMCFloss-RGBvalues.html) show\n'
			
			ps += 'showpage\n'
			
			ps += '%d %d translate\n' % (54, page_height-54)
			ps += '/Helvetica findfont\n'
			ps += '12 scalefont\n'
			ps += 'setfont\n'
			
			for nc in range(num_columns):
				ps += '%d %d moveto\n' % (nc * column_width + block_size+gap_size, 0)
				ps += '(DMC # and Name) show\n'
				ps += '%d %d moveto\n' % (nc * column_width + 180, 0)
				ps += '(Count) show\n'
	
	
	ps += '0 -420 moveto\n'
	ps += '(Spriter v'+_version+' beta) show\n'
	ps += '0 -436 moveto\n'
	ps += '(created by ScienceDad) show\n'
	ps += '0 -452 moveto\n'
	ps += '(beta testing by Lord Libidan) show\n'
	ps += '24 -480 moveto\n'
	ps += '(Spriter is written in Python and uses the open source library pypng, http://code.google.com/p/pypng/) show\n'
	ps += '24 -496 moveto\n'
	ps += '(DMC to RGB colour space values were obtained from http://www.xstitchtreasures.com/DMCFloss-RGBvalues.html) show\n'
	
	ps += 'showpage\n'
	
	
	# write PostScript to file
	outfile = open (outfilename, 'w')
	outfile.write(ps)
	outfile.close()
	
	subprocess.call(['open', outfilename])


# ===================================
if __name__ == "__main__":
	main()
pixelcraft, a free and open-source web app for making craft patterns from sprites:
http://www.theoryandwings.com/software/pixelcraft

Post Reply