<reponame>johnkerl/scripts-math
#!/usr/bin/python -Wall

# ================================================================
# <NAME>
# <EMAIL>
# 2011-03-06
# Makes a pseudo-starry-sky image suitable for wallpaper.
# ================================================================

from __future__ import division
import sys, Image
import random

# ----------------------------------------------------------------
# Tweakable parameters
density = 0.01
radius_CDF = [[0.9, 1], [0.98, 2], [1.0, 3]]
spread = 40

# ----------------------------------------------------------------
def main():
	# My laptop monitor size:
	#width = 1366
	#height = 768

	# My laptop monitor size times 120%.  Then I can shrink the image
	# inside GIMP to get a little softer look.
	width = 1640
	height = 922

	argc = len(sys.argv)
	if argc == 2:
		file_name = sys.argv[1]
	elif argc == 4:
		width  = int(sys.argv[1])
		height = int(sys.argv[2])
		file_name = sys.argv[3]
	else:
		usage()

	im = Image.new('RGB', (width, height), 0)

	num_stars = int(density * width * height)
	for k in xrange(0, num_stars):
		i = random.randint(0, width-1)
		j = random.randint(0, height-1)
		putrandstar(im, width, height, i, j)

	im.save(file_name)

# ----------------------------------------------------------------
def usage():
	print >> sys.stderr, \
		"Usage: %s {width in pixels} {height in pixels} {output file name}"
	sys.exit(1)

# ----------------------------------------------------------------
# 1. Keep r/g/b close to one another, & vary brightness more.
# 2. Take random radius & bleed over into adjacent pixels

def putrandstar(im, width, height, i, j):
	r = random.randint(0, 255)
	g = random.randint(max(r-spread,0), min(r+spread,255))
	b = random.randint(max(r-spread,0), min(r+spread,255))

	radius = random_selection_from_CDF(radius_CDF)

	# How to handle various radii:  treat the star as a circular disk, with
	# brightness uniform across the disk.  So, for each pixel in a 1x1, 2x2, or
	# 3x3 grid, count the fraction of the pixel that is covered by a circle.
	# (Place star centers at pixel centers for convenience.)

	# Eyeball estimates:
	# o 1x1: middle about 3/4 covered.
	# o 2x2: center 100% covered.  up/down/left/right about 45% covered.
	#        corners about 15% covered.
	# o 3x3: center 100% covered.  up/down/left/right about 90% covered.
	#        corners about 60% covered.

	if radius == 1:
		im.putpixel((i,j), scale_rgb(r,g,b,0.75))
	elif radius == 2:

		clipped_putpixel(im, width, height, i,   j,   scale_rgb(r,g,b,1.00))

		clipped_putpixel(im, width, height, i+1, j,   scale_rgb(r,g,b,0.45))
		clipped_putpixel(im, width, height, i-1, j,   scale_rgb(r,g,b,0.45))
		clipped_putpixel(im, width, height, i,   j+1, scale_rgb(r,g,b,0.45))
		clipped_putpixel(im, width, height, i,   j-1, scale_rgb(r,g,b,0.45))

		clipped_putpixel(im, width, height, i+1, j+1, scale_rgb(r,g,b,0.15))
		clipped_putpixel(im, width, height, i+1, j-1, scale_rgb(r,g,b,0.15))
		clipped_putpixel(im, width, height, i-1, j+1, scale_rgb(r,g,b,0.15))
		clipped_putpixel(im, width, height, i-1, j-1, scale_rgb(r,g,b,0.15))

	elif radius == 3:

		clipped_putpixel(im, width, height, i,   j,   scale_rgb(r,g,b,1.00))

		clipped_putpixel(im, width, height, i+1, j,   scale_rgb(r,g,b,0.90))
		clipped_putpixel(im, width, height, i-1, j,   scale_rgb(r,g,b,0.90))
		clipped_putpixel(im, width, height, i,   j+1, scale_rgb(r,g,b,0.90))
		clipped_putpixel(im, width, height, i,   j-1, scale_rgb(r,g,b,0.90))

		clipped_putpixel(im, width, height, i+1, j+1, scale_rgb(r,g,b,0.40))
		clipped_putpixel(im, width, height, i+1, j-1, scale_rgb(r,g,b,0.40))
		clipped_putpixel(im, width, height, i-1, j+1, scale_rgb(r,g,b,0.40))
		clipped_putpixel(im, width, height, i-1, j-1, scale_rgb(r,g,b,0.40))

# ----------------------------------------------------------------
def random_selection_from_CDF(CDF):
	U = random.uniform(0.0, 1.0)
	for [C, value] in CDF:
		if U < C:
			return value
	print >> sys.stderr, 'random_selection_from_CDF b0rk'
	sys.exit(1)

# ----------------------------------------------------------------
def scale_rgb(r,g,b,frac):
	return (int(frac*r), int(frac*g), int(frac*b))

# ----------------------------------------------------------------
def clipped_putpixel(im, width, height, i, j, rgb):
	if i < 0:
		return
	if i >= width:
		return
	if j < 0:
		return
	if j >= height:
		return
	im.putpixel((i,j), rgb)

# ================================================================
main()
