summaryrefslogtreecommitdiff
path: root/src/uqm/flash.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/flash.c')
-rw-r--r--src/uqm/flash.c805
1 files changed, 805 insertions, 0 deletions
diff --git a/src/uqm/flash.c b/src/uqm/flash.c
new file mode 100644
index 0000000..2c73ed0
--- /dev/null
+++ b/src/uqm/flash.c
@@ -0,0 +1,805 @@
+/*
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// NOTE: A lot of this code is untested. Only highlite and overlay flash
+// areas, drawing directly to the screen, using a cache, are
+// currently in use.
+
+// NOTE:
+// - If you change the properties of the original CONTEXT, specifically the
+// dimensions and origin, you'll need to call Flash_preUpdate() before and
+// Flash_postUpdate() after that change. Note that this may change which
+// part of the screen is flashing.
+
+// TODO:
+// - During a few frames during the sequence, the frame to be displayed
+// is equal to a frame which was supplied as a parameter to the flash
+// sequence (instead of generated during the sequence). It is not
+// necessary to make a copy in this case. Instead, the original can be
+// used. I had code to do this, but this doesn't work anymore with the
+// addition of startNumer, endNumer, and denom. This code is still
+// present, but disabled with BEGIN_AND_END_FRAME_EXCEPTIONS.
+
+#define FLASH_INTERNAL
+#include "flash.h"
+
+#include "libs/log.h"
+#include "libs/memlib.h"
+#include "libs/threadlib.h"
+
+
+static FlashContext *Flash_create (CONTEXT gfxContext);
+static void Flash_fixState (FlashContext *context);
+static void Flash_nextState (FlashContext *context);
+static void Flash_clearCache (FlashContext *context);
+static void Flash_initCache (FlashContext *context);
+static void Flash_grabOriginal (FlashContext *context);
+static void Flash_blendFraction (FlashContext *context, int numer, int denom,
+ int *resNumer, int *resDenom);
+static void Flash_makeFrame (FlashContext *context,
+ FRAME dest, int numer, int denom);
+static inline void Flash_prepareCacheFrame (FlashContext *context,
+ COUNT index);
+static void Flash_drawFrame (FlashContext *context, FRAME frame);
+static void Flash_drawCacheFrame (FlashContext *context, COUNT index);
+static inline void Flash_drawUncachedFrame (FlashContext *context,
+ int numer, int denom);
+static inline void Flash_drawCachedFrame (FlashContext *context,
+ int numer, int denom);
+static void Flash_drawCurrentFrame (FlashContext *context);
+
+static CONTEXT workGfxContext;
+ // Off-screen internal drawing context
+
+static FlashContext *
+Flash_create (CONTEXT gfxContext)
+{
+ FlashContext *context = HMalloc (sizeof (FlashContext));
+
+ context->gfxContext = gfxContext;
+
+ context->original = 0;
+
+ context->startNumer = 0;
+ context->endNumer = 1;
+ context->denom = 1;
+
+ context->fadeInTime = Flash_DEFAULT_FADE_IN_TIME;
+ context->onTime = Flash_DEFAULT_ON_TIME;
+ context->fadeOutTime = Flash_DEFAULT_FADE_OUT_TIME;
+ context->offTime = Flash_DEFAULT_OFF_TIME;
+
+ context->frameTime = 0;
+
+ context->state = FlashState_off;
+ context->lastStateTime = 0;
+ context->lastFrameTime = 0;
+
+ context->started = false;
+ context->paused = false;
+
+ context->cache = 0;
+ context->cacheSize = Flash_DEFAULT_CACHE_SIZE;
+
+ context->lastFrameIndex = (COUNT) -1;
+
+ // TODO: Delete the context somewhere
+ if (!workGfxContext)
+ workGfxContext = CreateContext ("Flash.workGfxContext");
+
+ return context;
+}
+
+// 'startNumer / denom' is the brightness in the start state of the sequence.
+// 'endNumer / denom' is the brightness in the end state of the sequence.
+// These numbers are relative to the brighness of the original image.
+FlashContext *
+Flash_createHighlight (CONTEXT gfxContext, const RECT *rect)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ if (rect == NULL)
+ {
+ // No rectangle specified. It should be specified later with
+ // Flash_setRect(), before calling Flash_start().
+ context->rect.corner.x = 0;
+ context->rect.corner.y = 0;
+ context->rect.extent.width = 0;
+ context->rect.extent.height = 0;
+ }
+ else
+ context->rect = *rect;
+ context->type = FlashType_highlight;
+
+ return context;
+}
+
+FlashContext *
+Flash_createTransition (CONTEXT gfxContext, const POINT *origin,
+ FRAME first, FRAME final)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ context->type = FlashType_transition;
+
+ context->u.transition.first = first;
+ context->u.transition.final = final;
+ GetFrameRect (final, &context->rect);
+ context->rect.corner = *origin;
+
+ return context;
+}
+
+FlashContext *
+Flash_createOverlay (CONTEXT gfxContext, const POINT *origin, FRAME overlay)
+{
+ FlashContext *context = Flash_create (gfxContext);
+
+ context->type = FlashType_overlay;
+
+ if (origin == NULL || overlay == NULL) {
+ // No overlay specified. It should be specified later with
+ // Flash_setOverlay(), before calling Flash_start().
+ context->u.overlay.frame = NULL;
+ context->rect.corner.x = 0;
+ context->rect.corner.y = 0;
+ context->rect.extent.width = 0;
+ context->rect.extent.height = 0;
+ } else
+ Flash_setOverlay (context, origin, overlay);
+
+ return context;
+}
+
+// Set the current state. 'timeSpentInState' determines how much time should
+// be considered to be already spent in this state.
+void
+Flash_setState (FlashContext *context, FlashState state,
+ TimeCount timeSpentInState)
+{
+ TimeCount now;
+
+ now = GetTimeCounter ();
+
+ context->state = state;
+ Flash_fixState (context);
+
+ context->lastStateTime = now - timeSpentInState;
+ context->lastFrameTime = now;
+
+ if (context->started)
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_start (FlashContext *context)
+{
+ if (context->started)
+ {
+ log_add (log_Warning, "Flash_start() called on already started "
+ "FlashContext.\n");
+ return;
+ }
+
+ Flash_initCache (context);
+
+ context->started = true;
+ context->paused = false;
+
+ Flash_grabOriginal (context);
+ context->lastFrameIndex = 0;
+
+ Flash_fixState (context);
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_terminate (FlashContext *context)
+{
+ if (context->started)
+ {
+ // Restore the flash rectangle:
+ Flash_drawFrame (context, context->original);
+
+ Flash_clearCache (context);
+ HFree (context->cache);
+ DestroyDrawable (ReleaseDrawable (context->original));
+ }
+
+ HFree (context);
+}
+
+void
+Flash_pause (FlashContext *context)
+{
+ context->paused = true;
+}
+
+void
+Flash_continue (FlashContext *context)
+{
+ context->paused = false;
+}
+
+// Change the state to the next state as long as the current state has
+// a zero-time duration.
+static void
+Flash_fixState (FlashContext *context)
+{
+ TimeCount stateTime = 0;
+
+ for (;;) {
+ switch (context->state) {
+ case FlashState_fadeIn:
+ stateTime = context->fadeInTime;
+ break;
+ case FlashState_on:
+ stateTime = context->onTime;
+ break;
+ case FlashState_fadeOut:
+ stateTime = context->fadeOutTime;
+ break;
+ case FlashState_off:
+ stateTime = context->offTime;
+ break;
+ }
+ if (stateTime != 0)
+ break;
+ context->state = (context->state + 1) & 0x3;
+ }
+}
+
+static void
+Flash_nextState (FlashContext *context)
+{
+ context->state = (context->state + 1) & 0x3;
+ Flash_fixState (context);
+}
+
+void
+Flash_process (FlashContext *context)
+{
+ TimeCount now;
+
+ if (!context->started || context->paused)
+ return;
+
+ now = GetTimeCounter ();
+
+ switch (context->state)
+ {
+ case FlashState_fadeIn:
+ if (now >= context->lastStateTime + context->fadeInTime)
+ {
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ }
+ context->lastFrameTime = now;
+ break;
+ case FlashState_on:
+ if (now < context->lastStateTime + context->onTime)
+ return;
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ break;
+ case FlashState_fadeOut:
+ if (now >= context->lastStateTime + context->fadeOutTime)
+ {
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ }
+ context->lastFrameTime = now;
+ break;
+ case FlashState_off:
+ if (now < context->lastStateTime + context->offTime)
+ return;
+ Flash_nextState (context);
+ context->lastStateTime = now;
+ break;
+ }
+
+ Flash_drawCurrentFrame (context);
+}
+
+void
+Flash_setSpeed (FlashContext *context, TimeCount fadeInTime,
+ TimeCount onTime, TimeCount fadeOutTime, TimeCount offTime)
+{
+ context->fadeInTime = fadeInTime;
+ context->onTime = onTime;
+ context->fadeOutTime = fadeOutTime;
+ context->offTime = offTime;
+}
+
+// Determines how the brightness of the flashing changes.
+// For highlights:
+// 'startNumer / denom' is the brightness, at the start state of the flash.
+// 'endNumer / denom' is the brightness, at the end state of the flash.
+// For overlays:
+// 'startNumer / denom' is the brightness of the image to overlay, at
+// the start state of the flash.
+// 'endNumer / denom' is the brightness of the image to overlay, at
+// the end state of the flash.
+// For transitions:
+// 'startNumer / denom' is the brightness of the second image, at
+// the start state of the flash; '1 - startNumer / denom' is the
+// brightness of the first image at the start state of the flash.
+// 'endNumer / denom' is the brightness of the second image, at
+// the end state of the flash; '1 - endNumer / denom' is the
+// brightness of the first image at the end state of the flash.
+// These numbers are relative to the brighness of each original image.
+void
+Flash_setMergeFactors(FlashContext *context, int startNumer, int endNumer,
+ int denom) {
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->startNumer = startNumer;
+ context->endNumer = endNumer;
+ context->denom = denom;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Set the time between updates of the flash area.
+void
+Flash_setFrameTime (FlashContext *context, TimeCount frameTime)
+{
+ context->frameTime = frameTime;
+}
+
+// Returns the time when the flash area is to be updated.
+TimeCount
+Flash_nextTime (FlashContext *context)
+{
+ if (!context->started || context->paused)
+ return (TimeCount) -1;
+
+ if (context->state == FlashState_fadeIn ||
+ context->state == FlashState_fadeOut)
+ {
+ // When we're fading in or out, we need updates during
+ // the fade.
+ return context->lastFrameTime + context->frameTime;
+ }
+ else
+ {
+ // When the flash area is completely on or off, we don't
+ // need an update until we're ready to change state again.
+ if (context->state == FlashState_on)
+ return context->lastStateTime + context->onTime;
+ else /* context->state == FlashState_off */
+ return context->lastStateTime + context->offTime;
+ }
+}
+
+static void
+Flash_clearCache (FlashContext *context)
+{
+ COUNT i;
+
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ if (context->type == FlashType_transition ||
+ context->type == FlashType_overlay)
+ {
+ // First frame is not allocated by the flash code, so
+ // we shouldn't free it.
+ context->cache[0] = (FRAME) 0;
+ }
+ if (context->type == FlashType_transition)
+ {
+ // Final frame is not allocated by the flash code, so
+ // we shouldn't free it.
+ context->cache[context->cacheSize - 1] = (FRAME) 0;
+ }
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+
+ for (i = 0; i < context->cacheSize; i++)
+ {
+ if (context->cache[i] != (FRAME) 0)
+ {
+ DestroyDrawable (ReleaseDrawable (context->cache[i]));
+ context->cache[i] = (FRAME) 0;
+ }
+ }
+}
+
+void
+Flash_setRect (FlashContext *context, const RECT *rect)
+{
+ assert(context->type == FlashType_highlight);
+
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->rect = *rect;
+ context->lastFrameIndex = (COUNT) -1;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+void
+Flash_getRect (FlashContext *context, RECT *rect)
+{
+ assert (!context->type == FlashType_highlight);
+
+ *rect = context->rect;
+}
+
+void
+Flash_setOverlay (FlashContext *context, const POINT *origin, FRAME overlay)
+{
+ assert(context->type == FlashType_overlay);
+
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+
+ context->u.overlay.frame = overlay;
+ GetFrameRect (overlay, &context->rect);
+ context->rect.corner.x += origin->x;
+ context->rect.corner.y += origin->y;
+
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Call before you update the graphics in the currently flashing area,
+// or before you change the dimensions or origin of the graphics context.
+void
+Flash_preUpdate (FlashContext *context)
+{
+ if (context->started)
+ {
+ Flash_drawFrame (context, context->original);
+ Flash_clearCache (context);
+ }
+}
+
+// Call after you update the graphics in the currently flashing area,
+// or after you change the dimensions or origin of the graphics context.
+void
+Flash_postUpdate (FlashContext *context)
+{
+ if (context->started)
+ {
+ Flash_grabOriginal (context);
+ Flash_drawCurrentFrame (context);
+ }
+}
+
+// Pre: context->original has been initialised.
+static void
+Flash_initCache (FlashContext *context)
+{
+ COUNT i;
+
+ context->cache = HMalloc (context->cacheSize * sizeof (FRAME));
+ for (i = 0; i < context->cacheSize; i++)
+ context->cache[i] = (FRAME) 0;
+}
+
+void
+Flash_setCacheSize (FlashContext *context, COUNT size)
+{
+ assert (size == 0 || size >= 2);
+
+ if (context->cache != NULL)
+ {
+ Flash_clearCache (context);
+ HFree (context->cache);
+ context->cache = NULL;
+ }
+
+ context->cacheSize = size;
+
+ if (size != 0)
+ Flash_initCache (context);
+}
+
+COUNT
+Flash_getCacheSize (const FlashContext *context)
+{
+ return context->cacheSize;
+}
+
+static void
+Flash_grabOriginal (FlashContext *context)
+{
+ CONTEXT oldGfxContext;
+
+ if (context->original != (FRAME) 0)
+ DestroyDrawable (ReleaseDrawable (context->original));
+
+ oldGfxContext = SetContext (context->gfxContext);
+ context->original = CaptureDrawable (CopyContextRect (&context->rect));
+ SetContext (oldGfxContext);
+ FlushGraphics ();
+ // CopyContextRect() may have queued the command to read
+ // a rectangle from the screen; a FlushGraphics()
+ // is necessary to ensure that it can actually be used.
+}
+
+static inline void
+Flash_blendFraction (FlashContext *context, int numer, int denom,
+ int *resNumer, int *resDenom)
+{
+ // This function merges two fractions (F0 and F1),
+ // based on another fraction (P) (yielding R).
+ // F0 = context->u.highlight.startNumer / context->u.highlight.denom
+ // F1 = context->u.highlight.endNumer / context->u.highlight.denom
+ // P = *numer / *denom
+ // R = P * F1 + (1 - P) * F0
+ // = numer * context->endNumer / (denom * context->denom) +
+ // (denom - numer) * startNumer / denom * context->denom
+
+ assert (numer >= 0 && numer <= denom);
+
+ *resNumer = numer * context->endNumer +
+ (denom - numer) * context->startNumer;
+ *resDenom = denom * context->denom;
+}
+
+static void
+Flash_makeFrame (FlashContext *context, FRAME dest, int numer, int denom)
+{
+ CONTEXT oldGfxContext;
+ STAMP s;
+ int blendedNumer;
+ int blendedDenom;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+
+ Flash_blendFraction (context, numer, denom, &blendedNumer, &blendedDenom);
+
+ oldGfxContext = SetContext (workGfxContext);
+ SetContextFGFrame (dest);
+
+ switch (context->type) {
+ case FlashType_highlight:
+ {
+ // Clear the destination just in case
+ SetContextBackGroundColor (BUILD_COLOR_RGBA (0, 0, 0, 0));
+ ClearDrawable ();
+ // Draw the frame at modulated strength (0 < strength <= 128)
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ADDITIVE,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = context->original;
+ DrawStamp (&s);
+ break;
+ }
+ case FlashType_transition:
+ {
+ FRAME first;
+ FRAME final;
+
+ first = context->u.transition.first;
+ if (first == (FRAME) 0)
+ first = context->original;
+ final = context->u.transition.final;
+ if (final == (FRAME) 0)
+ final = context->original;
+
+ // Draw the first frame at full strength
+ SetContextDrawMode (DRAW_REPLACE_MODE);
+ s.frame = first;
+ DrawStamp (&s);
+ // Merge in the final frame
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ALPHA,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = final;
+ DrawStamp (&s);
+ break;
+ }
+ case FlashType_overlay:
+ {
+ POINT oldOrigin;
+
+ // Draw the original at full strength
+ SetContextDrawMode (DRAW_REPLACE_MODE);
+ s.frame = context->original;
+ DrawStamp (&s);
+ // Add or subtract the overlay at partial strength
+ SetContextDrawMode (MAKE_DRAW_MODE (DRAW_ADDITIVE,
+ DRAW_FACTOR_1 * blendedNumer / blendedDenom));
+ s.frame = context->u.overlay.frame;
+ // Offset the draw origin to hit the right area
+ oldOrigin = SetContextOrigin (GetFrameHot (s.frame));
+ DrawStamp (&s);
+ SetContextOrigin (oldOrigin);
+ break;
+ }
+ }
+
+ SetContext (oldGfxContext);
+}
+
+// Prepare an entry in the cache.
+static inline void
+Flash_prepareCacheFrame (FlashContext *context, COUNT index)
+{
+ if (context->cache[index] != (FRAME) 0)
+ return;
+
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ if (index == 0 && context->type == FlashType_overlay)
+ context->cache[index] = context->original;
+ else if (index == 0 && context->type == FlashType_transition)
+ context->cache[index] = context->u.transition.first != (FRAME) 0 ?
+ context->u.transition.first : context->original;
+ else if (index == context->cacheSize - 1 &&
+ context->type == FlashType_transition)
+ context->cache[index] = context->u.transition.final != (FRAME) 0 ?
+ context->u.transition.final : context->original;
+ else
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+ {
+ context->cache[index] = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ context->rect.extent.width, context->rect.extent.height, 1));
+ Flash_makeFrame (context, context->cache[index],
+ index, context->cacheSize - 1);
+ }
+}
+
+static void
+Flash_drawFrame (FlashContext *context, FRAME frame)
+{
+ CONTEXT oldGfxContext;
+ STAMP stamp;
+
+ oldGfxContext = SetContext (context->gfxContext);
+
+ stamp.origin = context->rect.corner;
+ stamp.frame = frame;
+ DrawStamp(&stamp);
+
+ SetContext (oldGfxContext);
+}
+
+static void
+Flash_drawCacheFrame (FlashContext *context, COUNT index)
+{
+ FRAME frame;
+
+ if (context->lastFrameIndex == index)
+ return;
+
+ frame = context->cache[index];
+ Flash_drawFrame (context, frame);
+ context->lastFrameIndex = index;
+}
+
+static inline void
+Flash_drawUncachedFrame (FlashContext *context, int numer, int denom)
+{
+#ifdef BEGIN_AND_END_FRAME_EXCEPTIONS
+ // 'lastFrameIndex' is 0 for the first image, 1 for the final
+ // image, and 2 otherwise.
+
+ if (numer == 0 && context->type == FlashType_overlay)
+ {
+ if (context->lastFrameIndex != 0)
+ return;
+
+ Flash_drawFrame (context, context->original);
+ context->lastFrameIndex = 0;
+ return;
+ }
+ else if (numer == 0 && context->type == FlashType_transition)
+ {
+ if (context->lastFrameIndex == 0)
+ return;
+
+ Flash_drawFrame (context, context->u.transition.first);
+ context->lastFrameIndex = 0;
+ return;
+ }
+ else if (numer == denom && context->type == FlashType_transition)
+ {
+ if (context->lastFrameIndex == 1)
+ return;
+
+ Flash_drawFrame (context, context->u.transition.final);
+ context->lastFrameIndex = 1;
+ return;
+ }
+
+ context->lastFrameIndex = 2;
+#endif /* BEGIN_AND_END_FRMAE_EXCEPTIONS */
+
+ {
+ // Painting to the screen; we need a temporary frame to draw to.
+ FRAME work;
+
+ work = CaptureDrawable (CreateDrawable (WANT_PIXMAP,
+ context->rect.extent.width, context->rect.extent.height, 1));
+ Flash_makeFrame (context, work, numer, denom);
+ Flash_drawFrame (context, work);
+
+ DestroyDrawable (ReleaseDrawable (work));
+ }
+}
+
+static inline void
+Flash_drawCachedFrame (FlashContext *context, int numer, int denom)
+{
+ COUNT cachePos;
+
+ cachePos = ((context->cacheSize - 1) * numer + (denom / 2)) / denom;
+ Flash_prepareCacheFrame (context, cachePos);
+ Flash_drawCacheFrame (context, cachePos);
+}
+
+static void
+Flash_drawCurrentFrame (FlashContext *context)
+{
+ int numer;
+ int denom;
+
+ if (context->state == FlashState_off)
+ {
+ numer = 0;
+ denom = 1;
+ }
+ else if (context->state == FlashState_on)
+ {
+ numer = 1;
+ denom = 1;
+ }
+ else
+ {
+ TimeCount now = GetTimeCounter ();
+
+ if (context->state == FlashState_fadeIn)
+ denom = (int) context->fadeInTime;
+ else
+ denom = (int) context->fadeOutTime;
+
+ numer = (int) (now - context->lastStateTime);
+
+ if (numer > denom)
+ numer = denom;
+
+ if (context->state == FlashState_fadeOut)
+ numer = (int) context->fadeOutTime - numer;
+ }
+
+ if (context->cacheSize == 0)
+ Flash_drawUncachedFrame (context, numer, denom);
+ else
+ Flash_drawCachedFrame (context, numer, denom);
+}
+