# HG changeset patch # User sqwishy # Date 1361774193 28800 # Sun Feb 24 22:36:33 2013 -0800 # Node ID 198e025bca3e1ae30eed21b9c2b8e873ff140dfc # Parent 0000000000000000000000000000000000000000 Initial commit. Adds what was done for the homework assigment as well as the extra stuff I did the following months diff --git a/.hgignore b/.hgignore new file mode 100644 --- /dev/null +++ b/.hgignore @@ -0,0 +1,5 @@ +build +\.prof$ +\.pyc$ +\.so$ +\.o$ diff --git a/cmandelbro.c b/cmandelbro.c new file mode 100644 --- /dev/null +++ b/cmandelbro.c @@ -0,0 +1,51 @@ +#include "cmandelbro.h" + +#include + +extern int get_iterations(double, double, int); + +PyObject* render_to_raw(Rect rect, int img_width, int img_height) +{ + int color, iters, x, y; + float f_img_width = img_width, + f_img_height = img_height; + double cx, cy; + // Magic number \todo, better magic number... + int max_iterations = 96; + + CvMat* mat = cvCreateMat(img_height, img_width, CV_8UC1); + + for(x=0; xdata.ptr, buf->step); + + cvReleaseMat(&mat); + cvReleaseMat(&buf); + return pystring; +} + + +int main() +{ + Rect rect; + rect.x = 0.0; + rect.y = 0.0; + rect.w = 1.0; + rect.h = 1.0; + + render_to_raw(rect, 800, 600); + return 0; +} diff --git a/cmandelbro.h b/cmandelbro.h new file mode 100644 --- /dev/null +++ b/cmandelbro.h @@ -0,0 +1,13 @@ +#ifndef CMANDELBRO_H +#define CMANDELBRO_H + +#include + +typedef struct +{ + double x, y, w, h; +} Rect; + +PyObject* render_to_raw(Rect rect, int img_width, int img_height); + +#endif diff --git a/makeproject b/makeproject new file mode 100755 --- /dev/null +++ b/makeproject @@ -0,0 +1,8 @@ +# Compiles mandelbro.asm -> mandelbrolib.so +fasm mandelbro.asm m.o && ld -shared m.o -o mandelbroasmlib.so && rm -f m.o && \ + +gcc cmandelbro.c mandelbroasmlib.so -shared -fPIC `pkg-config --libs opencv python-2.7 --cflags opencv python-2.7` -o mandelbrolib.so $* && \ + +python2.7 setup.py build_ext --inplace --force $* && \ + +LD_LIBRARY_PATH=`pwd` python2.7 -c "import mandelbro; mandelbro.render_to_raw((-1,2,3,-4), (400, 950))" diff --git a/mandelbro.asm b/mandelbro.asm new file mode 100644 --- /dev/null +++ b/mandelbro.asm @@ -0,0 +1,71 @@ +format ELF64 + +public get_iterations + +section '.text' executable + +;--- get_iterations ------------------------------------------------------------ +; Used registers +; rax return register +; Parameters +; rdi number of iterations +; xmm0 real part of given complex number, c +; xmm1 imaginary part of given complex number, c +; +; xmm2 real part of complex number, z +; xmm3 imaginary part of complex number, z +; xmm4 these four registers are used for caching and storing comparison +; xmm5 results and other temporary data +; xmm6 We put a sum in this one +; xmm7 We put 4.0 in this register + +four: dq 4.0 + +get_iterations: + movsd xmm7, [four] ; We use this later in comparison + ; i = 1 + mov rax, 1 ; Initilize iteration count + ; z_r, z_i = c_r, c_i + movupd xmm2, xmm0 + movupd xmm3, xmm1 + +_loop: + ; if z_r*z_r + z_i*z_i > 4.0: break + ; We will want to store zx*zx and zy*zy in xmm4 and xmm5 respectively + ; because we can use them later. + movupd xmm4, xmm2 ; Copy both parts of complex number, z, and + mulpd xmm4, xmm4 ; square each. So... + movupd xmm5, xmm3 ; xmm4 = z_r * z_r + mulpd xmm5, xmm5 ; xmm5 = z_i * z_i + + movupd xmm6, xmm4 ; Copy our z_r register, we are preserving xmm4 + ; and xmm5 for later. + addpd xmm6, xmm5 ; z_r**2 + z_i**2 -> xmm6 + + comisd xmm6, xmm7 ; Compare xmm6 to 4.0 + ja return + +_ctn: + ; z = z**2 + c + ; This is a bit tricky. Computing each component involves the value of + ; both components. So, if we modify one component, the other one needs + ; a to use a copy of the component before it was just modified. However, + ; the real component only requires the squared values, which, + ; conveniently, we have set aside in xmm4 and xmm5. + ; z_i = 2 * z_i * z_r + c_i + mulpd xmm3, xmm2 ; z_i = z_i * z_r + addpd xmm3, xmm3 ; z_i = 2 * z_i * z_r + addpd xmm3, xmm1 ; z_i = 2 * z_i * z_r + c_i + ; z_r = z_r * z_r - z_i * z_i + c_r + subpd xmm4, xmm5 ; z_r * z_r - z_i * z_i -> xmm4 + addpd xmm4, xmm0 ; xmm4 + c_r -> xmm4 + movupd xmm2, xmm4 ; xmm4 -> xmm2 + + ; i += 1 + ; if i == max_iterations: break + inc rax ; Increment counter + cmp rax, rdi ; Return if at max iterations + jne _loop + +return: + ret diff --git a/mandelbro.pyx b/mandelbro.pyx new file mode 100644 --- /dev/null +++ b/mandelbro.pyx @@ -0,0 +1,12 @@ +cdef extern from 'cmandelbro.h': + ctypedef struct Rect: + double x, y, w, h + + cdef str c_render_to_raw "render_to_raw" (Rect, int, int) nogil + +def render_to_raw(tuple _rect, tuple size): + ' Rect has been reduced to a tuple, deal with it ' + cdef Rect rect = Rect(_rect[0], _rect[1], _rect[2], _rect[3]) + cdef int w, h, len + w, h = size + return c_render_to_raw(rect, w, h) diff --git a/mandeltest.py b/mandeltest.py new file mode 100644 --- /dev/null +++ b/mandeltest.py @@ -0,0 +1,18 @@ +''' FIXME TODO XXX, this isn't even a real test, it doesn't test the output or anything +''' +import mandelbro + +import pstats +import cProfile + +#cProfile.runctx('mandelbro.render_to_raw((-2, 1.5, 3, -3), (80, 60))', +# globals(), {}, 'mandelbro.prof') + +cProfile.runctx('mandelbro.render_to_raw((-2, 1.5, 3, -3), (800, 600))', + globals(), {}, 'mandelbro.prof') + +#cProfile.runctx('mandelbro.render(mandelbro.Rect(-2, 1.5, 3, -3), (800, 600))', +# globals(), {}, 'mandelbro.prof') + +s = pstats.Stats('mandelbro.prof') +s.strip_dirs().sort_stats('time').print_stats() diff --git a/setup.py b/setup.py new file mode 100644 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext + +ext_modules = [ + Extension('mandelbro', + ['mandelbro.pyx'], + libraries=[':mandelbrolib.so'], + ) +] + +setup( + name = 'Mandelbro rendering library thingy', + cmdclass = {'build_ext': build_ext}, + ext_modules = ext_modules +) diff --git a/testperf b/testperf new file mode 100755 --- /dev/null +++ b/testperf @@ -0,0 +1,5 @@ +#!/bin/bash +export LD_LIBRARY_PATH=`pwd` + +#python setup.py build_ext --force --inplace && echo "Compiling complete ... testing" && python mandeltest.py +python mandeltest.py diff --git a/www/index.html b/www/index.html new file mode 100644 --- /dev/null +++ b/www/index.html @@ -0,0 +1,16 @@ + + + + + + + + + +
+ Fetch + Toggle 4:3 Aspect Ratio + Cancel +
+ + diff --git a/www/index.py b/www/index.py new file mode 100644 --- /dev/null +++ b/www/index.py @@ -0,0 +1,42 @@ +import bottle +from urllib import urlencode, quote +from mandelbro import render_to_raw + +ctx = { + 'site_root': '/cmandelbro', + 'media_root': '/cmandelbro/file', + } + +# Hacky file serving + +@bottle.route(ctx['site_root'] + '/file/:filename') +def get_file(filename=''): + ' Hacky static file hosting ' + if filename in ('style.css', 'js.js', 'index.html'): + # TODO, set headers? + s = open(filename, 'rb').read() + try: + s = s.decode('utf-8') + for key, value in ctx.items(): + s = s.replace('__%s__' % key, value) + except UnicodeDecodeError: + pass + return s + +# Request handling + +@bottle.get(ctx['site_root']) +def index(): + ' Serve just index page ' + return get_file('index.html') + +@bottle.get(ctx['site_root'] + '/mandelbroget') +def get_mandelbro(): + ' Serve mandelbrot image ' + print bottle.request + bottle.response.content_type = 'image/png' + D = dict(bottle.request.GET.items()) + rect = tuple(map(float, (D['x'], D['y'], D['w'], D['h']))) + return render_to_raw(rect, (800, 600)) + +bottle.run(server=bottle.FlupFCGIServer, host='127.0.0.1', port=8043, reloader=True) diff --git a/www/js.js b/www/js.js new file mode 100644 --- /dev/null +++ b/www/js.js @@ -0,0 +1,127 @@ +/***** Data? ******************************************************************/ + +function Rect(x, y, w, h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.tostring = function() { + return 'x=' + this.x + ', y=' + this.y + ', w=' + this.w + ', h=' + this.h; + } + this.toquery = function() { + return 'x=' + this.x + '&y=' + this.y + '&w=' + this.w + '&h=' + this.h; + } +} + +var img_width = 800, + img_height = 600; +var mouseisdown = false; // Why isn't this in the event? +var fixed_ar = true; // Fixed aspect ratio +// Rect of current image +var current_rect = new Rect(-2.2, 1.2, 3.2, -2.4); +// Rect of selected area +var desired_rect; + +/***** UI *********************************************************************/ + +// Updates the displayed mandelbrot +function update_bro(str) { + $('body').css('background-image', 'url("' + str + '")'); +} +//update_bro('__media_root__/defaultbro.png'); +update_bro('__site_root__/mandelbroget?' + current_rect.toquery()); + +// Scales a pixel in the page to a position in the mandelbrot based on +// the size and current rectangle of the current image +function tr_x(px) { + px -= (document.body.offsetWidth - img_width) / 2 + return current_rect.x + px / img_width * current_rect.w; +} +function tr_y(py) { + py -= (document.body.offsetHeight - img_height) / 2 + return current_rect.y + py / img_height * current_rect.h; +} + +// These control the appearance of the selection box widget +function start_selection(x, y) { + var w = $('#selectionwidget'); + w.show(); + w.offset({'top': y, 'left': x}); + w.width(0); + w.height(0); + update_info(); +} +function resize_selection(x, y) { + var w = $('#selectionwidget'); + w.width(x - w.offset().left); + w.height(y - w.offset().top); + update_info(); +} +function resize_to_ratio(ratio) { + var w = $('#selectionwidget'); + w.width(w.height() * ratio); + desired_rect.w = -desired_rect.h * ratio; +} +function update_info() { + $('#selectioninfo').html(desired_rect.tostring()); +} + +/* Selection */ + +$('html').mousedown(function(event) { + if(event.target.tagName == 'SPAN') + return; + mouseisdown = true; + event.stopPropagation() + + desired_rect = new Rect(tr_x(event.pageX), tr_y(event.pageY), 0, 0); + start_selection(event.pageX, event.pageY); +}); + +$('html').mousemove(function(event) { + if(!mouseisdown) + return; + event.stopPropagation() + + desired_rect.w = tr_x(event.pageX) - desired_rect.x; + desired_rect.h = tr_y(event.pageY) - desired_rect.y; + + resize_selection(event.pageX, event.pageY); + if(fixed_ar) + resize_to_ratio(4/3); +}); + +$('html').mouseup(function(event) { + if(!mouseisdown) // Mouse is already up? Do nothing. + return; + mouseisdown = false; + event.stopPropagation() + + if(desired_rect.w != 0 && desired_rect.h != 0) { + resize_selection(event.pageX, event.pageY); + if(fixed_ar) + resize_to_ratio(4/3); + } + else // They selected nothing, clear selection + $('#selectionwidget').hide(); +}); + +/* Selection box buttons */ + +$('#update').click(function(event) { + current_rect = desired_rect; // New rect! :D + update_bro('__site_root__/mandelbroget?' + current_rect.toquery()); + $('#selectionwidget').hide(); +}); + +$('#cancelselection').click(function(event) { + $('#selectionwidget').hide(); + $('#selectioninfo').html(''); +}); + +$('#toggle_aspect').click(function(event) { + fixed_ar = !fixed_ar; + if(fixed_ar) + resize_to_ratio(4/3); + update_info(); +}); diff --git a/www/style.css b/www/style.css new file mode 100644 --- /dev/null +++ b/www/style.css @@ -0,0 +1,43 @@ +a, a:visited { color: #333; text-decoration: underline; } + +html, body { + margin: 0; + height: 100%; + font-family: 'Inconsolata', monospace; + /* Disable text selection */ + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; +} + +body { + background-repeat: no-repeat; + background-position: center; +} + +#infobar { + background: black; + color: white; +} + +#selectionwidget { + position: absolute; + z-index: 100; + display: none; + border: 1px dashed grey; +} +#selectionwidget * { + padding: 2px; + display: none; + border-bottom: 1px dashed grey; +} +#selectionwidget:hover * { + display: block; + background: white; +} +#selectionwidget span:hover { + cursor: pointer; + background: lightgrey; +}