Initial commit. Adds what was done for the homework assigment as well as the extra stuff I did the following months
13 files changed, 427 insertions(+), 0 deletions(-)

A => .hgignore
A => cmandelbro.c
A => cmandelbro.h
A => makeproject
A => mandelbro.asm
A => mandelbro.pyx
A => mandeltest.py
A => setup.py
A => testperf
A => www/index.html
A => www/index.py
A => www/js.js
A => www/style.css
A => .hgignore +5 -0
@@ 0,0 1,5 @@ 
+build
+\.prof$
+\.pyc$
+\.so$
+\.o$

          
A => cmandelbro.c +51 -0
@@ 0,0 1,51 @@ 
+#include "cmandelbro.h"
+
+#include <opencv2/highgui/highgui_c.h>
+
+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; x<img_width; x++)
+    {
+        cx = rect.x + rect.w * (x / f_img_width);
+        for(y=0; y<img_height; y++)
+        {
+            cy = rect.y + rect.h * (y / f_img_height);
+            iters = get_iterations(cx, cy, max_iterations);
+/*             assert(iters == 1); */
+            // TODO, not greyscale colour
+            color = 0xff * iters/max_iterations;
+            CV_MAT_ELEM((*mat), char, y, x) = color;
+        }
+    }
+
+    CvMat* buf = cvEncodeImage(".png", mat, 0);
+    PyObject* pystring = PyString_FromStringAndSize(buf->data.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;
+}

          
A => cmandelbro.h +13 -0
@@ 0,0 1,13 @@ 
+#ifndef CMANDELBRO_H
+#define CMANDELBRO_H
+
+#include <Python.h>
+
+typedef struct
+{
+    double x, y, w, h;
+} Rect;
+
+PyObject* render_to_raw(Rect rect, int img_width, int img_height);
+
+#endif

          
A => makeproject +8 -0
@@ 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))"

          
A => mandelbro.asm +71 -0
@@ 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

          
A => mandelbro.pyx +12 -0
@@ 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)

          
A => mandeltest.py +18 -0
@@ 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()

          
A => setup.py +16 -0
@@ 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
+)

          
A => testperf +5 -0
@@ 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

          
A => www/index.html +16 -0
@@ 0,0 1,16 @@ 
+<noscript>I need javascript you silly goose</noscript>
+<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js' type='text/javascript'></script>
+<link rel='stylesheet' type='text/css' href='__media_root__/style.css' />
+<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
+
+<span id=infobar>
+    <span id=selectioninfo></span>
+</span>
+
+<div id=selectionwidget>
+    <span id=update>Fetch</span>
+    <span id=toggle_aspect>Toggle 4:3 Aspect Ratio</span>
+    <span id=cancelselection>Cancel</span>
+</div>
+
+<script src='__media_root__/js.js' type='text/javascript'></script>

          
A => www/index.py +42 -0
@@ 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)

          
A => www/js.js +127 -0
@@ 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();
+});

          
A => www/style.css +43 -0
@@ 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;
+}