/* * 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); }