diff options
Diffstat (limited to 'src/uqm/flash.c')
-rw-r--r-- | src/uqm/flash.c | 805 |
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); +} + |