diff --git a/apps/plugins/pictureflow.c b/apps/plugins/pictureflow.c
index cd7e54c..e3e7836 100644
--- a/apps/plugins/pictureflow.c
+++ b/apps/plugins/pictureflow.c
@@ -111,6 +111,9 @@ typedef fb_data pix_t;
#define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
(REFLECT_HEIGHT * 5))
#define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
+#define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
+#define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
+#define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_ONE / 2)

#define SLIDE_CACHE_SIZE 100

@@ -235,7 +238,6 @@ static struct configdata config[] =
/** below we allocate the memory we want to use **/

static pix_t *buffer; /* for now it always points to the lcd framebuffer */
-static PFreal rays[LCD_WIDTH];
static uint16_t reflect_table[REFLECT_HEIGHT];
static struct slide_data center_slide;
static struct slide_data left_slides[MAX_SLIDES_COUNT];
@@ -337,6 +339,16 @@ static inline PFreal fmul(PFreal a, PFreal b)
return (a*b) >> PFREAL_SHIFT;
}

+/**
+ * This version preshifts each operand, which is useful when we know how many
+ * of the least significant bits will be empty, or are worried about overflow
+ * in a particular calculation
+ */
+static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
+{
+ return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
+}
+
/* ARMv5+ has a clz instruction equivalent to our function.
*/
#if (defined(CPU_ARM) && (ARM_ARCH > 4))
@@ -1154,23 +1166,28 @@ void reset_slides(void)
/**
Updates look-up table and other stuff necessary for the rendering.
Call this when the viewport size or slide dimension is changed.
+ *
+ * To calculate the offset that will provide the proper margin, we use the same
+ * projection used to render the slides. Assuming zo == 0, the solution for xc,
+ * the slide center, is:
+ * xp * xs * sin(r)
+ * xc = xp - xs * cos(r) + ────────────────
+ * z
+ * TODO: support moving the side slides toward or away from the camera
*/
-void recalc_table(void)
+void recalc_offsets(void)
{
- int w = (LCD_WIDTH + 1) / 2;
- int h = (LCD_HEIGHT + 1) / 2;
- int i;
- for (i = 0; i < w; i++) {
- PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2 * h);
- rays[w - i - 1] = -gg;
- rays[w + i] = gg;
- }
+ PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
+ PFreal xp = DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
+ PFREAL_ONE;
+ PFreal cosr, sinr;

itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
-
- offsetX = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE);
+ cosr = fcos(-itilt);
+ sinr = fsin(-itilt);
+ offsetX = xp - fmul(xs, cosr) + fmuln(xp,
+ fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)/CAM_DIST;
offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
- offsetX += center_margin << PFREAL_SHIFT;
}


@@ -1207,8 +1224,28 @@ static inline pix_t fade_color(pix_t c, unsigned int a)
#endif

