diff --git a/cuburn/code/filtering.py b/cuburn/code/filtering.py index 905bc59..6c0ab72 100644 --- a/cuburn/code/filtering.py +++ b/cuburn/code/filtering.py @@ -11,7 +11,7 @@ _CODE = ''' #include __global__ -void colorclip(float4 *pixbuf, float gamma, float vibrancy, float highpow, +void colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow, float linrange, float lingam, float3 bkgd, int fbsize, int blend_background_color) { int i = threadIdx.x + blockDim.x * (blockIdx.x + gridDim.x * blockIdx.y); @@ -42,7 +42,7 @@ void colorclip(float4 *pixbuf, float gamma, float vibrancy, float highpow, return; } - float ls = vibrancy * alpha / pix.w; + float ls = vibrance * alpha / pix.w; alpha = fminf(1.0f, fmaxf(0.0f, alpha)); float maxc = fmaxf(pix.x, fmaxf(pix.y, pix.z)); @@ -71,9 +71,9 @@ void colorclip(float4 *pixbuf, float gamma, float vibrancy, float highpow, } } - pix.x += (1.0f - vibrancy) * powf(opix.x, gamma); - pix.y += (1.0f - vibrancy) * powf(opix.y, gamma); - pix.z += (1.0f - vibrancy) * powf(opix.z, gamma); + pix.x += (1.0f - vibrance) * powf(opix.x, gamma); + pix.y += (1.0f - vibrance) * powf(opix.y, gamma); + pix.z += (1.0f - vibrance) * powf(opix.z, gamma); pix.x += (1.0f - alpha) * bkgd.x; pix.y += (1.0f - alpha) * bkgd.y; @@ -318,7 +318,7 @@ class Filtering(object): # TODO: implement integration over cubic splines? gam = f32(1 / gnm.color.gamma(tc)) - vib = f32(gnm.color.vibrancy(tc)) + vib = f32(gnm.color.vibrance(tc)) hipow = f32(gnm.color.highlight_power(tc)) lin = f32(gnm.color.gamma_threshold(tc)) lingam = f32(lin ** (gam-1.0) if lin > 0 else 0) diff --git a/cuburn/code/primes.bin b/cuburn/code/primes.bin new file mode 100644 index 0000000..88c86cb Binary files /dev/null and b/cuburn/code/primes.bin differ diff --git a/cuburn/code/util.py b/cuburn/code/util.py index 30bf4bb..5fc97f6 100644 --- a/cuburn/code/util.py +++ b/cuburn/code/util.py @@ -7,6 +7,8 @@ import tempita def crep(s): """Escape for PTX assembly""" + if isinstance(s, unicode): + s = s.encode('utf-8') return '"%s"' % s.encode("string_escape") class Template(tempita.Template): diff --git a/cuburn/genome.py b/cuburn/genome.py index 2d9e1de..d8811fe 100644 --- a/cuburn/genome.py +++ b/cuburn/genome.py @@ -1,7 +1,8 @@ -import json import base64 import numpy as np +from code.util import crep + class SplEval(object): _mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1], [1,-1, 0, 0], [-2, 3, 0, 0]]) @@ -80,14 +81,12 @@ class SplEval(object): return list(self.knots.T.flat) class Palette(object): - """Wafer-thin wrapper around palettes. For the future!""" - def __init__(self, datastr, fmt='rgb8'): - if fmt != 'rgb8': + def __init__(self, datastrs): + if datastrs[0] != 'rgb8': raise NotImplementedError - if len(datastr) != 768: - raise ValueError("Unsupported palette width") self.width = 256 - pal = np.reshape(np.fromstring(datastr, np.uint8), (256, 3)) + raw = base64.b64decode(''.join(datastrs[1:])) + pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3)) self.data = np.ones((256, 4), np.float32) self.data[:,:3] = pal / 255.0 @@ -106,36 +105,61 @@ class _AttrDict(dict): return dct class Genome(_AttrDict): + """ + Load a genome description, wrapping all data structures in _AttrDicts, + converting lists of numbers to splines, and deriving some values. Derived + values are stored as instance properties, rather than replacing the + original values, such that JSON-encoding this structure should always + print a valid genome. + """ # For now, we base the Genome class on an _AttrDict, letting its structure - # be defined implicitly by the way it is used in device code. More formal - # unpacking will happen soon. - def __init__(self, gnm, base_den): + # be defined implicitly by the way it is used in device code, except for + # these derived properties. + def __init__(self, gnm): super(Genome, self).__init__(gnm) for k, v in self.items(): + if not isinstance(v, dict): + continue v = _AttrDict(v) + # These two properties must be handled separately if k not in ('info', 'time'): _AttrDict._wrap(v) self[k] = v - # TODO: this is a hack, figure out how to solve it more elegantly - self.spp = SplEval(self.camera.density.knotlist) - self.spp.knots[1] *= base_den - # TODO: decide how to handle palettes. For now, it's the caller's - # responsibility to replace this list with actual palettes. - pal = self.color.palette + + self.decoded_palettes = map(Palette, self.palettes) + print self.color + pal = self.color.palette_times if isinstance(pal, basestring): - self.color.palette = [(0.0, pal), (1.0, pal)] - elif isinstance(pal, list): - self.color.palette = zip(pal[::2], pal[1::2]) + self.palette_times = [(0.0, int(pal)), (1.0, int(pal))] + else: + self.palette_times = zip(pal[::2], map(int, pal[1::2])) - # TODO: caller also needs to call set_timing() - self.adj_frame_width = None - self.canonical_right = (not self.get('link') or not self.link == 'self' - or not self.link.get('right')) + self.adj_frame_width, self.spp = None, None + self.canonical_right = not (self.get('link') and + (self.link == 'self' or self.link.get('right'))) - def set_timing(self, base_dur, fps, offset=0.0, err_spread=True): + def set_profile(self, prof, offset=0.0, err_spread=True): """ - Set frame timing. Must be called at least once prior to rendering. + Sets the instance props which are dependent on a profile. Also + calculates timing information, which is returned instead of being + attached to the genome. May be called multiple times to set different + options. + + ``prof`` is a profile dictionary. ``offset`` is the time in seconds + that the first frame's effective presentation time should be offset + from the natural presentation time. ``err_spread`` will spread the + rounding error in this frame across all frames, such that PTS+(1/FPS) + is exactly equal to the requested duration. + + Returns ``(err, times)``, where ``err`` is the rounding error in + seconds (taking ``offset`` into account), and ``times`` is a list of + the central time of each frame in the animation in relative-time + coordinates. Also sets the ``spp`` and ``adj_frame_width`` properties. """ + self.spp = SplEval(self.camera.density.knotlist) + self.spp.knots[1] *= prof['quality'] + fps, base_dur = prof['fps'], prof['duration'] + # TODO: test! dur = self.time.duration if isinstance(dur, basestring): @@ -162,3 +186,43 @@ class Genome(_AttrDict): epts = np.linspace(-2*np.pi, 2*np.pi, nframes) times = times + 0.5 * err * (np.tanh(epts) + 1) return err, times + +def json_encode_genome(obj, indent=0): + """ + Encode an object into JSON notation. This serializer only works on the + subset of JSON used in genomes. + """ + # TODO: test, like so many other things + isnum = lambda v: isinstance(v, (float, int, np.number)) + + def wrap(pairs, delims): + do, dc = delims + i = ' ' * indent + out = ''.join([do, ', '.join(pairs), dc]) + if '\n' not in out and len(out) + indent < 70: + return out + return ''.join(['\n', i, do, ' ', ('\n'+i+', ').join(pairs), + '\n', i, dc]) + + if isinstance(obj, dict): + if not obj: + return '{}' + ks, vs = zip(*sorted(obj.items())) + if ks == ('b', 'g', 'r'): + ks, vs = reversed(ks), reversed(vs) + ks = [crep('%.8g' % k if isnum(k) else str(k)) for k in ks] + vs = [json_encode_genome(v, indent+2) for v in vs] + return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}') + elif isinstance(obj, list): + vs = [json_encode_genome(v, indent+2) for v in obj] + if vs and len(vs) % 2 == 0 and isnum(obj[0]): + vs = map(', '.join, zip(vs[::2], vs[1::2])) + return wrap(vs, '[]') + elif isinstance(obj, SplEval): + return json_encode_genome(obj.knotlist, indent) + elif isinstance(obj, basestring): + return crep(obj) + elif isnum(obj): + return '%.8g' % obj + raise TypeError("Don't know how to serialize %s of type %s" % + (obj, type(obj))) diff --git a/cuburn/render.py b/cuburn/render.py index 8b4f2bb..6a7644b 100644 --- a/cuburn/render.py +++ b/cuburn/render.py @@ -250,15 +250,13 @@ class Renderer(object): info_size = 4 * len(self._iter.packer) * ntemporal_samples d_infos = cuda.mem_alloc(info_size) - pals = genome.color.palette - if isinstance(pals, basestring): - pals = [0.0, pals, 1.0, pals] + ptimes, pidxs = zip(*genome.palette_times) palint_times = np.empty(len(genome_times[0]), f32) - palint_times.fill(100.0) - palint_times[:len(pals)] = [p[0] for p in pals] + palint_times.fill(1e10) + palint_times[:len(ptimes)] = ptimes d_palint_times = cuda.to_device(palint_times) - d_palint_vals = cuda.to_device( - np.concatenate([p[1].data for p in pals])) + pvals = [genome.decoded_palettes[i].data for i in pidxs] + d_palint_vals = cuda.to_device(np.concatenate(pvals)) if self.acc_mode in ('deferred', 'atomic'): palette_fun = self.mod.get_function("interp_palette_hsv_flat")