# MiniLight Python : minimal global illumination renderer # # Copyright (c) 2007-2008, Harrison Ainsworth / HXA7241 and Juraj Sukop. # http://www.hxa7241.org/ from math import log10 from vector3f import Vector3f PPM_ID = 'P6' MINILIGHT_URI = 'http://www.hxa7241.org/minilight/' DISPLAY_LUMINANCE_MAX = 200.0 RGB_LUMINANCE = Vector3f(0.2126, 0.7152, 0.0722) GAMMA_ENCODE = 0.45 class Image(object): def __init__(self, in_stream): for line in in_stream: if not line.isspace(): self.width, self.height = map(lambda dimension: min(max(1, int(dimension)), 10000), line.split()) self.pixels = [0.0] * self.width * self.height * 3 break def add_to_pixel(self, x, y, radiance): if x >= 0 and x < self.width and y >= 0 and y < self.height: index = (x + ((self.height - 1 - y) * self.width)) * 3 for a in radiance: self.pixels[index] += a index += 1 def get_formatted(self, out, iteration): divider = 1.0 / ((iteration if iteration > 0 else 0) + 1) tonemap_scaling = self.calculate_tone_mapping(self.pixels, divider) out.write('%s\n# %s\n\n%u %u\n255\n' % (PPM_ID, MINILIGHT_URI, self.width, self.height)) for channel in self.pixels: mapped = channel * divider * tonemap_scaling gammaed = (mapped if mapped > 0.0 else 0.0) ** GAMMA_ENCODE out.write(chr(min(int((gammaed * 255.0) + 0.5), 255))) def calculate_tone_mapping(self, pixels, divider): sum_of_logs = 0.0 for i in range(len(pixels) / 3): y = Vector3f(pixels[i * 3: i * 3 + 3]).dot(RGB_LUMINANCE) * divider sum_of_logs += log10(y if y > 1e-4 else 1e-4) log_mean_luminance = 10.0 ** (sum_of_logs / (len(pixels) / 3)) a = 1.219 + (DISPLAY_LUMINANCE_MAX * 0.25) ** 0.4 b = 1.219 + log_mean_luminance ** 0.4 return ((a / b) ** 2.5) / DISPLAY_LUMINANCE_MAX