/**
- Render a single slide
-*/
+ * Render a single slide
+ * Where xc is the slide's horizontal offset from center, xs is the horizontal
+ * on the slide from its center, zo is the slide's depth offset from the plane
+ * of the display, r is the angle at which the slide is tilted, and xp is the
+ * point on the display corresponding to xs on the slide, the projection
+ * formulas are:
+ *
+ * z * (xc + xs * cos(r))
+ * xp = ──────────────────────
+ * z + zo + xs * sin(r)
+ *
+ * z * (xc - xp) - xp * zo
+ * xs = ────────────────────────
+ * xp * sin(r) - z * cos(r)
+ *
+ * We use the xp projection once, to find the left edge of the slide on the
+ * display. From there, we use the xs reverse projection to find the horizontal
+ * offset from the slide center of each column on the screen, until we reach
+ * the right edge of the slide, or the screen. The reverse projection can be
+ * optimized by saving the numerator and denominator of the fraction, which can
+ * then be incremented by (z + zo) and sin(r) respectively.
+ */
void render_slide(struct slide_data *slide, const int alpha)
{
struct bitmap *bmp = surface(slide->slide_index);
@@ -1219,112 +1256,118 @@ void render_slide(struct slide_data *slide, const int alpha)

const int sw = bmp->width;
const int sh = bmp->height;
+ const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;

const int h = LCD_HEIGHT;
const int w = LCD_WIDTH;


- int distance = (h + slide->distance) * 100 / zoom;
- if (distance < 100 ) distance = 100; /* clamp distances */
- PFreal dist = distance * PFREAL_ONE;
- PFreal sdx = fcos(slide->angle);
- PFreal sdy = fsin(slide->angle);
- PFreal xs = slide->cx - sw * fdiv(sdx, dist) / 2;
-
- int xi = fmax(0, xs) >> PFREAL_SHIFT;
+ PFreal zo = (CAM_DIST_R + PFREAL_ONE * slide->distance) * 100 / zoom -
+ CAM_DIST_R;
+ PFreal cosr = fcos(slide->angle);
+ PFreal sinr = fsin(slide->angle);
+ PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
+ PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
+ (CAM_DIST_R + zo + fmul(xs,sinr)));
+
+ /* Since we're finding the screen position of the left edge of the slide,
+ * we round up.
+ */
+ int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
+ >> PFREAL_SHIFT;
+ xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
if (xi >= w) {
return;
}
-
+ xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
+ xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
+ xs = fdiv(xsnum, xsden);
+
+ xsnumi = -CAM_DIST_R - zo;
+ xsdeni = sinr;
int x;
- for (x = fmax(xi, 0); x < w; x++) {
- PFreal hity = 0;
- PFreal fk = rays[x];
- if (sdy) {
- fk = fk - fdiv(sdx, sdy);
- if (fk)
- hity = -fdiv(( rays[x] * distance
- - slide->cx
- + slide->cy * sdx / sdy), fk);
- }
-
- dist = distance * PFREAL_ONE + hity;
- if (dist < 0)
- continue;
-
- PFreal hitx = fmul(dist, rays[x]);
-
- PFreal hitdist = fdiv(hitx - slide->cx, sdx);
-
- const int column = (sw >> 1) + (hitdist >> PFREAL_SHIFT);
+ int dy = PFREAL_ONE;
+ for (x = xi; x < w; x++) {
+ int column = (xs - slide_left) / PFREAL_ONE;
if (column >= sw)
break;
- if (column < 0)
- continue;
-
- int y1 = (LCD_HEIGHT / 2) - 1;
- int y2 = y1 + 1;
- pix_t *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
- pix_t *pixel2 = &buffer[y2 * BUFFER_WIDTH + x];
- const int pixelstep = pixel2 - pixel1;
-
- int dy = dist / h;
- int p1 = (bmp->height - 1 - (DISPLAY_OFFS)) * PFREAL_ONE;
- int p2 = p1 + dy;
- const pix_t *ptr = &src[column * bmp->height];
-
- if (alpha == 256)
+ if (column > 0)
{
- while ((y1 >= 0) && (p1 >= 0))
- {
- *pixel1 = ptr[p1 >> PFREAL_SHIFT];
- p1 -= dy;
- y1--;
- pixel1 -= pixelstep;
- }
- while ((p2 < sh * PFREAL_ONE) && (y2 < h))
+ if (zo || slide->angle)
+ dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
+ int y1 = (LCD_HEIGHT / 2) - 1;
+ int y2 = y1 + 1;
+ pix_t *pixel1 = &buffer[y1 * BUFFER_WIDTH + x];
+ pix_t *pixel2 = pixel1 + BUFFER_WIDTH;
+ const int pixelstep = BUFFER_WIDTH;
+
+ int p1 = (bmp->height - 1 - (DISPLAY_OFFS)) * PFREAL_ONE;
+ int p2 = p1 + dy;
+ const pix_t *ptr = &src[column * bmp->height];
+
+ if (alpha == 256)
{
- *pixel2 = ptr[p2 >> PFREAL_SHIFT];
- p2 += dy;
- y2++;
- pixel2 += pixelstep;
+ while ((y1 >= 0) && (p1 >= 0))
+ {
+ *pixel1 = ptr[p1 >> PFREAL_SHIFT];
+ p1 -= dy;
+ y1--;
+ pixel1 -= pixelstep;
+ }
+ while ((p2 < sh * PFREAL_ONE) && (y2 < h))
+ {
+ *pixel2 = ptr[p2 >> PFREAL_SHIFT];
+ p2 += dy;
+ y2++;
+ pixel2 += pixelstep;
+ }
+ while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
+ (y2 < h))
+ {
+ int ty = (p2 >> PFREAL_SHIFT) - sh;
+ int lalpha = reflect_table[ty];
+ *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
+ p2 += dy;
+ y2++;
+ pixel2 += pixelstep;
+ }
}
- while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
- (y2 < h))
+ else
{
- int ty = (p2 >> PFREAL_SHIFT) - sh;
- int lalpha = reflect_table[ty];
- *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
- p2 += dy;
- y2++;
- pixel2 += pixelstep;
+ while ((y1 >= 0) && (p1 >= 0))
+ {
+ *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT],alpha);
+ p1 -= dy;
+ y1--;
+ pixel1 -= pixelstep;
+ }
+ while ((p2 < sh * PFREAL_ONE) && (y2 < h))
+ {
+ *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT],alpha);
+ p2 += dy;
+ y2++;
+ pixel2 += pixelstep;
+ }
+ while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
+ (y2 < h))
+ {
+ int ty = (p2 >> PFREAL_SHIFT) - sh;
+ int lalpha = (reflect_table[ty] * alpha + 128) >> 8;
+ *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
+ p2 += dy;
+ y2++;
+ pixel2 += pixelstep;
+ }
}
}
- else
- while ((y1 >= 0) && (p1 >= 0))
- {
- *pixel1 = fade_color(ptr[p1 >> PFREAL_SHIFT],alpha);
- p1 -= dy;
- y1--;
- pixel1 -= pixelstep;
- }
- while ((p2 < sh * PFREAL_ONE) && (y2 < h))
- {
- *pixel2 = fade_color(ptr[p2 >> PFREAL_SHIFT],alpha);
- p2 += dy;
- y2++;
- pixel2 += pixelstep;
- }
- while ((p2 < MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE) &&
- (y2 < h))
- {
- int ty = (p2 >> PFREAL_SHIFT) - sh;
- int lalpha = (reflect_table[ty] * alpha + 128) >> 8;
- *pixel2 = fade_color(ptr[sh - 1 - ty],lalpha);
- p2 += dy;
- y2++;
- pixel2 += pixelstep;
- }
+ if (zo || slide->angle)
+ {
+ xsnum += xsnumi;
+ xsden += xsdeni;
+ xs = fdiv(xsnum, xsden);
+ } else
+ xs += PFREAL_ONE;
+
}
/* let the music play... */
rb->yield();
@@ -1645,7 +1688,7 @@ int settings_menu(void)
rb->set_int("Spacing between slides", "", 1,
&slide_spacing,
NULL, 1, 0, 100, NULL );
- recalc_table();
+ recalc_offsets();
reset_slides();
break;

@@ -1653,27 +1696,27 @@ int settings_menu(void)
rb->set_int("Center margin", "", 1,
&center_margin,
NULL, 1, 0, 80, NULL );
- recalc_table();
+ recalc_offsets();
reset_slides();
break;

case 3:
rb->set_int("Number of slides", "", 1, &num_slides,
NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
- recalc_table();
+ recalc_offsets();
reset_slides();
break;

case 4:
rb->set_int("Zoom", "", 1, &zoom,
NULL, 1, 10, 300, NULL );
- recalc_table();
+ recalc_offsets();
reset_slides();
break;
case 5:
album_name_menu();
reset_track_list();
- recalc_table();
+ recalc_offsets();
reset_slides();
break;
case 6:
@@ -2037,7 +2080,7 @@ int main(void)
target = 0;
fade = 256;

- recalc_table();
+ recalc_offsets();
reset_slides();

char fpstxt[10];