From b53f703e6e7d9b0673c57296c17fe526ff45d383 Mon Sep 17 00:00:00 2001 From: Steven Robertson Date: Tue, 10 Apr 2012 08:44:25 -0700 Subject: [PATCH] Checkpoint! Renders again. Many fixes outstanding. --- cuburn/code/filters.py | 8 +- cuburn/code/interp.py | 190 +++++++------- cuburn/code/iter.py | 178 +++++++------ cuburn/code/variations.py | 499 +++++++++++++++++------------------- cuburn/filters.py | 63 ++--- cuburn/genome.py | 473 ---------------------------------- cuburn/genome/__init__.py | 0 cuburn/genome/blend.py | 288 +++++++++++++++++++++ cuburn/genome/convert.py | 182 +++++++++++++ cuburn/genome/schema.py | 82 ++++++ cuburn/genome/spec.py | 112 ++++++++ cuburn/genome/spectypes.py | 45 ++++ cuburn/genome/use.py | 188 ++++++++++++++ cuburn/genome/util.py | 122 +++++++++ cuburn/genome/variations.py | 127 +++++++++ cuburn/render.py | 61 +++-- main.py | 20 +- 17 files changed, 1646 insertions(+), 992 deletions(-) delete mode 100644 cuburn/genome.py create mode 100644 cuburn/genome/__init__.py create mode 100644 cuburn/genome/blend.py create mode 100644 cuburn/genome/convert.py create mode 100644 cuburn/genome/schema.py create mode 100644 cuburn/genome/spec.py create mode 100644 cuburn/genome/spectypes.py create mode 100644 cuburn/genome/use.py create mode 100644 cuburn/genome/util.py create mode 100644 cuburn/genome/variations.py diff --git a/cuburn/code/filters.py b/cuburn/code/filters.py index 74067af..4de14e2 100644 --- a/cuburn/code/filters.py +++ b/cuburn/code/filters.py @@ -260,13 +260,13 @@ haloclip(float4 *pixbuf, const float *denbuf, float gamma) { colorcliplib = devlib(deps=[yuvlib], defs=r''' __global__ void colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow, - float linrange, float lingam, float3 bkgd) + float linrange, float lingam) { GET_IDX(i); float4 pix = pixbuf[i]; if (pix.w <= 0) { - pixbuf[i] = make_float4(bkgd.x, bkgd.y, bkgd.z, 0.0f); + pixbuf[i] = make_float4(0, 0, 0, 0); return; } pix.y -= 0.5f * pix.w; @@ -321,10 +321,6 @@ colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow, 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; - pix.z += (1.0f - alpha) * bkgd.z; - pix.x = fminf(1.0f, pix.x); pix.y = fminf(1.0f, pix.y); pix.z = fminf(1.0f, pix.z); diff --git a/cuburn/code/interp.py b/cuburn/code/interp.py index c3fca6d..e868cdd 100644 --- a/cuburn/code/interp.py +++ b/cuburn/code/interp.py @@ -2,16 +2,14 @@ from collections import OrderedDict from itertools import cycle import numpy as np +from cuburn.genome.use import Wrapper, SplineEval + import util from util import Template, assemble_code, devlib, binsearchlib, ringbuflib from color import yuvlib from mwc import mwclib -class GenomePackerName(str): - """Class to indicate that a property is precalculated on the device""" - pass - -class GenomePackerView(object): +class PackerWrapper(Wrapper): """ Obtain accessors in generated code. @@ -25,47 +23,46 @@ class GenomePackerView(object): code and an interpolator for use in generating that code. This conversion is done when the property is coerced into a string by the templating mechanism, so you can easily nest objects by saying, for instance, - {{pcp.camera.rotation}} from within templated code. The accessed property - must be a SplEval object, or a precalculated value (see - ``GenomePackerPrecalc``). - - Index operations are converted to property accesses as well, so that you - don't have to make a mess with 'getattr' in your code: {{pcp.xforms[x]}} - works just fine. This means, however, that no arrays can be packed - directly; they must be converted to have string-based keys first, and - any loops must be unrolled in your code. + {{pcp.camera.rotation}} from within templated code. """ - def __init__(self, packer, ptr_name, wrapped, prefix=()): - self.packer = packer - self.ptr_name = ptr_name - self.wrapped = wrapped - self.prefix = prefix + def __init__(self, packer, val, spec=None, path=()): + super(PackerWrapper, self).__init__(val, spec) + self.packer, self.path = packer, path + + def wrap_dict(self, path, spec, val): + return type(self)(self.packer, val, spec, path) + + def wrap_spline(self, path, spec, val): + return PackerSpline(self.packer, path, spec) + def __getattr__(self, name): - w = getattr(self.wrapped, name) - return type(self)(self.packer, self.ptr_name, w, self.prefix+(name,)) - # As with the Genome class, we're all-dict, no-array here - __getitem__ = lambda s, n: getattr(s, str(n)) + path = self.path + (name,) + if path in self.packer.packed_precalc: + return self.packer.devname(path) + return super(PackerWrapper, self).__getattr__(name) + + def _precalc(self): + """Create a GenomePackerPrecalc object. See that class for details.""" + return PrecalcWrapper(self.packer, self._val, self.spec, self.path) + +class PackerSpline(object): + def __init__(self, packer, path, spec): + self.packer, self.path, self.spec = packer, path, spec def __str__(self): """ Returns the packed name in a format suitable for embedding directly into device code. """ - # So evil. When the template calls __str__ to format the output, we - # allocate things. This makes for neater embedded code, which is where - # the real complexity lies, but it also means printf() debugging when - # templating will screw with the allocation tables! - if not isinstance(self.wrapped, GenomePackerName): - self.packer._require(self.prefix) - # TODO: verify namespace stomping, etc - return '%s.%s' % (self.ptr_name, '_'.join(self.prefix)) + # When the template calls __str__ to format one of these splines, this + # allocates the corresponding spline. + return self.packer._require(self.spec, self.path) - def _precalc(self): - """Create a GenomePackerPrecalc object. See that class for details.""" - return GenomePackerPrecalc(self.packer, self.ptr_name, - self.wrapped, self.prefix) +class PrecalcSpline(PackerSpline): + def __str__(self): + return self.packer._require_pre(self.spec, self.path) -class GenomePackerPrecalc(GenomePackerView): +class PrecalcWrapper(PackerWrapper): """ Insert precalculated values into the packed genome. @@ -91,35 +88,24 @@ class GenomePackerPrecalc(GenomePackerView): Example: - def do_precalc(px): - pcam = px._precalc() + def do_precalc(pcam): pcam._code(Template(''' {{pcam._set('prop_sin')}} = sin({{pcam.prop}}); ''').substitute(pcam=pcam)) def gen_code(px): return Template(''' - {{do_precalc(px)}} + {{do_precalc(px._precalc())}} printf("The sin of %g is %g.", {{px.prop}}, {{px.prop_sin}}); ''').substitute(px=px) """ - def __init__(self, packer, ptr_name, wrapped, prefix): - super(GenomePackerPrecalc, self).__init__(packer, 'out', wrapped, prefix) - def __str__(self): - return self.packer._require_pre(self.prefix) - def _magscale(self): - """ - This is a temporary hack which turns on magnitude scaling for the - value on which it is called. Takes the place of __str__ serialization. - """ - return self.packer._require_pre(self.prefix, True) + def wrap_spline(self, path, spec, val): + return PrecalcSpline(self.packer, path, spec) + def _set(self, name): - fullname = self.prefix + (name,) - self.packer._pre_alloc(fullname) - # This just modifies the underlying object, because I'm too lazy right - # now to ghost the namespace - self.wrapped[name] = GenomePackerName('_'.join(fullname)) - return '%s->%s' % (self.ptr_name, self.wrapped[name]) + path = self.path + (name,) + return self.packer._pre_alloc(path) + def _code(self, code): self.packer.precalc_code.append(code) @@ -127,26 +113,27 @@ class GenomePacker(object): """ Packs a genome for use in iteration. """ - def __init__(self, tname): + def __init__(self, tname, ptr_name, spec): """ Create a new DataPacker. ``tname`` is the name of the structure typedef that will be emitted via this object's ``decls`` property. """ - self.tname = tname + self.tname, self.ptr_name, self.spec = tname, ptr_name, spec # We could do this in the order that things are requested, but we want # to be able to treat the direct stuff as a list so this function # doesn't unroll any more than it has to. So we separate things into # direct requests, and those that need precalculation. # Values of OrderedDict are unused; basically, it's just OrderedSet. self.packed_direct = OrderedDict() + # Feel kind of bad about this, but it's just under the threshold of + # being worth refactoring to be agnostic to interpolation types + self.packed_direct_mag = OrderedDict() self.genome_precalc = OrderedDict() self.packed_precalc = OrderedDict() self.precalc_code = [] - self.ns = {} - self._len = None self.decls = None self.defs = None @@ -156,29 +143,37 @@ class GenomePacker(object): self.search_rounds = util.DEFAULT_SEARCH_ROUNDS def __len__(self): + """Length in elements. (*4 for length in bytes.)""" assert self._len is not None, 'len() called before finalize()' return self._len - def view(self, ptr_name, wrapped_obj, prefix): + def view(self, val={}): """Create a DataPacker view. See DataPackerView class for details.""" - self.ns[prefix] = wrapped_obj - return GenomePackerView(self, ptr_name, wrapped_obj, (prefix,)) + return PackerWrapper(self, val, self.spec) - def _require(self, name): + def _require(self, spec, path): """ Called to indicate that the named parameter from the original genome must be available during interpolation. """ - self.packed_direct[name] = None + if spec.interp == 'mag': + self.packed_direct_mag[path] = None + else: + self.packed_direct[path] = None + return self.devname(path) - def _require_pre(self, name, mag_scaling=False): + def _require_pre(self, spec, path): i = len(self.genome_precalc) << self.search_rounds - self.genome_precalc[name] = None - name = 'catmull_rom_mag' if mag_scaling else 'catmull_rom' - return '%s(×[%d], &knots[%d], time)' % (name, i, i) + self.genome_precalc[path] = None + func = 'catmull_rom_mag' if spec.interp == 'mag' else 'catmull_rom' + return '%s(×[%d], &knots[%d], time)' % (func, i, i) - def _pre_alloc(self, name): - self.packed_precalc[name] = None + def _pre_alloc(self, path): + self.packed_precalc[path] = None + return '%s->%s' % (self.ptr_name, '_'.join(path)) + + def devname(self, path): + return '%s.%s' % (self.ptr_name, '_'.join(path)) def finalize(self): """ @@ -187,20 +182,18 @@ class GenomePacker(object): # At the risk of packing a few things more than once, we don't # uniquify the overall precalc order, sparing us the need to implement # recursive code generation - self.packed = self.packed_direct.keys() + self.packed_precalc.keys() - self.genome = self.packed_direct.keys() + self.genome_precalc.keys() + direct = self.packed_direct.keys() + self.packed_direct_mag.keys() + self.packed = direct + self.packed_precalc.keys() + self.genome = direct + self.genome_precalc.keys() self._len = len(self.packed) - decls = self._decls.substitute(packed=self.packed, tname=self.tname) - defs = self._defs.substitute( - packed_direct=self.packed_direct, tname=self.tname, - precalc_code=self.precalc_code, - search_rounds=self.search_rounds) + decls = self._decls.substitute(**self.__dict__) + defs = self._defs.substitute(**self.__dict__) return devlib(deps=[catmullromlib], decls=decls, defs=defs) - def pack(self, pool=None): + def pack(self, gnm, pool=None): """ Return a packed copy of the genome ready for uploading to the GPU, as two float32 NDArrays for the knot times and values. @@ -213,50 +206,59 @@ class GenomePacker(object): times, knots = np.empty((2, len(self.genome), width), 'f4') times.fill(1e9) - for idx, gname in enumerate(self.genome): - attr = self.ns[gname[0]] - for g in gname[1:]: - attr = getattr(attr, g) - times[idx,:len(attr.knots[0])] = attr.knots[0] - knots[idx,:len(attr.knots[1])] = attr.knots[1] + for idx, path in enumerate(self.genome): + attr = gnm + for name in path: + attr = attr[name] + attr = SplineEval.normalize(attr) + times[idx,:len(attr[0])] = attr[0] + knots[idx,:len(attr[1])] = attr[1] return times, knots _defs = Template(r""" __global__ void interp_{{tname}}( - {{tname}}* out, + {{tname}}* {{ptr_name}}, const float *times, const float *knots, float tstart, float tstep, int maxid) { int id = gtid(); if (id >= maxid) return; - out = &out[id]; + {{ptr_name}} = &{{ptr_name}}[id]; float time = tstart + id * tstep; - float *outf = reinterpret_cast(out); + float *outf = reinterpret_cast({{ptr_name}}); + + {{py:lpd = len(packed_direct)}} + {{py:lpdm = len(packed_direct_mag)}} // TODO: unroll pragma? - for (int i = 0; i < {{len(packed_direct)}}; i++) { + for (int i = 0; i < {{lpd}}; i++) { int j = i << {{search_rounds}}; outf[i] = catmull_rom(×[j], &knots[j], time); } + for (int i = {{lpd}}; i < {{lpd+lpdm}}; i++) { + int j = i << {{search_rounds}}; + outf[i] = catmull_rom_mag(×[j], &knots[j], time); + } + // Advance 'times' and 'knots' to the purely generated sections, so that // the pregenerated statements emitted by _require_pre are correct. - times = ×[{{len(packed_direct)<head, blockDim.x * threadIdx.y + threadIdx.x); mwc_st rctx = msts[this_rb_idx]; - {{precalc_camera(pcp.camera)}} + {{precalc_camera(cp.camera._precalc())}} if (threadIdx.y == 5 && threadIdx.x == 4) { - float ditherwidth = {{pcp.camera.dither_width}} * 0.5f; - {{pcp.camera.xo}} += ditherwidth * mwc_next_11(rctx); - {{pcp.camera.yo}} += ditherwidth * mwc_next_11(rctx); + if (blockIdx.x == 0) + printf("Hiya %f\n", {{cp.camera.xx}}); + float ditherwidth = {{cp.camera.dither_width}} * 0.5f; + {{cp.camera.xo}} += ditherwidth * mwc_next_11(rctx); + {{cp.camera.yo}} += ditherwidth * mwc_next_11(rctx); } + // TODO: spare the register, reuse at call site? int time = blockIdx.x >> 4; float color_dither = 0.49f * mwc_next_11(rctx); @@ -229,24 +228,26 @@ iter(uint64_t out_ptr, uint64_t atom_ptr, color = mwc_next_01(rctx); } + +{{py:xk = cp.xforms.keys()}} {{if chaos_used}} - {{precalc_chaos(pcp, std_xforms)}} + {{precalc_chaos(cp)}} // For now, we don't attempt to use the swap buffer when chaos is used float xfsel = mwc_next_01(rctx); - {{for prior_xform_idx, prior_xform_name in enumerate(std_xforms)}} + {{for prior_xform_idx, prior_xform_name in enumerate(xk)}} if (last_xf_used == {{prior_xform_idx}}) { - {{for xform_idx, xform_name in enumerate(std_xforms[:-1])}} - if (xfsel <= {{pcp['chaos_'+prior_xform_name+'_'+xform_name]}}) { + {{for xform_idx, xform_name in enumerate(xk[:-1])}} + if (xfsel <= {{cp['chaos_'+prior_xform_name+'_'+xform_name]}}) { apply_xf_{{xform_name}}(x, y, color, rctx); last_xf_used = {{xform_idx}}; } else {{endfor}} { - apply_xf_{{std_xforms[-1]}}(x, y, color, rctx); - last_xf_used = {{len(std_xforms)-1}}; + apply_xf_{{xk[-1]}}(x, y, color, rctx); + last_xf_used = {{len(xk)-1}}; } } else {{endfor}} @@ -256,18 +257,18 @@ iter(uint64_t out_ptr, uint64_t atom_ptr, } {{else}} - {{precalc_densities(pcp, std_xforms)}} + {{precalc_densities(cp._precalc())}} float xfsel = cosel[threadIdx.y]; - {{for xform_idx, xform_name in enumerate(std_xforms[:-1])}} - if (xfsel <= {{pcp['den_'+xform_name]}}) { + {{for xform_idx, xform_name in enumerate(xk[:-1])}} + if (xfsel <= {{cp['den_'+xform_name]}}) { apply_xf_{{xform_name}}(x, y, color, rctx); last_xf_used = {{xform_idx}}; } else {{endfor}} { - apply_xf_{{std_xforms[-1]}}(x, y, color, rctx); - last_xf_used = {{len(std_xforms)-1}}; + apply_xf_{{xk[-1]}}(x, y, color, rctx); + last_xf_used = {{len(xk)-1}}; } // Rotate points between threads. @@ -298,18 +299,14 @@ iter(uint64_t out_ptr, uint64_t atom_ptr, continue; } -{{if 'final' in cp.xforms}} + float cx, cy, cc; +{{if 'final_xform' in cp}} float fx = x, fy = y, fcolor = color; apply_xf_final(fx, fy, fcolor, rctx); -{{endif}} - - float cx, cy, cc; - -{{if 'final' in cp.xforms}} - {{apply_affine('fx', 'fy', 'cx', 'cy', pcp.camera)}} + {{apply_affine('fx fy cx cy', cp.camera)}} cc = fcolor; {{else}} - {{apply_affine('x', 'y', 'cx', 'cy', pcp.camera)}} + {{apply_affine('x y cx cy', cp.camera)}} cc = color; {{endif}} @@ -407,11 +404,9 @@ oflow_end: } ''' -def iter_body(cp, pcp): - # For legacy reasons, 'cp' is used here instead of 'genome'. +def iter_body(cp): tmpl = Template(iter_body_code, 'iter_body') NWARPS = NTHREADS / 32 - std_xforms = [n for n in sorted(cp.xforms) if n != 'final'] # TODO: detect this properly and use it chaos_used = False @@ -420,12 +415,15 @@ def iter_body(cp, pcp): vars.update(locals()) return tmpl.substitute(vars) -def mkiterlib(genome): - packer = interp.GenomePacker('iter_params') - pcp = packer.view('params', genome, 'cp') +def mkiterlib(gnm): + packer = interp.GenomePacker('iter_params', 'params', + cuburn.genome.spec.anim) + cp = packer.view(gnm) - iterbody = iter_body(genome, pcp) - bodies = [iter_xf_body(pcp, i, x) for i, x in sorted(genome.xforms.items())] + iterbody = iter_body(cp) + bodies = [iter_xf_body(cp, i, x) for i, x in sorted(cp.xforms.items())] + if 'final_xform' in cp: + bodies.append(iter_xf_body(cp, 'final', cp.final_xform)) bodies.append(iterbody) packer_lib = packer.finalize() diff --git a/cuburn/code/variations.py b/cuburn/code/variations.py index bdd5d40..65f907d 100644 --- a/cuburn/code/variations.py +++ b/cuburn/code/variations.py @@ -3,116 +3,102 @@ import numpy as np from util import Template var_code = {} -var_params = {} -def var(num, name, code, params=None): - var_code[name] = Template(code, name) - if params is not None: - r = {} - for p in params.split(): - if '=' in p: - p, default = p.split('=') - if default == 'M_PI': - default = np.pi - else: - default = float(default) - else: - default = 0.0 - r[p] = default - var_params[name] = r - -# TODO: This is a shitty hack -def precalc(name, code): - def precalc_fun(pv, px=None): - pre = pv._precalc() - prex = px._precalc() if px is not None else None - pre._code(Template(code, name+'_precalc').substitute(**locals())) - Template.default_namespace[name+'_precalc'] = precalc_fun +def var(name, code, precalc=None): + precalc_fun = None + if precalc: + def precalc_fun(pv, px): + pv, px = pv._precalc(), px._precalc() + tmpl = Template(precalc, name+'_precalc').substitute(pv=pv, px=px) + pv._code(tmpl) + code = "\n {{precalc_fun(pv, px)}}" + code + var_code[name] = Template(code, name, + namespace=dict(precalc_fun=precalc_fun)) # Variables note: all functions will have their weights as 'w', # input variables 'tx' and 'ty', and output 'ox' and 'oy' available # from the calling context. Each statement will be placed inside brackets, # to avoid namespace pollution. -var(0, 'linear', """ +var('linear', """ ox += tx * w; oy += ty * w; - """) +""") -var(1, 'sinusoidal', """ +var('sinusoidal', """ ox += w * sinf(tx); oy += w * sinf(ty); - """) +""") -var(2, 'spherical', """ +var('spherical', """ float r2 = w / (tx*tx + ty*ty); ox += tx * r2; oy += ty * r2; - """) +""") -var(3, 'swirl', """ +var('swirl', """ float r2 = tx*tx + ty*ty; float c1 = sinf(r2); float c2 = cosf(r2); ox += w * (c1*tx - c2*ty); oy += w * (c2*tx + c1*ty); - """) +""") -var(4, 'horseshoe', """ +var('horseshoe', """ float r = w / sqrtf(tx*tx + ty*ty); ox += r * (tx - ty) * (tx + ty); oy += 2.0f * tx * ty * r; - """) +""") -var(5, 'polar', """ +var('polar', """ ox += w * atan2f(tx, ty) * M_1_PI; oy += w * (sqrtf(tx * tx + ty * ty) - 1.0f); - """) +""") -var(6, 'handkerchief', """ +var('handkerchief', """ float a = atan2f(tx, ty); float r = sqrtf(tx*tx + ty*ty); ox += w * r * sinf(a+r); oy += w * r * cosf(a-r); - """) +""") -var(7, 'heart', """ +var('heart', """ float sq = sqrtf(tx*tx + ty*ty); float a = sq * atan2f(tx, ty); float r = w * sq; ox += r * sinf(a); oy -= r * cosf(a); - """) +""") -var(8, 'disc', """ +var('disc', """ float a = w * atan2f(tx, ty) * M_1_PI; float r = M_PI * sqrtf(tx*tx + ty*ty); ox += sinf(r) * a; oy += cosf(r) * a; - """) +""") -var(9, 'spiral', """ +var('spiral', """ float a = atan2f(tx, ty); float r = sqrtf(tx*tx + ty*ty); float r1 = w / r; ox += r1 * (cosf(a) + sinf(r)); oy += r1 * (sinf(a) - cosf(r)); - """) +""") -var(10, 'hyperbolic', """ +var('hyperbolic', """ float a = atan2f(tx, ty); float r = sqrtf(tx*tx + ty*ty); ox += w * sinf(a) / r; oy += w * cosf(a) * r; - """) +""") -var(11, 'diamond', """ +var('diamond', """ float a = atan2f(tx, ty); float r = sqrtf(tx*tx + ty*ty); ox += w * sinf(a) * cosf(r); oy += w * cosf(a) * sinf(r); - """) +""") -var(12, 'ex', """ +var('ex', """ float a = atan2f(tx, ty); float r = sqrtf(tx*tx + ty*ty); float n0 = sinf(a+r); @@ -121,119 +107,119 @@ var(12, 'ex', """ float m1 = n1*n1*n1*r; ox += w * (m0 + m1); oy += w * (m0 - m1); - """) +""") -var(13, 'julia', """ +var('julia', """ float a = 0.5f * atan2f(tx, ty); if (mwc_next(rctx) & 1) a += M_PI; float r = w * sqrtf(sqrtf(tx*tx + ty*ty)); // TODO: fastest? ox += r * cosf(a); oy += r * sinf(a); - """) +""") -var(14, 'bent', """ +var('bent', """ float nx = 1.0f; if (tx < 0.0f) nx = 2.0f; float ny = 1.0f; if (ty < 0.0f) ny = 0.5f; ox += w * nx * tx; oy += w * ny * ty; - """) +""") -precalc('waves', """ - float dx = {{prex.affine.offset.x}}; - float dy = {{prex.affine.offset.y}}; - {{pre._set('dx2')}} = 1.0f / (dx * dx + 1.0e-20f); - {{pre._set('dy2')}} = 1.0f / (dy * dy + 1.0e-20f); - """) - -var(15, 'waves', """ - {{waves_precalc(pv, px)}} - float c10 = {{px.affine.xy}}; - float c11 = {{px.affine.yy}}; +var('waves', """ + float c10 = {{px.pre_affine.xy}}; + float c11 = {{px.pre_affine.yy}}; ox += w * (tx + c10 * sinf(ty * {{pv.dx2}})); oy += w * (ty + c11 * sinf(tx * {{pv.dy2}})); - """) +""", """ + float dx = {{px.pre_affine.offset.x}}; + float dy = {{px.pre_affine.offset.y}}; + {{pv._set('dx2')}} = 1.0f / (dx * dx + 1.0e-20f); + {{pv._set('dy2')}} = 1.0f / (dy * dy + 1.0e-20f); +""") -var(16, 'fisheye', """ +var('fisheye', """ float r = sqrtf(tx*tx + ty*ty); r = 2.0f * w / (r + 1.0f); ox += r * ty; oy += r * tx; - """) +""") -var(17, 'popcorn', """ +var('popcorn', """ float dx = tanf(3.0f*ty); float dy = tanf(3.0f*tx); - ox += w * (tx + {{px.affine.xo}} * sinf(dx)); - oy += w * (ty + {{px.affine.yo}} * sinf(dy)); - """) + ox += w * (tx + {{px.pre_affine.xo}} * sinf(dx)); + oy += w * (ty + {{px.pre_affine.yo}} * sinf(dy)); +""") -var(18, 'exponential', """ +var('exponential', """ float dx = w * expf(tx - 1.0f); if (isfinite(dx)) { float dy = M_PI * ty; ox += dx * cosf(dy); oy += dx * sinf(dy); } - """) +""") -var(19, 'power', """ +var('power', """ float a = atan2f(tx, ty); float sa = sinf(a); float r = w * powf(sqrtf(tx*tx + ty*ty),sa); ox += r * cosf(a); oy += r * sa; - """) +""") -var(20, 'cosine', """ +var('cosine', """ float a = M_PI * tx; ox += w * cosf(a) * coshf(ty); oy -= w * sinf(a) * sinhf(ty); - """) +""") -var(21, 'rings', """ - float dx = {{px.affine.xo}} * {{px.affine.xo}}; +var('rings', """ + float dx = {{px.pre_affine.xo}}; + dx *= dx; float r = sqrtf(tx*tx + ty*ty); float a = atan2f(tx, ty); r = w * (fmodf(r+dx, 2.0f*dx) - dx + r * (1.0f - dx)); ox += r * cosf(a); oy += r * sinf(a); - """) +""") -var(22, 'fan', """ - float dx = M_PI * ({{px.affine.xo}} * {{px.affine.xo}}); +var('fan', """ + float dx = {{px.pre_affine.xo}}; + dx *= dx * M_PI; float dx2 = 0.5f * dx; - float dy = {{px.affine.yo}}; + float dy = {{px.pre_affine.yo}}; float a = atan2f(tx, ty); a += (fmodf(a+dy, dx) > dx2) ? -dx2 : dx2; float r = w * sqrtf(tx*tx + ty*ty); ox += r * cosf(a); oy += r * sinf(a); - """) +""") -var(23, 'blob', """ +var('blob', """ float r = sqrtf(tx*tx + ty*ty); float a = atan2f(tx, ty); float bdiff = 0.5f * ({{pv.high}} - {{pv.low}}); r *= w * ({{pv.low}} + bdiff * (1.0f + sinf({{pv.waves}} * a))); ox += sinf(a) * r; oy += cosf(a) * r; - """, 'low high=1 waves=1') +""") -var(24, 'pdj', """ +var('pdj', """ float nx1 = cosf({{pv.b}} * tx); float nx2 = sinf({{pv.c}} * tx); float ny1 = sinf({{pv.a}} * ty); float ny2 = cosf({{pv.d}} * ty); ox += w * (ny1 - nx1); oy += w * (nx2 - ny2); - """, 'a b c d') +""") -var(25, 'fan2', """ +var('fan2', """ float dy = {{pv.y}}; - float dx = M_PI * {{pv.x}} * {{pv.x}}; + float dx = {{pv.x}}; + dx *= dx * M_PI; float dx2 = 0.5f * dx; float a = atan2f(tx, ty); float r = w * sqrtf(tx*tx + ty*ty); @@ -245,62 +231,55 @@ var(25, 'fan2', """ ox += r * sinf(a); oy += r * cosf(a); - """, 'x y') +""") -var(26, 'rings2', """ - float dx = {{pv.val}} * {{pv.val}}; +var('rings2', """ + float dx = {{pv.val}}; + dx *= dx; float r = sqrtf(tx*tx + ty*ty); float a = atan2f(tx, ty); r += -2.0f * dx * (int)((r+dx)/(2.0f*dx)) + r * (1.0f - dx); ox += w * sinf(a) * r; oy += w * cosf(a) * r; - """, 'val') +""") -var(27, 'eyefish', """ +var('eyefish', """ float r = 2.0f * w / (sqrtf(tx*tx + ty*ty) + 1.0f); ox += r * tx; oy += r * ty; - """) +""") -var(28, 'bubble', """ +var('bubble', """ float r = w / (0.25f * (tx*tx + ty*ty) + 1.0f); ox += r * tx; oy += r * ty; - """) +""") -var(29, 'cylinder', """ +var('cylinder', """ ox += w * sinf(tx); oy += w * ty; - """) - -precalc('perspective', """ - float pang = {{pre.angle}} * M_PI_2; - float pdist = fmaxf(1e-9, {{pre.dist}}); - {{pre._set('mdist')}} = pdist; - {{pre._set('sin')}} = sin(pang); - {{pre._set('cos')}} = pdist * cos(pang); - """) - -var(30, 'perspective', """ - {{perspective_precalc(pv)}} +""") +var('perspective', """ float t = 1.0f / ({{pv.mdist}} - ty * {{pv.sin}}); ox += w * {{pv.mdist}} * tx * t; oy += w * {{pv.cos}} * ty * t; - """, 'angle dist') +""", """ + float pang = {{pv.angle}} * M_PI_2; + float pdist = fmaxf(1e-9, {{pv.dist}}); + {{pv._set('mdist')}} = pdist; + {{pv._set('sin')}} = sin(pang); + {{pv._set('cos')}} = pdist * cos(pang); +""") -var(31, 'noise', """ +var('noise', """ float tmpr = mwc_next_01(rctx) * 2.0f * M_PI; float r = w * mwc_next_01(rctx); ox += tx * r * cosf(tmpr); oy += ty * r * sinf(tmpr); - """) +""") -precalc('julian', - "{{pre._set('cn')}} = {{pre.dist}} / (2.0f * {{pre.power}});\n") - -var(32, 'julian', """ - {{julian_precalc(pv)}} +var('julian', """ float power = {{pv.power}}; float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power)); float a = atan2f(ty, tx); @@ -310,14 +289,11 @@ var(32, 'julian', """ ox += r * cosf(tmpr); oy += r * sinf(tmpr); - """, 'power=1 dist=1') - -precalc('juliascope', - "{{pre._set('cn')}} = {{pre.dist}} / (2.0f * {{pre.power}});\n") - -var(33, 'juliascope', """ - {{juliascope_precalc(pv)}} +""", """ + {{pv._set('cn')}} = {{pv.dist}} / (2.0f * {{pv.power}}); +""") +var('juliascope', """ float ang = atan2f(ty, tx); float power = {{pv.power}}; float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power)); @@ -328,16 +304,18 @@ var(33, 'juliascope', """ ox += r * cosf(tmpr); oy += r * sinf(tmpr); - """, 'power=1 dist=1') +""", """ + {{pv._set('cn')}} = {{pv.dist}} / (2.0f * {{pv.power}}); +""") -var(34, 'blur', """ +var('blur', """ float tmpr = mwc_next_01(rctx) * 2.0f * M_PI; float r = w * mwc_next_01(rctx); ox += r * cosf(tmpr); oy += r * sinf(tmpr); - """) +""") -var(35, 'gaussian_blur', """ +var('gaussian_blur', """ float ang = mwc_next_01(rctx) * 2.0f * M_PI; // constant factor here is stdev correction for converting to Box-Muller; // np.std(np.sum(np.random.random((1<<30, 4)), axis=1) - 2) @@ -345,9 +323,9 @@ var(35, 'gaussian_blur', """ float r = w * 0.57736 * sqrtf(-2.0f * log2f(mwc_next_01(rctx)) / M_LOG2E); ox += r * cosf(ang); oy += r * sinf(ang); - """) +""") -var(36, 'radial_blur', """ +var('radial_blur', """ float blur_angle = {{pv.angle}} * M_PI * 0.5f; float spinvar = sinf(blur_angle); float zoomvar = cosf(blur_angle); @@ -358,9 +336,9 @@ var(36, 'radial_blur', """ float rz = zoomvar * r - 1.0f; ox += ra*cosf(tmpa) + rz*tx; oy += ra*sinf(tmpa) + rz*ty; - """, 'angle') +""") -var(37, 'pie', """ +var('pie', """ float slices = {{pv.slices}}; float sl = truncf(mwc_next_01(rctx) * slices + 0.5f); float a = {{pv.rotation}} + @@ -368,9 +346,9 @@ var(37, 'pie', """ float r = w * mwc_next_01(rctx); ox += r * cosf(a); oy += r * sinf(a); - """, 'slices=6 rotation thickness=0.5') +""") -var(38, 'ngon', """ +var('ngon', """ float power = {{pv.power}} * 0.5f; float b = 2.0f * M_PI / {{pv.sides}}; float corners = {{pv.corners}}; @@ -384,9 +362,9 @@ var(38, 'ngon', """ ox += w * tx * amp; oy += w * ty * amp; - """, 'sides=5 power=3 circle=1 corners=2') +""") -var(39, 'curl', """ +var('curl', """ float c1 = {{pv.c1}}; float c2 = {{pv.c2}}; @@ -396,48 +374,48 @@ var(39, 'curl', """ ox += r * (tx*re + ty*im); oy += r * (ty*re - tx*im); - """, 'c1=1 c2') +""") -var(40, 'rectangles', """ +var('rectangles', """ float rx = {{pv.x}}; float ry = {{pv.y}}; ox += w * ( (rx==0.0f) ? tx : rx * (2.0f * floorf(tx/rx) + 1.0f) - tx); oy += w * ( (ry==0.0f) ? ty : ry * (2.0f * floorf(ty/ry) + 1.0f) - ty); - """, 'x y') +""") -var(41, 'arch', """ +var('arch', """ float ang = mwc_next_01(rctx) * w * M_PI; ox += w * sinf(ang); oy += w * sinf(ang) * sinf(ang) / cosf(ang); - """) +""") -var(42, 'tangent', """ +var('tangent', """ ox += w * sinf(tx) / cosf(ty); oy += w * tanf(ty); - """) +""") -var(43, 'square', """ +var('square', """ ox += w * (mwc_next_01(rctx) - 0.5f); oy += w * (mwc_next_01(rctx) - 0.5f); - """) +""") -var(44, 'rays', """ +var('rays', """ float ang = w * mwc_next_01(rctx) * M_PI; float r = w / (tx*tx + ty*ty); float tanr = w * tanf(ang) * r; ox += tanr * cosf(tx); oy += tanr * sinf(ty); - """) +""") -var(45, 'blade', """ +var('blade', """ float r = mwc_next_01(rctx) * w * sqrtf(tx*tx + ty*ty); ox += w * tx * (cosf(r) + sinf(r)); oy += w * tx * (cosf(r) - sinf(r)); - """) +""") -var(46, 'secant2', """ +var('secant2', """ float r = w * sqrtf(tx*tx + ty*ty); float cr = cosf(r); float icr = 1.0f / cr; @@ -445,19 +423,19 @@ var(46, 'secant2', """ ox += w * tx; oy += w * icr; - """) +""") # var 47 is twintrian, has a call to badvalue in it -var(48, 'cross', """ +var('cross', """ float s = tx*tx - ty*ty; float r = w * sqrtf(1.0f / (s*s)); ox += r * tx; oy += r * ty; - """) +""") -var(49, 'disc2', """ +var('disc2', """ float twist = {{pv.twist}}; float rotpi = {{pv.rot}} * M_PI; @@ -481,9 +459,9 @@ var(49, 'disc2', """ ox += r * (sinf(t) + costwist); oy += r * (cosf(t) + sintwist); - """, 'rot twist') +""") -var(50, 'super_shape', """ +var('super_shape', """ float ang = atan2f(ty, tx); float theta = 0.25f * ({{pv.m}} * ang + M_PI); float t1 = fabsf(cosf(theta)); @@ -498,9 +476,9 @@ var(50, 'super_shape', """ ox += r * tx; oy += r * ty; - """, 'rnd m n1=1 n2=1 n3=1 holes') +""") -var(51, 'flower', """ +var('flower', """ float holes = {{pv.holes}}; float petals = {{pv.petals}}; @@ -509,9 +487,9 @@ var(51, 'flower', """ ox += r * tx; oy += r * ty; - """, 'holes petals') +""") -var(52, 'conic', """ +var('conic', """ float d = sqrtf(tx*tx + ty*ty); float ct = tx / d; float holes = {{pv.holes}}; @@ -521,27 +499,27 @@ var(52, 'conic', """ ox += r * tx; oy += r * ty; - """, 'holes eccentricity=1') +""") -var(53, 'parabola', """ +var('parabola', """ float r = sqrtf(tx*tx + ty*ty); float sr = sinf(r); float cr = cosf(r); ox += {{pv.height}} * w * sr * sr * mwc_next_01(rctx); oy += {{pv.width}} * w * cr * mwc_next_01(rctx); - """, 'height width') +""") -var(54, 'bent2', """ +var('bent2', """ float nx = 1.0f; if (tx < 0.0f) nx = {{pv.x}}; float ny = 1.0f; if (ty < 0.0f) ny = {{pv.y}}; ox += w * nx * tx; oy += w * ny * ty; - """, 'x=1 y=1') +""") -var(55, 'bipolar', """ +var('bipolar', """ float x2y2 = tx*tx + ty*ty; float t = x2y2 + 1.0f; float x2 = tx * 2.0f; @@ -555,9 +533,9 @@ var(55, 'bipolar', """ ox += w * 0.25f * M_2_PI * logf( (t+x2) / (t-x2) ); oy += w * M_2_PI * y; - """, 'shift') +""") -var(56, 'boarders', """ +var('boarders', """ float roundX = rintf(tx); float roundY = rintf(ty); float offsetX = tx - roundX; @@ -585,18 +563,18 @@ var(56, 'boarders', """ } } } - """) +""") -var(57, 'butterfly', """ +var('butterfly', """ /* wx is weight*4/sqrt(3*pi) */ float wx = w * 1.3029400317411197908970256609023f; float y2 = ty * 2.0f; float r = wx * sqrtf(fabsf(ty * tx)/(tx*tx + y2*y2)); ox += r * tx; oy += r * y2; - """) +""") -var(58, 'cell', """ +var('cell', """ float cell_size = {{pv.size}}; float inv_cell_size = 1.0f/cell_size; @@ -629,37 +607,33 @@ var(58, 'cell', """ ox += w * (dx + x*cell_size); oy -= w * (dy + y*cell_size); - """, 'size=1') +""") -var(59, 'cpow', """ +var('cpow', """ float a = atan2f(ty, tx); float lnr = 0.5f * logf(tx*tx+ty*ty); - float power = {{pv.power}}; - float va = 2.0f * M_PI / power; - float vc = {{pv.cpow_r}} / power; - float vd = {{pv.cpow_i}} / power; + float power = 1.0f / {{pv.power}}; + float va = 2.0f * M_PI * power; + float vc = {{pv.r}} * power; + float vd = {{pv.i}} * power; float ang = vc*a + vd*lnr + va*floorf(power*mwc_next_01(rctx)); float m = w * expf(vc * lnr - vd * a); ox += m * cosf(ang); oy += m * sinf(ang); - """, 'r=1 i power=1') +""") - -precalc('curve', ''' - float xl = {{pv.xlength}}, yl = {{pv.ylength}}; - {{pre._set('x2')}} = 1.0f / max(1e-20f, xl * xl); - {{pre._set('y2')}} = 1.0f / max(1e-20f, yl * yl); - ''') - -var(60, 'curve', """ - {{curve_precalc()}} +var('curve', """ float pc_xlen = {{pv.x2}}, pc_ylen = {{pv.y2}}; ox += w * (tx + {{pv.xamp}} * expf(-ty*ty*pc_xlen)); oy += w * (ty + {{pv.yamp}} * expf(-tx*tx*pc_ylen)); - """, 'xamp yamp xlength=1 ylength=1') +""", """ + float xl = {{pv.xlength}}, yl = {{pv.ylength}}; + {{pv._set('x2')}} = 1.0f / max(1e-20f, xl * xl); + {{pv._set('y2')}} = 1.0f / max(1e-20f, yl * yl); +""") -var(61, 'edisc', """ +var('edisc', """ float tmp = tx*tx + ty*ty + 1.0f; float tmp2 = 2.0f * tx; float r1 = sqrtf(tmp+tmp2); @@ -675,9 +649,9 @@ var(61, 'edisc', """ ox += neww * coshf(a2) * csv; oy += neww * sinhf(a2) * snv; - """) +""") -var(62, 'elliptic', """ +var('elliptic', """ float tmp = tx*tx + ty*ty + 1.0f; float x2 = 2.0f * tx; float xmax = 0.5f * (sqrtf(tmp+x2) + sqrtf(tmp-x2)); @@ -702,9 +676,9 @@ var(62, 'elliptic', """ oy += neww * logf(xmax + ssx); else oy -= neww * logf(xmax + ssx); - """) +""") -var(63, 'escher', """ +var('escher', """ float a = atan2f(ty,tx); float lnr = 0.5f * logf(tx*tx + ty*ty); float ebeta = {{pv.beta}}; @@ -717,9 +691,9 @@ var(63, 'escher', """ ox += m * cosf(n); oy += m * sinf(n); - """, 'beta') +""") -var(64, 'foci', """ +var('foci', """ float expx = expf(tx) * 0.5f; float expnx = 0.25f / expx; float sn = sinf(ty); @@ -727,9 +701,9 @@ var(64, 'foci', """ float tmp = w / (expx + expnx - cn); ox += tmp * (expx - expnx); oy += tmp * sn; - """) +""") -var(65, 'lazysusan', """ +var('lazysusan', """ float lx = {{pv.x}}; float ly = {{pv.y}}; float x = tx - lx; @@ -748,9 +722,9 @@ var(65, 'lazysusan', """ ox += w * (r * x + lx); oy += w * (r * y - ly); } - """, 'x y twist space spin') +""") -var(66, 'loonie', """ +var('loonie', """ float r2 = tx*tx + ty*ty;; float w2 = w*w; @@ -762,9 +736,9 @@ var(66, 'loonie', """ ox += w * tx; oy += w * ty; } - """) +""") -var(67, 'pre_blur', """ +var('pre_blur', """ float rndG = w * (mwc_next_01(rctx) + mwc_next_01(rctx) + mwc_next_01(rctx) + mwc_next_01(rctx) - 2.0f); float rndA = mwc_next_01(rctx) * 2.0f * M_PI; @@ -772,9 +746,9 @@ var(67, 'pre_blur', """ /* Note: original coordinate changed */ tx += rndG * cosf(rndA); ty += rndG * sinf(rndA); - """) +""") -var(68, 'modulus', """ +var('modulus', """ float mx = {{pv.x}}, my = {{pv.y}}; float xr = 2.0f*mx; float yr = 2.0f*my; @@ -792,9 +766,9 @@ var(68, 'modulus', """ oy += w * ( my - fmodf(my - ty, yr)); else oy += w * ty; - """, 'x y') +""") -var(69, 'oscope', """ +var('oscope', """ float tpf = 2.0f * M_PI * {{pv.frequency}}; float amp = {{pv.amplitude}}; float sep = {{pv.separation}}; @@ -807,21 +781,21 @@ var(69, 'oscope', """ oy -= w*ty; else oy += w*ty; - """, 'separation=1 frequency=M_PI amplitude=1 damping') +""") -var(70, 'polar2', """ +var('polar2', """ float p2v = w / M_PI; ox += p2v * atan2f(tx,ty); oy += 0.5f * p2v * logf(tx*tx + ty*ty); - """) +""") -var(71, 'popcorn2', """ +var('popcorn2', """ float c = {{pv.c}}; ox += w * (tx + {{pv.x}} * sinf(tanf(ty*c))); oy += w * (ty + {{pv.y}} * sinf(tanf(tx*c))); - """, 'x y c') +""") -var(72, 'scry', """ +var('scry', """ /* note that scry does not multiply by weight, but as the */ /* values still approach 0 as the weight approaches 0, it */ /* should be ok */ @@ -829,9 +803,9 @@ var(72, 'scry', """ float r = 1.0f / (sqrtf(t) * (t + 1.0f/w)); ox += tx*r; oy += ty*r; - """) +""") -var(73, 'separation', """ +var('separation', """ float sx2 = {{pv.x}} * {{pv.x}}; float sy2 = {{pv.y}} * {{pv.y}}; @@ -844,9 +818,9 @@ var(73, 'separation', """ oy += w * (sqrtf(ty*ty + sy2) - ty*{{pv.yinside}}); else oy -= w * (sqrtf(ty*ty + sy2) + ty*{{pv.yinside}}); - """, 'x xinside y yinside') +""") -var(74, 'split', """ +var('split', """ if (cosf(tx*{{pv.xsize}}*M_PI) >= 0.0f) oy += w*ty; else @@ -856,21 +830,21 @@ var(74, 'split', """ ox += w*tx; else ox -= w*tx; - """, 'xsize ysize') +""") -var(75, 'splits', """ +var('splits', """ ox += w*(tx + copysignf({{pv.x}}, tx)); oy += w*(ty + copysignf({{pv.y}}, ty)); - """, 'x y') +""") -var(76, 'stripes', """ +var('stripes', """ float roundx = floorf(tx + 0.5f); float offsetx = tx - roundx; ox += w * (offsetx * (1.0f - {{pv.space}}) + roundx); oy += w * (ty + offsetx*offsetx*{{pv.warp}}); - """, 'space warp') +""") -var(77, 'wedge', """ +var('wedge', """ float r = sqrtf(tx*tx + ty*ty); float a = atan2f(ty, tx) + {{pv.swirl}} * r; float wc = {{pv.count}}; @@ -881,9 +855,9 @@ var(77, 'wedge', """ r = w * (r + {{pv.hole}}); ox += r * cosf(a); oy += r * sinf(a); - """, 'angle hole count=1 swirl') +""") -var(80, 'whorl', """ +var('whorl', """ float r = sqrtf(tx*tx + ty*ty); float a = atan2f(ty,tx); @@ -894,93 +868,93 @@ var(80, 'whorl', """ ox += w * r * cosf(a); oy += w * r * sinf(a); - """, 'inside outside') +""") -var(81, 'waves2', """ +var('waves2', """ ox += w*(tx + {{pv.scalex}}*sinf(ty * {{pv.freqx}})); oy += w*(ty + {{pv.scaley}}*sinf(tx * {{pv.freqy}})); - """, 'scalex scaley freqx freqy') +""") -var(82, 'exp', """ +var('exp', """ float expe = expf(tx); ox += w * expe * cosf(ty); oy += w * expe * sinf(ty); - """) +""") -var(83, 'log', """ +var('log', """ ox += w * 0.5f * logf(tx*tx + ty*ty); oy += w * atan2f(ty, tx); - """) +""") -var(84, 'sin', """ +var('sin', """ ox += w * sinf(tx) * coshf(ty); oy += w * cosf(tx) * sinhf(ty); - """) +""") -var(85, 'cos', """ +var('cos', """ ox += w * cosf(tx) * coshf(ty); oy -= w * sinf(tx) * sinhf(ty); - """) +""") -var(86, 'tan', """ +var('tan', """ float tanden = 1.0f/(cosf(2.0f*tx) + coshf(2.0f*ty)); ox += w * tanden * sinf(2.0f*tx); oy += w * tanden * sinhf(2.0f*ty); - """) +""") -var(87, 'sec', """ +var('sec', """ float secden = 2.0f/(cosf(2.0f*tx) + coshf(2.0f*ty)); ox += w * secden * cosf(tx) * coshf(ty); oy += w * secden * sinf(tx) * sinhf(ty); - """) +""") -var(88, 'csc', """ +var('csc', """ float cscden = 2.0f/(coshf(2.0f*ty) - cosf(2.0f*tx)); ox += w * cscden * sinf(tx) * coshf(ty); oy -= w * cscden * cosf(tx) * sinhf(ty); - """) +""") -var(89, 'cot', """ +var('cot', """ float cotden = 1.0f/(coshf(2.0f*ty) - cosf(2.0f*tx)); ox += w * cotden * sinf(2.0f*tx); oy += w * cotden * -1.0f * sinhf(2.0f*ty); - """) +""") -var(90, 'sinh', """ +var('sinh', """ ox += w * sinhf(tx) * cosf(ty); oy += w * coshf(tx) * sinf(ty); - """) +""") -var(91, 'cosh', """ +var('cosh', """ ox += w * coshf(tx) * cosf(ty); oy += w * sinhf(tx) * sinf(ty); - """) +""") -var(92, 'tanh', """ +var('tanh', """ float tanhden = 1.0f/(cosf(2.0f*ty) + coshf(2.0f*tx)); ox += w * tanhden * sinhf(2.0f*tx); oy += w * tanhden * sinf(2.0f*ty); - """) +""") -var(93, 'sech', """ +var('sech', """ float sechden = 2.0f/(cosf(2.0f*ty) + coshf(2.0f*tx)); ox += w * sechden * cosf(ty) * coshf(tx); oy -= w * sechden * sinf(ty) * sinhf(tx); - """) +""") -var(94, 'csch', """ +var('csch', """ float cschden = 2.0f/(coshf(2.0f*tx) - cosf(2.0f*ty)); ox += w * cschden * sinhf(tx) * cosf(ty); oy -= w * cschden * coshf(tx) * sinf(ty); - """) +""") -var(95, 'coth', """ +var('coth', """ float cothden = 1.0f/(coshf(2.0f*tx) - cosf(2.0f*ty)); ox += w * cothden * sinhf(2.0f*tx); oy += w * cothden * sinf(2.0f*ty); - """) +""") -var(97, 'flux', """ +var('flux', """ float xpw = tx + w; float xmw = tx - w; float avgr = w * (2.0f + {{pv.spread}}) @@ -988,9 +962,9 @@ var(97, 'flux', """ float avga = (atan2f(ty, xmw) - atan2f(ty,xpw))*0.5f; ox += avgr * cosf(avga); oy += avgr * sinf(avga); - """, 'spread') +""") -var(98, 'mobius', """ +var('mobius', """ float rea = {{pv.re_a}}; float ima = {{pv.im_a}}; float reb = {{pv.re_b}}; @@ -1011,5 +985,4 @@ var(98, 'mobius', """ ox += rad_v * (re_u*re_v + im_u*im_v); oy += rad_v * (im_u*re_v - re_u*im_v); - """, 're_a im_a re_b im_b re_c im_c re_d im_d') - +""") diff --git a/cuburn/filters.py b/cuburn/filters.py index 0cc99d1..ebb15e2 100644 --- a/cuburn/filters.py +++ b/cuburn/filters.py @@ -21,7 +21,7 @@ def mkdsc(dim, ch): format=cuda.array_format.FLOAT) class Filter(object): - def apply(self, fb, gnm, dim, tc, stream=None): + def apply(self, fb, gprof, params, dim, tc, stream=None): """ Queue the application of this filter. When the live stream finishes executing the last item enqueued by this method, the result must be @@ -32,15 +32,10 @@ class Filter(object): class Bilateral(Filter, ClsMod): lib = code.filters.bilaterallib - def __init__(self, directions=8, r=15, sstd=6, cstd=0.05, - dstd=1.5, dpow=0.8, gspeed=4.0): - # TODO: expose these parameters on the genome, or at least on the - # profile, and set them by a less ugly mechanism - for n in 'directions r sstd cstd dstd dpow gspeed'.split(): - setattr(self, n, locals()[n]) - super(Bilateral, self).__init__() + radius = 15 + directions = 8 - def apply(self, fb, gnm, dim, tc, stream=None): + def apply(self, fb, gprof, params, dim, tc, stream=None): # Helper variables and functions to keep it clean sb = 16 * dim.astride bs = sb * dim.ah @@ -53,7 +48,7 @@ class Bilateral(Filter, ClsMod): for pattern in range(self.directions): # Scale spatial parameter so that a "pixel" is equivalent to an # actual pixel at 1080p - sstd = self.sstd * dim.w / 1920. + sstd = params.spatial_std(tc) * dim.w / 1920. tref.set_address_2d(fb.d_front, dsc, sb) @@ -67,38 +62,39 @@ class Bilateral(Filter, ClsMod): grad_tref.set_address_2d(fb.d_side, grad_dsc, sb / 4) launch2('bilateral', self.mod, stream, dim, - fb.d_back, i32(pattern), i32(self.r), - f32(sstd), f32(self.cstd), f32(self.dstd), - f32(self.dpow), f32(self.gspeed), + fb.d_back, i32(pattern), i32(self.radius), + f32(sstd), f32(params.color_std(tc)), + f32(params.density_std(tc)), f32(params.density_pow(tc)), + f32(params.gradient(tc)), texrefs=[tref, grad_tref]) fb.flip() class Logscale(Filter, ClsMod): lib = code.filters.logscalelib - def apply(self, fb, gnm, dim, tc, stream=None): + def apply(self, fb, gprof, params, dim, tc, stream=None): """Log-scale in place.""" - k1 = f32(gnm.color.brightness(tc) * 268 / 256) + k1 = f32(params.brightness(tc) * 268 / 256) # Old definition of area is (w*h/(s*s)). Since new scale 'ns' is now # s/w, new definition is (w*h/(s*s*w*w)) = (h/(s*s*w)) - area = dim.h / (gnm.camera.scale(tc) ** 2 * dim.w) - k2 = f32(1.0 / (area * gnm.spp(tc))) + area = dim.h / (params.scale(tc) ** 2 * dim.w) + k2 = f32(1.0 / (area * gprof.spp(tc))) launch2('logscale', self.mod, stream, dim, fb.d_front, fb.d_front, k1, k2) class HaloClip(Filter, ClsMod): lib = code.filters.halocliplib - def apply(self, fb, gnm, dim, tc, stream=None): - gam = f32(1 / gnm.color.gamma(tc) - 1) + def apply(self, fb, gprof, params, dim, tc, stream=None): + gam = f32(1 / params.gamma(tc) - 1) dsc = mkdsc(dim, 1) tref = mktref(self.mod, 'chan1_src') launch2('apply_gamma', self.mod, stream, dim, fb.d_side, fb.d_front, gam) - tref.set_address_2d(fb.d_side, dsc, 4 * dim.astride) + tref.set_address_2d(fb.d_side, dsc, 4 * params.astride) launch2('den_blur_1c', self.mod, stream, dim, fb.d_back, i32(0), i32(0), texrefs=[tref]) - tref.set_address_2d(fb.d_back, dsc, 4 * dim.astride) + tref.set_address_2d(fb.d_back, dsc, 4 * params.astride) launch2('den_blur_1c', self.mod, stream, dim, fb.d_side, i32(1), i32(0), texrefs=[tref]) @@ -107,17 +103,22 @@ class HaloClip(Filter, ClsMod): class ColorClip(Filter, ClsMod): lib = code.filters.colorcliplib - def apply(self, fb, gnm, dim, tc, stream=None): + def apply(self, fb, gprof, params, dim, tc, stream=None): # TODO: implement integration over cubic splines? - gam = f32(1 / gnm.color.gamma(tc)) - vib = f32(gnm.color.vibrance(tc)) - hipow = f32(gnm.color.highlight_power(tc)) - lin = f32(gnm.color.gamma_threshold(tc)) + gam = f32(1 / params.gamma(tc)) + vib = f32(params.vibrance(tc)) + hipow = f32(params.highlight_power(tc)) + lin = f32(params.gamma_threshold(tc)) lingam = f32(lin ** (gam-1.0) if lin > 0 else 0) - bkgd = vec.make_float3( - gnm.color.background.r(tc), - gnm.color.background.g(tc), - gnm.color.background.b(tc)) launch2('colorclip', self.mod, stream, dim, - fb.d_front, gam, vib, hipow, lin, lingam, bkgd) + fb.d_front, gam, vib, hipow, lin, lingam) + +# Ungainly but practical. +filter_map = dict(bilateral=Bilateral, logscale=Logscale, haloclip=HaloClip, + colorclip=ColorClip) +def create(gprof): + # TODO: redesign this (should not have to care about internals of + # use.Wrapper in order to find types from TypedList elements) + filts = gprof._val.get('filters') or gprof.spec['filters'].defaults + return [filter_map[f['type']]() for f in filts] diff --git a/cuburn/genome.py b/cuburn/genome.py deleted file mode 100644 index 5839859..0000000 --- a/cuburn/genome.py +++ /dev/null @@ -1,473 +0,0 @@ -#!/usr/bin/env python2 - -import base64 -import warnings -import xml.parsers.expat -import numpy as np - -from code.variations import var_code, var_params -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]]) - _deriv = np.matrix(np.diag([3,2,1], 1)) - - def __init__(self, knots, v0=None, v1=None): - self.knots = self.normalize(knots, v0, v1) - - @staticmethod - def normalize(knots, v0=None, v1=None): - if isinstance(knots, (int, float)): - knots = [0.0, knots, 1.0, knots] - elif not np.all(np.diff(np.float32(np.asarray(knots))[::2]) > 0): - raise ValueError("Spline times are non-monotonic. (Use " - "nextafterf()-spaced times to anchor tangents.)") - - # If stabilizing knots are missing before or after the edges of the - # [0,1] interval, add them. - if knots[0] >= 0: - if v0 is None: - v0 = (knots[3] - knots[1]) / float(knots[2] - knots[0]) - knots = [-2, knots[3] - (knots[2] + 2) * v0] + knots - if knots[-2] <= 1: - if v1 is None: - v1 = (knots[-1] - knots[-3]) / float(knots[-2] - knots[-4]) - knots.extend([3, knots[-3] + (3 - knots[-4]) * v1]) - - knotarray = np.zeros((2, len(knots)/2)) - knotarray.T.flat[:] = knots - return knotarray - - def find_knots(self, itime): - idx = np.searchsorted(self.knots[0], itime) - 2 - idx = max(0, min(idx, len(self.knots[0]) - 4)) - - times = self.knots[0][idx:idx+4] - vals = self.knots[1][idx:idx+4] - # Normalize to [0,1] - t = itime - times[1] - times = times - times[1] - scale = 1 / times[2] - t = t * scale - times = times * scale - return times, vals, t, scale - - def __call__(self, itime, deriv=0): - times, vals, t, scale = self.find_knots(itime) - - m1 = (vals[2] - vals[0]) / (1.0 - times[0]) - m2 = (vals[3] - vals[1]) / times[3] - - mat = self._mat - if deriv: - mat = mat * (scale * self._deriv) ** deriv - val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T - return val[0,0] - - def _plt(self, name='SplEval', fig=111, show=True): - import matplotlib.pyplot as plt - x = np.linspace(-0.0, 1.0, 500) - r = x[1] - x[0] - plt.figure(fig) - plt.title(name) - plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--', - self.knots[0],self.knots[1],'x') - plt.xlim(0.0, 1.0) - if show: - plt.show() - - def __str__(self): - return '[%g:%g]' % (self(0), self(1)) - def __repr__(self): - return '' % (self(0), self(1)) - - @property - def knotlist(self): - # TODO: scale error constants proportional to RMS? - # If everything is constant, return a constant - if np.std(self.knots[1]) < 1e-6: - return self.knots[1][0] - # If constant slope, omit the end knots - slopes = np.diff(self.knots[1]) / np.diff(self.knots[0]) - if np.std(slopes) < 1e-6: - return list(self.knots.T.flat)[2:-2] - return list(self.knots.T.flat) - - def update(self, knots, overwrite=True): - """ - Update this spline's knotlist with ``knots``, a list of two-tuples - (time, value) or a dictionary of the same, while preserving the zeroth - and first derivatives at t=0 and t=1. - - If `overwrite` is True (the default), any knot values with precisely - the same float32 representation will be overwritten by the incoming - values. If not, a KeyError will be raised. Counting on this is not - recommended, due to the vagaries of floating-point representations, - but it works fine in a pinch. - - Endpoint-preservation is not guaranteed (or conversely, is guaranteed - not to work) if any time is passed outside of the exclusive range - (0,1). - """ - old = dict(self.knots.T[1:-1]) - new = dict(knots) - if not overwrite and set(old).intersection(set(new)): - raise KeyError("Conflicting spline times") - old.update(new) - knots = list(sum(sorted(old.items()), ())) - self.knots = self.normalize(knots, self(0, 1), self(1, 1)) - - def insert_knot(self, t, v): - self.update([(t, v)], True) - -def palette_decode(datastrs): - """ - Decode a palette (stored as a list suitable for JSON packing) into a - palette. Internal palette format is simply as a (256,4) array of [0,1] - RGBA floats. - """ - if datastrs[0] != 'rgb8': - raise NotImplementedError - raw = base64.b64decode(''.join(datastrs[1:])) - pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3)) - data = np.ones((256, 4), np.float32) - data[:,:3] = pal / 255.0 - return data - -def palette_encode(data, format='rgb8'): - """ - Encode an internal-format palette to an external representation. - """ - if format != 'rgb8': - raise NotImplementedError - clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0))) - enc = base64.b64encode(np.uint8(clamp)) - return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)] - -class _AttrDict(dict): - def __getattr__(self, name): - if name in self: - return self[name] - raise AttributeError('%s not a dict key' % name) - - @classmethod - def _wrap(cls, dct): - for k, v in dct.items(): - if (isinstance(v, (float, int)) or - (isinstance(v, list) and isinstance(v[1], (float, int)))): - dct[k] = SplEval(v) - elif isinstance(v, dict): - dct[k] = cls._wrap(cls(v)) - 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, 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 - - self.decoded_palettes = map(palette_decode, self.palettes) - pal = self.color.palette_times - if isinstance(pal, basestring): - self.palette_times = [(0.0, int(pal)), (1.0, int(pal))] - else: - self.palette_times = zip(pal[::2], map(int, pal[1::2])) - - self.adj_frame_width, self.spp = None, None - - def set_profile(self, prof, offset=0.0, err_spread=True): - """ - 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): - clock = float(dur[:-1]) + offset - else: - clock = dur * base_dur + offset - if (not isinstance(self.get('link'), dict) or - not self.link.get('right')): - warnings.warn("Genomes with missing or string-valued 'link' " - "properties are deprecated, and will be axed shortly.") - nframes = int(np.ceil(clock * fps)) - elif self.link.right == 'reference': - nframes = int(np.floor(clock * fps)) - else: - nframes = int(np.ceil(clock * fps)) - err = (clock - nframes / fps) / clock - - fw = self.time.frame_width - if not isinstance(fw, list): - fw = [0, fw, 1, fw] - fw = [float(f[:-1]) * fps if isinstance(f, basestring) - else float(f) / (clock * fps) for f in fw] - self.adj_frame_width = SplEval(fw) - - times = np.linspace(offset, 1 - err, nframes + 1) - # Move each time to a center time, and discard the last value - times = times[:-1] + 0.5 * (times[1] - times[0]) - if err_spread: - 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): - """ - Encode an object into JSON notation. This serializer only works on the - subset of JSON used in genomes. - """ - result = _js_enc_obj(obj).lstrip() - result = '\n'.join(l.rstrip() for l in result.split('\n')) - return result + '\n' - -def _js_enc_obj(obj, indent=0): - 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 '{}' - digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv - ks, vs = zip(*sorted(obj.items(), key=digsort)) - if ks == ('b', 'g', 'r'): - ks, vs = reversed(ks), reversed(vs) - ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks] - vs = [_js_enc_obj(v, indent+2) for v in vs] - return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}') - elif isinstance(obj, list): - vs = [_js_enc_obj(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 _js_enc_obj(obj.knotlist, indent) - elif isinstance(obj, basestring): - return crep(obj) - elif isnum(obj): - return '%.6g' % obj - raise TypeError("Don't know how to serialize %s of type %s" % - (obj, type(obj))) - -class XMLGenomeParser(object): - """ - Parse an XML genome into a list of dictionaries. - """ - def __init__(self): - self.flames = [] - self._flame = None - self.parser = xml.parsers.expat.ParserCreate() - self.parser.StartElementHandler = self.start_element - self.parser.EndElementHandler = self.end_element - - def start_element(self, name, attrs): - if name == 'flame': - assert self._flame is None - self._flame = dict(attrs) - self._flame['xforms'] = [] - self._flame['palette'] = np.ones((256, 4), dtype=np.float32) - elif name == 'xform': - self._flame['xforms'].append(dict(attrs)) - elif name == 'finalxform': - self._flame['finalxform'] = dict(attrs) - elif name == 'color': - idx = int(attrs['index']) - self._flame['palette'][idx][:3] = [float(v) / 255.0 - for v in attrs['rgb'].split()] - elif name == 'symmetry': - self._flame['symmetry'] = int(attrs['kind']) - def end_element(self, name): - if name == 'flame': - self.flames.append(self._flame) - self._flame = None - - @classmethod - def parse(cls, src): - parser = cls() - parser.parser.Parse(src, True) - return parser.flames - -def convert_flame(flame, arc=-360, offset=0): - """ - Convert an XML flame (as returned by XMLGenomeParser) into a plain dict - in cuburn's JSON genome format representing a loop edge. Caller is - responsible for correctly setting the 'link' dict. - """ - cvt = lambda ks: dict((k, float(flame[k])) for k in ks) - camera = { - 'center': dict(zip('xy', map(float, flame['center'].split()))), - 'scale': float(flame['scale']) / float(flame['size'].split()[0]), - 'dither_width': float(flame['filter']), - 'rotation': float(flame.get('rotate', 0)), - 'density': 1.0 - } - - info = {} - if 'name' in flame: - info['name'] = flame['name'] - if 'nick' in flame: - info['authors'] = [flame['nick']] - if flame.get('url'): - info['authors'][0] = info['authors'][0] + ', http://' + flame['url'] - - time = dict(frame_width=float(flame.get('temporal_filter_width', 1)), - duration=abs(arc)/360.) - - color = cvt(['brightness', 'gamma']) - color.update((k, float(flame.get(k, d))) for k, d in - [('highlight_power', -1), ('gamma_threshold', 0.01)]) - color['vibrance'] = float(flame.get('vibrancy', 1)) - color['background'] = dict(zip('rgb', - map(float, flame['background'].split()))) - color['palette_times'] = "0" - pal = palette_encode(flame['palette']) - - de = dict((k, float(flame.get(f, d))) for f, k, d in - [('estimator', 'radius', 11), - ('estimator_minimum', 'minimum', 0), - ('estimator_curve', 'curve', 0.6)]) - - num_xf = len(flame['xforms']) - xfs = dict([(str(k), convert_xform(v, num_xf, arc, offset)) - for k, v in enumerate(flame['xforms'])]) - if 'symmetry' in flame: - xfs.update(make_symm_xforms(flame['symmetry'], len(xfs))) - if 'finalxform' in flame: - xfs['final'] = convert_xform(flame['finalxform'], num_xf, - arc, offset, True) - return dict(camera=camera, color=color, de=de, xforms=xfs, - info=info, time=time, palettes=[pal], link='self') - -def convert_xform(xf, num_xf, arc, offset, isfinal=False): - # TODO: chaos - xf = dict(xf) - symm = float(xf.pop('symmetry', 0)) - anim = xf.pop('animate', symm <= 0) - out = dict((k, float(xf.pop(k, v))) for k, v in - dict(color=0, color_speed=(1-symm)/2, opacity=1).items()) - if not isfinal: - out['density'] = float(xf.pop('weight')) - out['affine'] = convert_affine(xf.pop('coefs'), arc, offset, anim) - if 'post' in xf and map(float, xf['post'].split()) != [1, 0, 0, 1, 0, 0]: - out['post'] = convert_affine(xf.pop('post'), arc, offset) - if 'chaos' in xf: - chaos = map(float, xf.pop('chaos').split()) - out['chaos'] = dict() - for i in range(num_xf): - if i < len(chaos): - out['chaos'][str(i)] = chaos[i] - else: - out['chaos'][str(i)] = 1.0 - - out['variations'] = {} - for k in var_code: - if k in xf: - var = dict(weight=float(xf.pop(k))) - for param, default in var_params.get(k, {}).items(): - var[param] = float(xf.pop('%s_%s' % (k, param), default)) - out['variations'][k] = var - assert not xf, 'Unrecognized parameters remain: ' + str(xf) - return out - -def convert_affine(aff, arc, offset, animate=False): - xx, yx, xy, yy, xo, yo = map(float, aff.split()) - # Invert all instances of y (yy is inverted twice) - yx, xy, yo = -yx, -xy, -yo - - xa = np.degrees(np.arctan2(yx, xx)) - ya = np.degrees(np.arctan2(yy, xy)) - xm = np.hypot(xx, yx) - ym = np.hypot(xy, yy) - - angle_between = ya - xa - if angle_between < 0: - angle_between += 360 - - if angle_between < 180: - spread = angle_between / 2.0 - else: - spread = -(360-angle_between) / 2.0 - - angle = xa + spread - if angle < 0: - angle += 360.0 - - if animate: - angle = [0, angle + offset, 1, angle + arc + offset] - - return dict(spread=spread, magnitude={'x': xm, 'y': ym}, - angle=angle, offset={'x': xo, 'y': yo}) - -def make_symm_xforms(kind, offset): - assert kind != 0, 'Pick your own damn symmetry.' - out = [] - boring_xf = dict(color=1, color_speed=0, density=1, opacity=1, - variations={'linear': {'weight': 1}}) - if kind < 0: - out.append(boring_xf.copy()) - out[-1]['affine'] = dict(angle=135, magnitude={'x': 1, 'y': 1}, - spread=-45, offset={'x': 0, 'y': 0}) - kind = -kind - for i in range(1, kind): - out.append(boring_xf.copy()) - if kind >= 3: - out[-1]['color'] = (i - 1) / (kind - 2.0) - ang = (45 + 360 * i / float(kind)) % 360 - out[-1]['affine'] = dict(angle=ang, magnitude={'x': 1, 'y': 1}, - spread=-45, offset={'x': 0, 'y': 0}) - return dict((str(i+offset), v) for i, v in enumerate(out)) - -def convert_file(path): - """Quick one-shot conversion for an XML genome.""" - flames = XMLGenomeParser.parse(open(path).read()) - if len(flames) > 10: - warnings.warn("Lot of flames in this file. Sure it's not a " - "frame-based animation?") - for flame in flames: - yield convert_flame(flame) - -if __name__ == "__main__": - import sys - print '\n\n'.join(map(json_encode_genome, convert_file(sys.argv[1]))) diff --git a/cuburn/genome/__init__.py b/cuburn/genome/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cuburn/genome/blend.py b/cuburn/genome/blend.py new file mode 100644 index 0000000..46671fc --- /dev/null +++ b/cuburn/genome/blend.py @@ -0,0 +1,288 @@ +#!/usr/bin/python + +# Copyright 2011-2012 Erik Reckase , +# Steven Robertson . + +import numpy as np +from copy import deepcopy +from itertools import izip_longest +from scipy.ndimage.filters import gaussian_filter1d + +import spectypes +import spec +import util +from util import get +import variations + +# TODO: move to better place before checkin +default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'} + + +def blend(src, dst, edit={}): + """ + Blend two nodes to produce an animation. + + ``src`` and ``dst`` are the source and destination node specs for the + animation. These should be plain node dicts (hierarchical, pre-merged, + and adjusted for loop temporal offset). + + ``edit`` is an optional edit dict, also hierarchical and pre-merged. + + Returns the animation spec as a plain dict. + """ + # By design, the blend element will contain only scalar values (no + # splines or hierarchy), so this can be done blindly + opts = dict(default_blend_opts) + for d in src, dst, edit: + opts.update(d.get('blend', {})) + + blended = merge_nodes(spec.node, src, dst, edit, opts['nloops']) + name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'], + explicit=zip(*opts.get('xform_map', []))) + + blended['xforms'] = {} + for (sxf_key, dxf_key) in name_map: + bxf_key = (sxf_key or 'pad') + '_' + (dxf_key or 'pad') + xf_edits = merge_edits(spec.xform, + get(edit, {}, 'xforms', 'src', sxf_key), + get(edit, {}, 'xforms', 'dst', dxf_key)) + blended['xforms'][bxf_key] = blend_xform( + src['xforms'].get(sxf_key), + dst['xforms'].get(dxf_key), + xf_edits, opts['nloops']) + + if 'final_xform' in src or 'final_xform' in dst: + blended['final_xform'] = blend_xform(src.get('final_xform'), + dst.get('final_xform'), edit.get('final_xform'), 0, True) + + # TODO: write 'info' section + # TODO: palflip + blended['type'] = 'animation' + blended.setdefault('time', {})['duration'] = opts['duration'] + return blended + +def merge_edits(sv, av, bv): + """ + Merge the values of ``av`` and ``bv`` according to the spec ``sv``. + """ + if isinstance(spec, (dict, spectypes.Map)): + av, bv = av or {}, bv or {} + getsv = lambda k: sv.type if isinstance(sv, spectypes.Map) else sv[k] + return dict([(k, merge_edits(getsv(k), av.get(k), bv.get(k))) + for k in set(av.keys() + bv.keys())]) + elif isinstance(sv, (spectypes.List, spectypes.Spline)): + return (av or []) + (bv or []) + else: + return bv if bv is not None else av + +def tospline(spl, src, dst, edit, loops): + def split_node_val(val): + if val is None: + return spl.default, 0 + if isinstance(val, (int, float)): + return val, 0 + return val + + sp, sv = split_node_val(src) # position, velocity + dp, dv = split_node_val(dst) + + # For variation parameters, copy missing values instead of using defaults + if spl.var: + if src is None: + sp = dp + if dst is None: + dp = sp + + edit = dict(zip(edit[::2], edit[1::2])) if edit else {} + e0, e1 = edit.pop(0, None), edit.pop(1, None) + edit = zip(*[(k, v) for k, v in edit.items() if v is not None]) + + if spl.period: + # Periodic extension: compute an appropriate number of loops based on + # the angular velocities at the endpoints, and extend the destination + # position by the appropriate number of periods. + avg_vel = round(float(sv + dv) * loops / spl.period) + dp = dp % spl.period + avg_vel * spl.period + + # Endpoint override: allow adjusting the number of loops as calculated + # above by locking to the nearest value with the same mod (i.e. the + # nearest value which will still line up with the node) + if e0 is not None: + sp += round(float(e0 - sp) / spl.period) * spl.period + if e1 is not None: + dp += round(float(e1 - dp) / spl.period) * spl.period + if edit or sv or dv: + return [sp, sv, dp, dv] + edit + if sp != dp: + return [sp, dp] + return sp + +def trace(k): + print k, + return k + +def merge_nodes(sp, src, dst, edit, loops): + if isinstance(sp, dict): + src, dst, edit = [x or {} for x in src, dst, edit] + return dict([(k, merge_nodes(sp[k], src.get(k), + dst.get(k), edit.get(k), loops)) + for k in set(src.keys() + dst.keys() + edit.keys()) if k in sp]) + elif isinstance(sp, spectypes.Spline): + return tospline(sp, src, dst, edit, loops) + elif isinstance(sp, spectypes.List): + if isinstance(sp.type, spectypes.Palette): + if src is not None: src = [[0] + src] + if dst is not None: dst = [[1] + dst] + return (src or []) + (dst or []) + (edit or []) + else: + return edit if edit is not None else dst if dst is not None else src + +def blend_xform(sxf, dxf, edits, loops, isfinal=False): + if sxf is None: + sxf = padding_xform(dxf, isfinal) + if dxf is None: + dxf = padding_xform(sxf, isfinal) + return merge_nodes(spec.xform, sxf, dxf, edits, loops) + +# If xin contains any of these, use the inverse identity +hole_variations = ('spherical ngon julian juliascope polar ' + 'wedge_sph wedge_julia bipolar').split() + +# These variations are identity functions at their default values +ident_variations = ('rectangles rings2 fan2 blob perspective curl ' + 'super_shape').split() + +def padding_xform(xf, isfinal): + vars = {} + xout = {'variations': vars, 'pre_affine': {'angle': 45}} + if isfinal: + xout.update(weight=0, color_speed=0) + if get(xf, 45, 'pre_affine', 'spread') > 90: + xout['pre_affine'] = {'angle': 135, 'spread': 135} + if get(xf, 45, 'post_affine', 'spread') > 90: + xout['post_affine'] = {'angle': 135, 'spread': 135} + + for k in xf['variations']: + if k in hole_variations: + # Attempt to correct for some known-ugly variations. + xout['pre_affine']['angle'] += 180 + vars['linear'] = dict(weight=-1) + return xout + if k in ident_variations: + # Try to use non-linear variations whenever we can + vars[k] = dict([(vk, vv.default) + for vk, vv in variations.var_params[k].items()]) + + if vars: + n = float(len(vars)) + for k in vars: + vars[k]['weight'] /= n + else: + vars['linear'] = dict(weight=1) + + return xout + +def blend_genomes(left, right, nloops=2, align='weightflip', seed=None, + stagger=False, blur=None, palflip=True): + align_xforms(left, right, align) + name = '%s=%s' % (left.info.get('name', ''), right.info.get('name', '')) + if seed is None: + seed = map(ord, name) + rng = np.random.RandomState(seed) + + blend = blend_splines(left, right, nloops, rng, stagger) + # TODO: licenses; check license compatibility when merging + # TODO: add URL and flockutil revision to authors + blend['info'] = { + 'name': name, + 'authors': sum([g.info.get('authors', []) for g in left, right], []) + } + blend['info']['authors'].append('flockutil') + blend['palettes'] = [get_palette(left, False), get_palette(right, True)] + blend['color']['palette_times'] = [0, "0", 1, "1"] + + if palflip: + checkpalflip(blend) + + if blur: + blur_palettes(blend, blur) + + return blend + + +def halfhearted_human_sort_key(key): + try: + return int(key) + except ValueError: + return key + +def sort_xforms(sxfs, dxfs, sortmethod, explicit=[]): + # Walk through the explicit pairs, popping previous matches from the + # forward (src=>dst) and reverse (dst=>src) maps + fwd, rev = {}, {} + for sx, dx in explicit: + if sx in fwd: + rev.pop(fwd.pop(sx, None), None) + if dx in rev: + fwd.pop(rev.pop(dx, None), None) + fwd[sx] = dx + rev[dx] = sx + + for sd in sorted(fwd.items()): + yield sd + + # Classify the remaining xforms. Currently we classify based on whether + # the pre- and post-affine transforms are flipped + scl, dcl = {}, {} + for (cl, xfs, exp) in [(scl, sxfs, fwd), (dcl, dxfs, rev)]: + for k, v in xfs.items(): + if k in exp: continue + xcl = (get(v, 45, 'pre_affine', 'spread') > 90, + get(v, 45, 'post_affine', 'spread') > 90) + cl.setdefault(xcl, []).append(k) + + def sort(keys, dct, snd=False): + if sortmethod in ('weight', 'weightflip'): + sortf = lambda k: dct[k].get('weight', 0) + elif sortmethod == 'color': + sortf = lambda k: dct[k].get('color', 0) + else: + # 'natural' key-based sort + sortf = halfhearted_human_sort_key + return sorted(keys, key=sortf) + + for cl in set(scl.keys() + dcl.keys()): + ssort = sort(scl.get(cl, []), sxfs) + dsort = sort(dcl.get(cl, []), dxfs) + if sortmethod == 'weightflip': + dsort = reversed(dsort) + for sd in izip_longest(ssort, dsort): + yield sd + +def checkpalflip(gnm): + if 'final' in gnm['xforms']: + f = gnm['xforms']['final'] + fcv, fcsp = f['color'], f['color_speed'] + else: + fcv, fcsp = SplEval(0), SplEval(0) + sansfinal = [v for k, v in gnm['xforms'].items() if k != 'final'] + + lc, rc = [np.array([v['color'](t) * (1 - fcsp(t)) + fcv(t) * fcsp(t) + for v in sansfinal]) for t in (0, 1)] + rcrv = 1 - rc + # TODO: use spline integration instead of L2 + dens = np.array([np.hypot(v['weight'](0), v['weight'](1)) + for v in sansfinal]) + return np.sum(np.abs(dens * (rc - lc))) > np.sum(np.abs(dens * (rcrv - lc))) + +def palflip(gnm): + for v in gnm['xforms'].values(): + c = v['color'] + v['color'] = SplEval([0, c(0), 1, 1 - c(1)], c(0, 1), -c(1, 1)) + pal = genome.palette_decode(gnm['palettes'][1]) + gnm['palettes'][1] = genome.palette_encode(np.flipud(pal)) + +if __name__ == "__main__": + import sys, json + a, b, c = [json.load(open(f+'.json')) for f in 'abc'] + print util.json_encode(blend(a, b, c)) diff --git a/cuburn/genome/convert.py b/cuburn/genome/convert.py new file mode 100644 index 0000000..d5b57f0 --- /dev/null +++ b/cuburn/genome/convert.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python2 + +import base64 +import warnings +import xml.parsers.expat +import numpy as np + +from variations import var_params +import util + +class XMLGenomeParser(object): + """ + Parse an XML genome into a list of dictionaries. + """ + def __init__(self): + self.flames = [] + self._flame = None + self.parser = xml.parsers.expat.ParserCreate() + self.parser.StartElementHandler = self.start_element + self.parser.EndElementHandler = self.end_element + + def start_element(self, name, attrs): + if name == 'flame': + assert self._flame is None + self._flame = dict(attrs) + self._flame['xforms'] = [] + self._flame['palette'] = np.ones((256, 4), dtype=np.float32) + elif name == 'xform': + self._flame['xforms'].append(dict(attrs)) + elif name == 'finalxform': + self._flame['finalxform'] = dict(attrs) + elif name == 'color': + idx = int(attrs['index']) + self._flame['palette'][idx][:3] = [float(v) / 255.0 + for v in attrs['rgb'].split()] + elif name == 'symmetry': + self._flame['symmetry'] = int(attrs['kind']) + def end_element(self, name): + if name == 'flame': + self.flames.append(self._flame) + self._flame = None + + @classmethod + def parse(cls, src): + parser = cls() + parser.parser.Parse(src, True) + return parser.flames + +def convert_affine(aff, animate=False): + xx, yx, xy, yy, xo, yo = vals = map(float, aff.split()) + if vals == [1, 0, 0, 1, 0, 0]: return None + + # Cuburn's IFS-space vertical direction is inverted with respect to flam3, + # so we invert all instances of y. (``yy`` is effectively inverted twice.) + yx, xy, yo = -yx, -xy, -yo + + xa = np.degrees(np.arctan2(yx, xx)) + ya = np.degrees(np.arctan2(yy, xy)) + xm = np.hypot(xx, yx) + ym = np.hypot(xy, yy) + spread = ((ya - xa) % 360) / 2 + angle = (xa + spread) % 360 + return dict(spread=spread, magnitude={'x': xm, 'y': ym}, + angle=angle, offset={'x': xo, 'y': yo}) + +def convert_vars(xf): + struct = lambda k, ps: ([('weight', k, float)] + + [(p, k+'_'+p, float) for p in ps]) + return dict([(k, apply_structure(struct(k, ps), xf)) + for k, ps in var_params.items() if k in xf]) + +def convert_xform(xf): + out = apply_structure(xform_structure, xf) + + # Deprecated symmetry arg makes this too much of a bother to handle within + # the structure framework + symm = float(xf.get('symmetry', 0)) + anim = xf.get('animate', symm <= 0) + if 'symmetry' in xf: + out.setdefault('color_speed', (1-symm)/2) + if anim and 'pre_affine' in out: + out['pre_affine']['angle'] = [out['pre_affine']['angle'], -360] + return out + +def make_symm_xforms(kind, offset): + assert kind != 0, 'Pick your own damn symmetry.' + out = [] + boring_xf = dict(color=1, color_speed=0, density=1, + variations={'linear': {'weight': 1}}) + if kind < 0: + out.append(boring_xf.copy()) + out[-1]['affine'] = dict(angle=135, spread=-45) + kind = -kind + for i in range(1, kind): + out.append(boring_xf.copy()) + if kind >= 3: + out[-1]['color'] = (i - 1) / (kind - 2.0) + ang = (45 + 360 * i / float(kind)) % 360 + out[-1]['affine'] = dict(angle=ang, spread=-45) + return dict(enumerate(out, offset)) + +def convert_xforms(flame): + xfs = dict(enumerate(map(convert_xform, flame['xforms']))) + if 'symmetry' in flame: + xfs.update(make_symm_xforms(float(flame['symmetry']), len(xfs))) + return xfs + +split_to_dict = lambda keys: lambda v: dict(zip(keys, map(float, v.split()))) +pair = split_to_dict('xy') +rgb_triple = split_to_dict('rgb') + +xform_structure = ( + ('pre_affine', 'coefs', convert_affine), + ('post_affine', 'post', convert_affine), + ('color', 'color', float), + ('color_speed', 'color_speed', float), + ('opacity', 'opacity', float), + ('weight', 'weight', float), + ('chaos', 'chaos', + lambda s: dict(enumerate(map(float, s.split())))), + ('variations', convert_vars) +) + +# A list of either three-tuples (dst, src, cvt_val), or two-tuples +# (dst, cvt_dict) for properties that are built from multiple source keys. +# If a function returns 'None', its key is dropped from the result. +flame_structure = ( + ('info.author', 'nick', str), + ('info.author_url', 'url', lambda s: 'http://' + str(s)), + ('info.name', 'name', str), + + ('camera.center', 'center', pair), + ('camera.rotation', 'rotate', float), + ('camera.dither_width', 'filter', float), + ('camera.scale', + lambda d: float(d['scale']) / float(d['size'].split()[0])), + + ('filters.colorclip.gamma', 'filter', float), + ('filters.colorclip.gamma_threshold', 'gamma_threshold', float), + ('filters.colorclip.highlight_power', 'highlight_power', float), + ('filters.colorclip.vibrance', 'vibrancy', float), + # Not sure about putting this one on colorclip + ('filters.colorclip.background', 'background', rgb_triple), + + ('filters.de.curve', 'estimator_curve', float), + ('filters.de.radius', 'estimator_radius', float), + ('filters.de.minimum', + lambda d: (float(d['estimator_minimum']) / + float(d.get('estimator_radius', 11))) + if 'estimator_minimum' in d else None), + + ('palette', 'palette', util.palette_encode), + ('xforms', convert_xforms), + ('final_xform', 'finalxform', convert_xform), +) + +def apply_structure(struct, src): + out = {} + for l in struct: + if len(l) == 2: + v = l[1](src) + else: + v = l[2](src[l[1]]) if l[1] in src else None + if v is not None: + out[l[0]] = v + return out + +def convert_flame(flame): + return util.unflatten(util.flatten(apply_structure(flame_structure, flame))) + +def convert_file(path): + """Quick one-shot conversion for an XML genome.""" + flames = XMLGenomeParser.parse(open(path).read()) + if len(flames) > 10: + warnings.warn("Lot of flames in this file. Sure it's not a " + "frame-based animation?") + for flame in flames: + yield convert_flame(flame) + +if __name__ == "__main__": + import sys + print '\n\n'.join(map(util.json_encode, convert_file(sys.argv[1]))) diff --git a/cuburn/genome/schema.py b/cuburn/genome/schema.py new file mode 100644 index 0000000..e3e5ac0 --- /dev/null +++ b/cuburn/genome/schema.py @@ -0,0 +1,82 @@ +from schematypes import * +from variations import var_ + +affine = ( + { angle: spline(45, period=360) + , spread: spline(45, period=180) + # TODO: should these scale relative to magnitude? + , off_x: spline() + , off_y: spline() + # TODO: this is probably an inappropriate scaling domain? Should one be + # constructed specifically for magnitudes? + , mag_x: spline(1) + , mag_y: spline(1) + }) + +xform = ( + { affine: affine + , post: affine + , color: spline(0, 0, 1) + , color_speed: spline(0.5, 0, 1) + , density: spline() + , opacity: scalespline(max=1) + , variations: cuburn.code.variations.params + }) + +# Since the structure of the info element differs between anims, nodes and +# edges, we pull out some of the common elements here +author = String('Attribution in the form: "Name [][, url]"') +name = String('A human-readable name for this entity') +src = String('The identifier of the source node') +dst = String('The identifier of the destination node') + +filters = ( + { bilateral: + { spatial_std: scalespline(d='Scale of profile spatial standard deviation') + , color_std: scalespline(d='Scale of profile color standard deviation') + , density_std: scalespline(d='Scale of profile density standard deviation') + , density_pow: scalespline(d='Scale of profile density pre-blur exponent') + , gradient: spline(1, d='Scale of profile gradient filter intensity ' + '(can be negative)') + } + , colorclip: + { bg_r: spline(0, 0, 1) + , bg_g: spline(0, 0, 1) + , bg_b: spline(0, 0, 1) + , gamma: scalespline() + , gamma_threshold: spline(0.01, 0, 1) + , highlight_power: spline(-1, -1, 1) + , vibrance: spline(1, 0, 1) + } + , de: + { radius: scalespline(d='Scale of profile filter radius') + , minimum: scalespline(0, d='Scale against adjusted DE radius of ' + 'minimum radius') + , curve: scalespline(0.6, d='Absolute (unscaled) value of DE curve') + } + # TODO: absolute or relative? + , logscale: {brightness: scalespline(4, d='Absolute log brightness')} + }) + +anim = ( + { type: 'animation' + , info: dict(authors=List(author), name=name, src=src, dst=dst) + , camera: + # Should center_{xy} be scaled relative to the 'scale' parameter, or is + # that just too complicated for this representation? + { center_x: spline() + , center_y: spline() + , density: scalespline() + , dither_width: scalespline() + , rotation: spline(period=360) + , scale: scalespline() + } + , filters: filters + , time: + { duration: + , frame_width: scalespline(d='Scale of profile temporal width per frame.') + } + , palettes: list_(Palette()) + , xforms: map(xform) + , final_xform: xform + }) diff --git a/cuburn/genome/spec.py b/cuburn/genome/spec.py new file mode 100644 index 0000000..0e9ba34 --- /dev/null +++ b/cuburn/genome/spec.py @@ -0,0 +1,112 @@ +from spectypes import * +from variations import var_params + +affine = ( + { 'angle': spline(45, period=360) + , 'spread': spline(45, period=180) + , 'magnitude': XYPair(scalespline()) + , 'offset': XYPair(spline()) + }) + +xform = ( + { 'pre_affine': affine + , 'post_affine': affine + , 'color': spline(0, 0, 1) + , 'color_speed': spline(0.5, 0, 1) + , 'weight': spline() + , 'opacity': scalespline(max=1) + , 'variations': var_params + }) + +# Since the structure of the info element differs between anims, nodes and +# edges, we pull out some of the common elements here +author = String('Attribution in the form: "Name [][, url]"') +name = String('A human-readable name for this entity') +src = String('The identifier of the source node') +dst = String('The identifier of the destination node') + +filters = ( + { 'bilateral': + { 'spatial_std': scalespline(6, + d='Spatial filter radius, normalized to 1080p pixels') + , 'color_std': scalespline(0.05, + d='Color filter radius, in YUV space, normalized to [0,1]') + , 'density_std': scalespline(1.5, d='Density standard deviation') + , 'density_pow': scalespline(0.8, d='Density pre-filter power') + , 'gradient': scalespline(4.0, min=None, + d='Intensity of gradient amplification (can be negative)') + } + , 'colorclip': + { 'gamma': scalespline(4) + , 'gamma_threshold': spline(0.01, 0, 1) + , 'highlight_power': spline(-1, -1, 1) + , 'vibrance': scalespline() + } + , 'de': + { 'radius': scalespline(11, d='Spatial filter radius in flam3 units') + , 'minimum': scalespline(0, max=1, d='Proportional min radius') + , 'curve': scalespline(0.6, d='Power of filter radius with density') + } + , 'haloclip': {'gamma': scalespline(4)} + , 'logscale': {'brightness': scalespline(4, d='Log-scale brightness')} + }) + +camera = ( + { 'center': XYPair(spline()) + , 'spp': scalespline(d='Samples per pixel multiplier') + , 'dither_width': scalespline() + , 'rotation': spline(period=360) + , 'scale': scalespline() + }) + +time = ( + { 'duration': scalar(1) + , 'frame_width': scalespline(d='Scale of profile temporal width per frame.') + }) + +base = ( + { 'camera': camera + , 'filters': filters + , 'palette': list_(Palette()) + , 'xforms': map_(xform) + , 'final_xform': xform + }) + +anim = dict(base) +anim.update(type='animation', time=time, + info=dict(authors=list_(author), name=name, src=src, dst=dst, + origin=string_())) + +# TODO +node = dict(base) +node.update(type='node', info=dict(author=author, author_url=string_(), + id=string_(), name=name)) + +# TODO +edge = dict(anim) +edge.update(type='edge', + info=dict(author=author, id=string_(), src=src, dst=dst), + xforms=dict(src=map_(xform), dst=map_(xform))) + +# Yeah, now I'm just messing around. +prof_filters = dict([(fk, dict([(k, refscalar(1, '.'.join(['filters', fk, k]))) + for k in fv])) for fk, fv in filters.items()]) +# And here's a completely stupid hack to drag scale into the logscale filter +prof_filters['logscale']['scale'] = refscalar(1, 'camera.scale') + +default_filters = [{'type': k} for k in ['bilateral', 'logscale', 'colorclip']] + +profile = ( + { 'duration': RefScalar(30, 'time.duration', 'Base duration in seconds') + , 'fps': Scalar(24, 'Frames per second') + , 'height': Scalar(1920, 'Output height in pixels') + , 'width': Scalar(1080, 'Output width in pixels') + , 'frame_width': refscalar(1, 'time.frame_width') + , 'spp': RefScalar(2000, 'camera.spp', 'Base samples per pixel') + , 'skip': Scalar(0, 'Skip this many frames between each rendered frame') + , 'filters': TypedList(prof_filters, default_filters, + 'Ordered list of filters to apply') + }) + +# Types recognized as independent units with a 'type' key +toplevels = dict(animation=anim, node=node, edge=edge, profile=profile) diff --git a/cuburn/genome/spectypes.py b/cuburn/genome/spectypes.py new file mode 100644 index 0000000..39c0edc --- /dev/null +++ b/cuburn/genome/spectypes.py @@ -0,0 +1,45 @@ +from collections import namedtuple + +Map = namedtuple('Map', 'type doc') +map_ = lambda type, d=None: Map(type, d) + +List = namedtuple('List', 'type doc') +list_ = lambda type, d=None: List(type, d) + +# A list as above, but where each element is a dict with a 'type' parameter +# corresponding to one of the specs listed in the 'types' dict on this spec. +TypedList = namedtuple('TypedList', 'types defaults doc') +typedlist = lambda types, defaults=[], d=None: TypedList(types, defaults, d) + +Spline = namedtuple('Spline', 'default min max interp period doc var') +def spline(default=0, min=None, max=None, interp='linear', period=None, d=None): + return Spline(default, min, max, interp, period, d, False) +def scalespline(default=1, min=0, max=None, d=None): + """Spline helper, with defaults appropriate for a scaling parameter.""" + return Spline(default, min, None, 'mag', None, d, False) + +class XYPair(dict): + """ + Specialization of spline over two dimensions. Separate type is a hint to + UIs and mutator, but this may be treated just like a normal dict. + """ + def __init__(self, type): + self['x'] = self['y'] = self.type = type + +Scalar = namedtuple('Scalar', 'default doc') +scalar = lambda default, d=None: Scalar(default, d) + +# These are scalars, as used in profiles, but which are scaled by some other +# parameter (in the genome) given by name as ``ref``. +RefScalar = namedtuple('RefScalar', 'default ref doc') +refscalar = lambda default, ref, d=None: RefScalar(default, ref, d) + +String = namedtuple('String', 'doc') +def string_(d=None): + return String(d) +Enum = namedtuple('Enum', 'choices doc') +def enum(choices, d=None): + """Enum helper. 'choices' is a space-separated string.""" + return Enum(choices.split(), d) + +Palette = namedtuple('Palette', '') diff --git a/cuburn/genome/use.py b/cuburn/genome/use.py new file mode 100644 index 0000000..4aa6f58 --- /dev/null +++ b/cuburn/genome/use.py @@ -0,0 +1,188 @@ +import numpy as np + +from spectypes import Spline, Scalar, RefScalar, Map, List, TypedList +from spec import toplevels + +class Wrapper(object): + def __init__(self, val, spec=None, path=()): + if spec is None: + spec = toplevels[val['type']] + # plain 'val' would conflict with some variation property names + self._val, self.spec, self.path = val, spec, path + + def wrap(self, name, spec, val): + # Oh, a visitor. How... pedestrian. + path = self.path + (name,) + if isinstance(spec, Spline): + return self.wrap_spline(path, spec, val) + elif isinstance(spec, Scalar): + return self.wrap_scalar(path, spec, val) + elif isinstance(spec, RefScalar): + return self.wrap_refscalar(path, spec, val) + elif isinstance(spec, dict): + return self.wrap_dict(path, spec, val) + elif isinstance(spec, Map): + return self.wrap_Map(path, spec, val) + elif isinstance(spec, List): + return self.wrap_List(path, spec, val) + elif isinstance(spec, TypedList): + return self.wrap_TypedList(path, spec, val) + return self.wrap_default(path, spec, val) + + def wrap_default(self, path, spec, val): + return val + + def wrap_spline(self, path, spec, val): + return val + + def wrap_scalar(self, path, spec, val): + return val if val is not None else spec.default + + def wrap_dict(self, path, spec, val): + return type(self)(val or {}, spec, path) + + def wrap_Map(self, path, spec, val): + return self.wrap_dict(path, spec, val) + + def wrap_List(self, path, spec, val): + return [self.wrap(spec.type, v) for v in val] + + def wrap_TypedList(self, path, spec, val): + val = val if val is not None else spec.defaults + return [self.wrap(path+(str(i),), spec.types[v['type']], v) + for i, v in enumerate(val)] + + def get_spec(self, name): + if isinstance(self.spec, Map): + return self.spec.type + return self.spec[name] + + def __getattr__(self, name): + return self.wrap(name, self.get_spec(name), self._val.get(name)) + + # Container emulation + def keys(self): + return sorted(self._val.keys()) + def items(self): + return sorted((k, self[k]) for k in self) + def __contains__(self, name): + self.get_spec(name) # raise IndexError if name is not typed + return name in self._val + def __iter__(self): + return iter(sorted(self._val)) + def __getitem__(self, name): + return getattr(self, str(name)) + + +class RefWrapper(Wrapper): + """ + Wrapper that handles RefScalars, as with profile objects. + """ + # Turns out (somewhat intentionally) that every spline parameter used on + # the host has a matching parameter in the profile, so this + def __init__(self, val, other, spec=None, path=()): + super(RefWrapper, self).__init__(val, spec, path) + self.other = other + + def wrap_dict(self, path, spec, val): + return type(self)(val or {}, self.other, spec, path) + + def wrap_refscalar(self, path, spec, val): + spev = self.other + for part in spec.ref.split('.'): + spev = spev[part] + spev *= val if val is not None else spec.default + return spev + +class SplineWrapper(Wrapper): + def wrap_spline(self, path, spec, val): + return SplineEval(val if val is not None else spec.default, + spec.interp) + +class SplineEval(object): + _mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1], + [1,-1, 0, 0], [-2, 3, 0, 0]]) + _deriv = np.matrix(np.diag([3,2,1], 1)) + + def __init__(self, knots, interp='linear'): + self.knots, self.interp = self.normalize(knots), interp + + @staticmethod + def normalize(knots): + if isinstance(knots, (int, float)): + v0, v1 = 0, 0 + knots = [(0, knots), (1, knots)] + elif len(knots) % 2 != 0: + raise ValueError("List with odd number of elements given") + elif len(knots) == 2: + v0, v1 = 0, 0 + knots = [(0, knots[0]), (1, knots[1])] + else: + p0, v0, p1, v1 = knots[:4] + knots = [(0, p0), (1, p1)] + zip(knots[4::2], knots[5::2]) + + knots = sorted(knots) + + # If stabilizing knots are missing before or after the edges of the + # [0,1] interval, add them. In almost all cases, the precise timing of + # the end knots has little affect on the shape of the curve. + td = 2 + if knots[0][0] >= 0: + knots = [(-td, knots[1][1] - (knots[1][0] - (-td)) * v0)] + knots + if knots[-1][0] <= 1: + knots.extend([(1+td, knots[-2][1] + (1+td - knots[-2][0]) * v1)]) + + knotarray = np.zeros((2, len(knots))) + knotarray.T[:] = knots + return knotarray + + def find_knots(self, itime): + idx = np.searchsorted(self.knots[0], itime) - 2 + idx = max(0, min(idx, len(self.knots[0]) - 4)) + + times = self.knots[0][idx:idx+4] + vals = self.knots[1][idx:idx+4] + # Normalize to [0,1] + t = itime - times[1] + times = times - times[1] + scale = 1 / times[2] + t = t * scale + times = times * scale + return times, vals, t, scale + + def __call__(self, itime, deriv=0): + times, vals, t, scale = self.find_knots(itime) + + m1 = (vals[2] - vals[0]) / (1.0 - times[0]) + m2 = (vals[3] - vals[1]) / times[3] + + mat = self._mat + if deriv: + mat = mat * (scale * self._deriv) ** deriv + val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T + return val[0,0] + + def __imul__(self, other): + self.knots[1] *= other + return self + + def _plt(self, name='SplEval', fig=111, show=True): + import matplotlib.pyplot as plt + x = np.linspace(-0.0, 1.0, 500) + r = x[1] - x[0] + plt.figure(fig) + plt.title(name) + plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--', + self.knots[0],self.knots[1],'x') + plt.xlim(0.0, 1.0) + if show: + plt.show() + +def wrap_genome(prof, gnm): + # It's not obvious that this is what needs to happen, so we wrap. The + # timing is simplistic, and may get expanded or moved later. + gprof = RefWrapper(prof, SplineWrapper(gnm), toplevels['profile']) + nframes = round(gprof.fps * gprof.duration) + times = np.linspace(0, 1, nframes + 1) + times = times[:-1] + 0.5 * (times[1] - times[0]) + return gprof, times diff --git a/cuburn/genome/util.py b/cuburn/genome/util.py new file mode 100644 index 0000000..32e0ddd --- /dev/null +++ b/cuburn/genome/util.py @@ -0,0 +1,122 @@ +import base64 +import numpy as np + +from cuburn.code.util import crep + +def get(dct, default, *keys): + if len(keys) == 1: + keys = keys[0].split('.') + for k in keys: + if k in dct: + dct = dct[k] + else: + return default + return dct + +def flatten(dct, ctx=()): + """ + Given a nested dict, return a flattened dict with dot-separated string + keys. Keys that have dots in them already are treated the same. + + >>> flatten({'ab': {'xy.zw': 1}, 4: 5}) == {'ab.xy.zw': 1, '4': 5} + True + """ + + for k, v in dct.items(): + k = str(k) + if isinstance(v, dict): + for sk, sv in flatten(v, ctx + (k,)): + yield sk, sv + else: + yield '.'.join(ctx + (k,)), v + +def unflatten(kvlist): + """ + Given a flattened dict, return a nested dict, where every dot-separated + key is converted into a sub-dict. + + >>> (unflatten([('ab.xy.zw', 1), ('4', 5)) == + ... {'ab': {'xy': {'zw': 1}}, '4': 5}) + True + """ + def go(d, k, v): + if len(k) == 1: + d[k[0]] = v + else: + go(d.setdefault(k[0], {}), k[1:], v) + out = {} + for k, v in kvlist: + go(out, k.split('.'), v) + return out + + +def palette_decode(datastrs): + """ + Decode a palette (stored as a list suitable for JSON packing) into a + palette. Internal palette format is simply as a (256,4) array of [0,1] + RGBA floats. + """ + if datastrs[0] != 'rgb8': + raise NotImplementedError + raw = base64.b64decode(''.join(datastrs[1:])) + pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3)) + data = np.ones((256, 4), np.float32) + data[:,:3] = pal / 255.0 + return data + +def palette_encode(data, format='rgb8'): + """ + Encode an internal-format palette to an external representation. + """ + if format != 'rgb8': + raise NotImplementedError + clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0))) + enc = base64.b64encode(np.uint8(clamp)) + return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)] + +def json_encode(obj): + """ + Encode an object into JSON notation, formatted to be more readable than + the output of the standard 'json' package for genomes. + + This serializer only works on the subset of JSON used in genomes. + """ + result = _js_enc_obj(obj).lstrip() + result = '\n'.join(l.rstrip() for l in result.split('\n')) + return result + '\n' + +def _js_enc_obj(obj, indent=0): + 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 '{}' + digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv + ks, vs = zip(*sorted(obj.items(), key=digsort)) + if ks == ('b', 'g', 'r'): + ks, vs = reversed(ks), reversed(vs) + ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks] + vs = [_js_enc_obj(v, indent+2) for v in vs] + return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}') + elif isinstance(obj, list): + vs = [_js_enc_obj(v, indent+2) for v in obj] + if vs and len(vs) % 2 == 0 and isnum(obj[1]): + vs = map(', '.join, zip(vs[::2], vs[1::2])) + return wrap(vs, '[]') + #elif isinstance(obj, SplEval): + #return _js_enc_obj(obj.knotlist, indent) + elif isinstance(obj, basestring): + return crep(obj) + elif isnum(obj): + return '%.6g' % obj + raise TypeError("Don't know how to serialize %s of type %s" % + (obj, type(obj))) diff --git a/cuburn/genome/variations.py b/cuburn/genome/variations.py new file mode 100644 index 0000000..def6436 --- /dev/null +++ b/cuburn/genome/variations.py @@ -0,0 +1,127 @@ +from spectypes import spline, scalespline +import numpy as np +# Pre-instantiated default splines. Used a *lot*. +s, ss, sz = spline(), scalespline(), scalespline(min=0) + +__all__ = ["var_names", "var_params"] + +# A map from flam3 variation numbers to variation names. Some variations may +# not be included in this list if they don't yet exist in flam3. +var_names = {} + +# A map from variation names to a dict of parameter types, suitable for +# inclusion in the genome schema. +var_params = {} + +def var(num, name, **params): + if num is not None: + var_names[num] = name + # Mark as a variation parameter spline. This can be handled in various + # ways by interpolation - usually by copying a value when missing, instead + # of reading the default value. + for k, v in params.items(): + params[k] = v._replace(var=True) + params['weight'] = scalespline(0) + var_params[name] = params + +# TODO: review all parameter splines, possibly programmatically +var(0, 'linear') +var(1, 'sinusoidal') +var(2, 'spherical') +var(3, 'swirl') +var(4, 'horseshoe') +var(5, 'polar') +var(6, 'handkerchief') +var(7, 'heart') +var(8, 'disc') +var(9, 'spiral') +var(10, 'hyperbolic') +var(11, 'diamond') +var(12, 'ex') +var(13, 'julia') +var(14, 'bent') +var(15, 'waves') +var(16, 'fisheye') +var(17, 'popcorn') +var(18, 'exponential') +var(19, 'power') +var(20, 'cosine') +var(21, 'rings') +var(22, 'fan') +var(23, 'blob', low=ss, high=ss, waves=ss) +var(24, 'pdj', a=s, b=s, c=s, d=s) +var(25, 'fan2', x=s, y=s) +var(26, 'rings2', val=s) +var(27, 'eyefish') +var(28, 'bubble') +var(29, 'cylinder') +var(30, 'perspective', angle=s, dist=s) # TODO: period +var(31, 'noise') +var(32, 'julian', power=ss, dist=ss) +var(33, 'juliascope', power=ss, dist=ss) +var(34, 'blur') +var(35, 'gaussian_blur') +var(36, 'radial_blur', angle=spline(period=4)) +var(37, 'pie', slices=spline(6, 1), rotation=s, thickness=spline(0.5, 0, 1)) +var(38, 'ngon', sides=spline(5), power=spline(3), + circle=spline(1), corners=spline(2)) +var(39, 'curl', c1=spline(1), c2=s) # TODO: not identity? + +var(40, 'rectangles', x=s, y=s) +var(41, 'arch') +var(42, 'tangent') +var(43, 'square') +var(44, 'rays') +var(45, 'blade') +var(46, 'secant2') +var(48, 'cross') +var(49, 'disc2', rot=s, twist=s) +var(50, 'super_shape', rnd=s, m=s, n1=ss, n2=spline(1), n3=spline(1), holes=s) +var(51, 'flower', holes=s, petals=s) +var(52, 'conic', holes=s, eccentricity=spline(1)) +var(53, 'parabola', height=ss, width=ss) +var(54, 'bent2', x=ss, y=ss) +var(55, 'bipolar', shift=s) +var(56, 'boarders') +var(57, 'butterfly') +var(58, 'cell', size=ss) +var(59, 'cpow', r=ss, i=s, power=ss) +var(60, 'curve', xamp=s, yamp=s, xlength=ss, ylength=ss) +var(61, 'edisc') +var(62, 'elliptic') +var(63, 'escher', beta=spline(period=2*np.pi)) +var(64, 'foci') +var(65, 'lazysusan', x=s, y=s, twist=s, space=s, spin=s) +var(66, 'loonie') +var(67, 'pre_blur') +var(68, 'modulus', x=s, y=s) +var(69, 'oscope', separation=spline(1), frequency=scalespline(np.pi), + amplitude=ss, damping=s) +var(70, 'polar2') +var(71, 'popcorn2', x=s, y=s, c=s) +var(72, 'scry') +var(73, 'separation', x=s, xinside=s, y=s, yinside=s) +var(74, 'split', xsize=s, ysize=s) +var(75, 'splits', x=s, y=s) +var(76, 'stripes', space=s, warp=s) +var(77, 'wedge', angle=s, hole=s, count=ss, swirl=s) +var(80, 'whorl', inside=s, outside=s) +var(81, 'waves2', scalex=ss, scaley=ss, + freqx=scalespline(np.pi), freqy=scalespline(np.pi)) +var(82, 'exp') +var(83, 'log') +var(84, 'sin') +var(85, 'cos') +var(86, 'tan') +var(87, 'sec') +var(88, 'csc') +var(89, 'cot') +var(90, 'sinh') +var(91, 'cosh') +var(92, 'tanh') +var(93, 'sech') +var(94, 'csch') +var(95, 'coth') +var(97, 'flux', spread=s) +var(98, 'mobius', re_a=s, im_a=s, re_b=s, im_b=s, + re_c=s, im_c=s, re_d=s, im_d=s) diff --git a/cuburn/render.py b/cuburn/render.py index 0d720c7..3052368 100644 --- a/cuburn/render.py +++ b/cuburn/render.py @@ -18,6 +18,7 @@ import filters import output from code import util, mwc, iter, interp, sort from code.util import ClsMod, devlib, filldptrlib, assemble_code, launch +from cuburn.genome.util import palette_decode RenderedImage = namedtuple('RenderedImage', 'buf idx gpu_time') Dimensions = namedtuple('Dimensions', 'w h aw ah astride') @@ -200,7 +201,7 @@ class Renderer(object): MAX_MODREFS = 20 _modrefs = [] - def __init__(self, gnm): + def __init__(self, gnm, gprof): self.packer, self.lib = iter.mkiterlib(gnm) cubin = util.compile('iter', assemble_code(self.lib)) self.mod = cuda.module_from_buffer(cubin) @@ -210,9 +211,7 @@ class Renderer(object): self._modrefs.append(self.mod) # TODO: make these customizable - self.filts = [ filters.Bilateral() - , filters.Logscale() - , filters.ColorClip() ] + self.filts = filters.create(gprof) self.out = output.PILOutput() class RenderManager(ClsMod): @@ -236,13 +235,14 @@ class RenderManager(ClsMod): Note that for now, this is broken! It ignores ``gnm``, and only packs the genome that was used when creating the renderer. """ - times, knots = rdr.packer.pack(self.pool) + times, knots = rdr.packer.pack(gnm, self.pool) cuda.memcpy_htod_async(self.src_a.d_times, times, self.stream_a) cuda.memcpy_htod_async(self.src_a.d_knots, knots, self.stream_a) - ptimes, pidxs = zip(*gnm.palette_times) - palettes = self.pool.allocate((len(ptimes), 256, 4), f32) - palettes[:] = [gnm.decoded_palettes[i] for i in pidxs] + palsrc = dict([(v[0], palette_decode(v[1:])) for v in gnm['palette']]) + ptimes, pvals = zip(*sorted(palsrc.items())) + palettes = self.pool.allocate((len(palsrc), 256, 4), f32) + palettes[:] = pvals palette_times = self.pool.allocate((self.src_a.max_knots,), f32) palette_times.fill(1e9) palette_times[:len(ptimes)] = ptimes @@ -271,7 +271,7 @@ class RenderManager(ClsMod): 256, np.ceil(nts / 256.), self.info_a.d_params, self.src_a.d_times, self.src_a.d_knots, f32(ts), f32(td / nts), i32(nts)) - #self._print_interp_knots(rdr) + self._print_interp_knots(rdr) def _print_interp_knots(self, rdr, tsidx=5): infos = cuda.from_device(self.info_a.d_params, @@ -279,7 +279,7 @@ class RenderManager(ClsMod): for i, n in zip(infos[-1], rdr.packer.packed): print '%60s %g' % ('_'.join(n), i) - def _iter(self, rdr, gnm, dim, tc): + def _iter(self, rdr, gnm, gprof, dim, tc): tref = rdr.mod.get_surfref('flatpal') tref.set_array(self.info_a.d_pal_array, 0) @@ -291,19 +291,29 @@ class RenderManager(ClsMod): fill(self.fb.d_points, self.fb._len_d_points / 4, f32(np.nan)) nts = self.info_a.ntemporal_samples - nsamps = (gnm.spp(tc) * dim.w * dim.h) + nsamps = (gprof.spp(tc) * dim.w * dim.h) nrounds = int(nsamps / (nts * 256. * 256)) + 1 - launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, nrounds), - self.fb.d_front, self.fb.d_side, - self.fb.d_rb, self.fb.d_seeds, self.fb.d_points, - self.info_a.d_params) + + def launch_iter(n): + if n == 0: return + launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, n), + self.fb.d_front, self.fb.d_side, + self.fb.d_rb, self.fb.d_seeds, self.fb.d_points, + self.info_a.d_params) + # Split the launch into multiple rounds, possibly (slightly) reducing + # work overlap but avoiding stalls when working on a device with an + # active X session. TODO: characterize performance impact, autodetect + BLOCK_SIZE = 4 + for i in range(BLOCK_SIZE-1, nrounds, BLOCK_SIZE): + launch_iter(BLOCK_SIZE) + launch_iter(nrounds%BLOCK_SIZE) nblocks = int(np.ceil(np.sqrt(dim.ah*dim.astride/256.))) launch('flush_atom', self.mod, self.stream_a, 256, (nblocks, nblocks), u64(self.fb.d_front), u64(self.fb.d_side), i32(nbins)) - def queue_frame(self, rdr, gnm, tc, w, h, copy=True): + def queue_frame(self, rdr, gnm, gprof, tc, copy=True): """ Queue one frame for rendering. @@ -332,9 +342,10 @@ class RenderManager(ClsMod): """ # Note: we synchronize on the previous stream if buffers need to be # reallocated, which implicitly also syncs the current stream. - dim = self.fb.set_dim(w, h, self.stream_b) + dim = self.fb.set_dim(gprof.width, gprof.height, self.stream_b) - td = gnm.adj_frame_width(tc) + # TODO: calculate this externally somewhere? + td = gprof.frame_width(tc) / round(gprof.fps * gprof.duration) ts, te = tc - 0.5 * td, tc + 0.5 * td # The stream interleaving here is nontrivial. @@ -345,12 +356,12 @@ class RenderManager(ClsMod): self._interp(rdr, gnm, dim, ts, td) if self.filt_evt: self.stream_a.wait_for_event(self.filt_evt) - self._iter(rdr, gnm, dim, tc) + self._iter(rdr, gnm, gprof, dim, tc) if self.copy_evt: self.stream_a.wait_for_event(self.copy_evt) - for filt in rdr.filts: - filt.apply(self.fb, gnm, dim, tc, self.stream_a) - rdr.out.convert(self.fb, gnm, dim, self.stream_a) + for filt, params in zip(rdr.filts, gprof.filters): + filt.apply(self.fb, gprof, params, dim, tc, self.stream_a) + rdr.out.convert(self.fb, gprof, dim, self.stream_a) self.filt_evt = cuda.Event().record(self.stream_a) h_out = rdr.out.copy(self.fb, dim, self.pool, self.stream_a) self.copy_evt = cuda.Event().record(self.stream_a) @@ -359,13 +370,13 @@ class RenderManager(ClsMod): self.stream_a, self.stream_b = self.stream_b, self.stream_a return self.copy_evt, h_out - def render(self, gnm, times, w, h): + def render(self, gnm, gprof, times): """ A port of the old rendering function, retained for backwards compatibility. Some of this will be pulled into as-yet-undecided methods for more DRY. """ - rdr = Renderer(gnm) + rdr = Renderer(gnm, gprof) last_evt = cuda.Event().record(self.stream_a) last_idx = None def wait(): # Times like these where you wish for a macro @@ -374,7 +385,7 @@ class RenderManager(ClsMod): gpu_time = last_evt.time_since(two_evts_ago) return RenderedImage(last_buf, last_idx, gpu_time) for idx, tc in times: - evt, h_buf = self.queue_frame(rdr, gnm, tc, w, h, last_idx is None) + evt, h_buf = self.queue_frame(rdr, gnm, gprof, tc, last_idx is None) if last_idx: yield wait() two_evts_ago, last_evt = last_evt, evt diff --git a/main.py b/main.py index 6b01f52..581d903 100644 --- a/main.py +++ b/main.py @@ -22,13 +22,14 @@ from itertools import ifilter import numpy as np import pycuda.driver as cuda -from cuburn import genome, render, filters, output +from cuburn import render, filters, output +from cuburn.genome import convert, use profiles = { - '1080p': dict(fps=24, width=1920, height=1080, quality=3000, skip=0), - '720p': dict(fps=24, width=1280, height=720, quality=2500, skip=0), - '540p': dict(fps=24, width=960, height=540, quality=2500, skip=0), - 'preview': dict(fps=24, width=640, height=360, quality=800, skip=1) + '1080p': dict(width=1920, height=1080), + '720p': dict(width=1280, height=720), + '540p': dict(width=960, height=540), + 'preview': dict(width=640, height=360, quality=800, skip=1) } def save(out): @@ -41,15 +42,14 @@ def main(args, prof): gnm_str = args.flame.read() if '<' in gnm_str[:10]: - flames = genome.XMLGenomeParser.parse(gnm_str) + flames = convert.XMLGenomeParser.parse(gnm_str) if len(flames) != 1: warnings.warn('%d flames in file, only using one.' % len(flames)) - gnm = genome.convert_flame(flames[0]) + gnm = convert.convert_flame(flames[0]) else: gnm = json.loads(gnm_str) - gnm = genome.Genome(gnm) - err, times = gnm.set_profile(prof) + gprof, times = use.wrap_genome(prof, gnm) rmgr = render.RenderManager() basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_' @@ -71,7 +71,7 @@ def main(args, prof): if not os.path.isfile(f[0]) or m > os.path.getmtime(f[0])) w, h = prof['width'], prof['height'] - gen = rmgr.render(gnm, frames, w, h) + gen = rmgr.render(gnm, gprof, frames) if not args.gfx: for out in gen: