rockbox/apps/plugins/fractals/mandelbrot_set.c
Marcin Bukat 708a54d3de Fix yellow for LCD_DEPTH == 32
Change-Id: I59e9ec8720555c17e882a3a8c7ed0ba281b8d7c7
2018-06-20 21:57:34 +02:00

422 lines
11 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2004 Matthias Wientapper
* Heavily extended 2005 Jens Arnold
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "fractal_sets.h"
#include "mandelbrot_set.h"
#define BUTTON_YIELD_TIMEOUT (HZ / 4)
#ifdef USEGSLIB
static unsigned char imgbuffer[LCD_HEIGHT];
#else
static fb_data imgbuffer[LCD_HEIGHT];
#endif
#ifdef USEGSLIB
#define LCOLOR(iter) ((iter ^ 7) << 5)
#else
/*
* Spread iter's colors over color range.
* 345 (=15*26-45) is max_iter maximal value
* This implementation ignores pixel format, thus it is not uniformly spread
*/
#if LCD_DEPTH > 24
/* when LCD_DEPTH is 32 casting to 64bit intermediate is needed to prevent
* overflow and warning 'left shift count >= width of type'
*/
#define LCOLOR(iter) ((unsigned int)(((unsigned long long)iter << LCD_DEPTH) / 345))
#else
#define LCOLOR(iter) ((iter << LCD_DEPTH) / 345)
#endif
#endif
#ifdef HAVE_LCD_COLOR
#define COLOR(iter) FB_SCALARPACK(LCOLOR(iter))
#define CONVERGENCE_COLOR FB_RGBPACK(0, 0, 0)
#else /* greyscale */
#define COLOR(iter) (unsigned char)LCOLOR(iter)
#define CONVERGENCE_COLOR 0
#endif
#if CONFIG_LCD == LCD_SSD1815
/* Recorder, Ondio: pixel_height == 1.25 * pixel_width */
#define MB_HEIGHT (LCD_HEIGHT*5/4)
#else
/* square pixels */
#define MB_HEIGHT LCD_HEIGHT
#endif
#define MB_XOFS (-0x03000000L) /* -0.75 (s5.26) */
#if (3000 * MB_HEIGHT / LCD_WIDTH) >= 2400 /* width is limiting factor */
#define MB_XFAC (0x06000000LL) /* 1.5 (s5.26) */
#define MB_YFAC (MB_XFAC*MB_HEIGHT / LCD_WIDTH)
#else /* height is limiting factor */
#define MB_YFAC (0x04cccccdLL) /* 1.2 (s5.26) */
#define MB_XFAC (MB_YFAC*LCD_WIDTH / MB_HEIGHT)
#endif
#ifndef USEGSLIB
#define UPDATE_FREQ (HZ/50)
#endif
/* fixed point format s5.26: sign, 5 bits integer part, 26 bits fractional part */
struct fractal_ops *ops;
long x_min;
long x_max;
long x_step;
long x_delta;
long y_min;
long y_max;
long y_step;
long y_delta;
int step_log2;
unsigned max_iter;
static void mandelbrot_init(void);
static int mandelbrot_calc_low_prec(struct fractal_rect *rect,
int (*button_yield_cb)(void *), void *button_yield_ctx);
static int mandelbrot_calc_high_prec(struct fractal_rect *rect,
int (*button_yield_cb)(void *), void *button_yield_ctx);
static void mandelbrot_move(int dx, int dy);
static int mandelbrot_zoom(int factor);
static int mandelbrot_precision(int d);
struct fractal_ops mandelbrot_ops =
{
.init = mandelbrot_init,
.calc = NULL,
.move = mandelbrot_move,
.zoom = mandelbrot_zoom,
.precision = mandelbrot_precision,
};
#define LOG2_OUT_OF_BOUNDS -32767
static int ilog2_fp(long value) /* calculate integer log2(value_fp_6.26) */
{
int i = 0;
if (value <= 0)
{
return LOG2_OUT_OF_BOUNDS;
}
else if (value > (1L << 26))
{
while (value >= (2L << 26))
{
value >>= 1;
i++;
}
}
else
{
while (value < (1L << 26))
{
value <<= 1;
i--;
}
}
return i;
}
static int recalc_parameters(void)
{
x_step = (x_max - x_min) / LCD_WIDTH;
y_step = (y_max - y_min) / LCD_HEIGHT;
step_log2 = ilog2_fp(MIN(x_step, y_step));
if (step_log2 == LOG2_OUT_OF_BOUNDS)
return 1; /* out of bounds */
x_delta = X_DELTA(x_step);
y_delta = Y_DELTA(y_step);
max_iter = MAX(15, -15 * step_log2 - 45);
ops->calc = (step_log2 <= -10) ?
mandelbrot_calc_high_prec : mandelbrot_calc_low_prec;
return 0;
}
static void mandelbrot_init(void)
{
ops = &mandelbrot_ops;
x_min = MB_XOFS - MB_XFAC;
x_max = MB_XOFS + MB_XFAC;
y_min = -MB_YFAC;
y_max = MB_YFAC;
recalc_parameters();
}
static int mandelbrot_calc_low_prec(struct fractal_rect *rect,
int (*button_yield_cb)(void *), void *button_yield_ctx)
{
#ifndef USEGSLIB
long next_update = *rb->current_tick;
int last_px = rect->px_min;
#endif
unsigned n_iter;
long a32, b32;
short x, x2, y, y2, a, b;
int p_x, p_y;
unsigned long last_yield = *rb->current_tick;
unsigned long last_button_yield = *rb->current_tick;
a32 = x_min + x_step * rect->px_min;
for (p_x = rect->px_min; p_x < rect->px_max; p_x++)
{
a = a32 >> 16;
b32 = y_min + y_step * (LCD_HEIGHT - rect->py_max);
for (p_y = rect->py_max - 1; p_y >= rect->py_min; p_y--)
{
b = b32 >> 16;
x = a;
y = b;
n_iter = 0;
while (++n_iter <= max_iter)
{
x2 = MULS16_ASR10(x, x);
y2 = MULS16_ASR10(y, y);
if (x2 + y2 > (4<<10)) break;
y = 2 * MULS16_ASR10(x, y) + b;
x = x2 - y2 + a;
}
if (n_iter > max_iter)
imgbuffer[p_y] = CONVERGENCE_COLOR;
else
imgbuffer[p_y] = COLOR(n_iter);
/* be nice to other threads:
* if at least one tick has passed, yield */
if (TIME_AFTER(*rb->current_tick, last_yield))
{
rb->yield();
last_yield = *rb->current_tick;
}
if (TIME_AFTER(*rb->current_tick, last_button_yield))
{
if (button_yield_cb(button_yield_ctx))
{
#ifndef USEGSLIB
/* update screen part that was changed since last yield */
rb->lcd_update_rect(last_px, rect->py_min,
p_x - last_px + 1, rect->py_max - rect->py_min);
#endif
rect->px_min = (p_x == 0) ? 0 : p_x - 1;
return 1;
}
last_button_yield = *rb->current_tick + BUTTON_YIELD_TIMEOUT;
}
b32 += y_step;
}
#ifdef USEGSLIB
grey_ub_gray_bitmap_part(imgbuffer, 0, rect->py_min, 1,
p_x, rect->py_min, 1, rect->py_max - rect->py_min);
#else
rb->lcd_bitmap_part(imgbuffer, 0, rect->py_min, 1,
p_x, rect->py_min, 1, rect->py_max - rect->py_min);
if ((p_x == rect->px_max - 1) ||
TIME_AFTER(*rb->current_tick, next_update))
{
next_update = *rb->current_tick + UPDATE_FREQ;
/* update screen part that was changed since last yield */
rb->lcd_update_rect(last_px, rect->py_min,
p_x - last_px + 1, rect->py_max - rect->py_min);
last_px = p_x;
}
#endif
a32 += x_step;
}
rect->valid = 0;
return 0;
}
static int mandelbrot_calc_high_prec(struct fractal_rect *rect,
int (*button_yield_cb)(void *), void *button_yield_ctx)
{
#ifndef USEGSLIB
long next_update = *rb->current_tick;
int last_px = rect->px_min;
#endif
unsigned n_iter;
long x, x2, y, y2, a, b;
int p_x, p_y;
unsigned long last_yield = *rb->current_tick;
unsigned long last_button_yield = *rb->current_tick;
MULS32_INIT();
a = x_min + x_step * rect->px_min;
for (p_x = rect->px_min; p_x < rect->px_max; p_x++)
{
b = y_min + y_step * (LCD_HEIGHT - rect->py_max);
for (p_y = rect->py_max - 1; p_y >= rect->py_min; p_y--)
{
x = a;
y = b;
n_iter = 0;
while (++n_iter <= max_iter)
{
x2 = MULS32_ASR26(x, x);
y2 = MULS32_ASR26(y, y);
if (x2 + y2 > (4L<<26)) break;
y = 2 * MULS32_ASR26(x, y) + b;
x = x2 - y2 + a;
}
if (n_iter > max_iter)
imgbuffer[p_y] = CONVERGENCE_COLOR;
else
imgbuffer[p_y] = COLOR(n_iter);
/* be nice to other threads:
* if at least one tick has passed, yield */
if (TIME_AFTER(*rb->current_tick, last_yield))
{
rb->yield();
last_yield = *rb->current_tick;
}
if (TIME_AFTER(*rb->current_tick, last_button_yield))
{
if (button_yield_cb(button_yield_ctx))
{
#ifndef USEGSLIB
/* update screen part that was changed since last yield */
rb->lcd_update_rect(last_px, rect->py_min,
p_x - last_px + 1, rect->py_max - rect->py_min);
#endif
rect->px_min = (p_x == 0) ? 0 : p_x - 1;
return 1;
}
last_button_yield = *rb->current_tick + BUTTON_YIELD_TIMEOUT;
}
b += y_step;
}
#ifdef USEGSLIB
grey_ub_gray_bitmap_part(imgbuffer, 0, rect->py_min, 1,
p_x, rect->py_min, 1, rect->py_max - rect->py_min);
#else
rb->lcd_bitmap_part(imgbuffer, 0, rect->py_min, 1,
p_x, rect->py_min, 1, rect->py_max - rect->py_min);
if ((p_x == rect->px_max - 1) ||
TIME_AFTER(*rb->current_tick, next_update))
{
next_update = *rb->current_tick + UPDATE_FREQ;
/* update screen part that was changed since last yield */
rb->lcd_update_rect(last_px, rect->py_min,
p_x - last_px + 1, rect->py_max - rect->py_min);
last_px = p_x;
}
#endif
a += x_step;
}
rect->valid = 0;
return 0;
}
static void mandelbrot_move(int x_factor, int y_factor)
{
long dx = (long)x_factor * x_delta;
long dy = (long)y_factor * y_delta;
x_min += dx;
x_max += dx;
y_min += dy;
y_max += dy;
}
static int mandelbrot_zoom(int factor)
{
int res;
long factor_x = (long)factor * x_delta;
long factor_y = (long)factor * y_delta;
x_min += factor_x;
x_max -= factor_x;
y_min += factor_y;
y_max -= factor_y;
res = recalc_parameters();
if (res) /* zoom not possible, revert */
{
mandelbrot_zoom(-factor);
}
return res;
}
static int mandelbrot_precision(int d)
{
int changed = 0;
/* Increase precision */
for (; d > 0; d--)
{
max_iter += max_iter / 2;
changed = 1;
}
/* Decrease precision */
for (; d < 0 && max_iter >= 15; d++)
{
max_iter -= max_iter / 3;
changed = 1;
}
return changed;
}