/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 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, see . * */ #include "m4/m4_types.h" #include "m4/adv_r/adv_control.h" #include "m4/core/errors.h" #include "m4/core/imath.h" #include "m4/graphics/krn_pal.h" #include "m4/graphics/gr_pal.h" #include "m4/graphics/gr_series.h" #include "m4/gui/gui_sys.h" #include "m4/gui/gui_vmng.h" #include "m4/platform/keys.h" #include "m4/vars.h" #include "m4/m4.h" #include "m4/platform/timer.h" namespace M4 { #define _GP(X) _G(krnPal)._##X #define BACKGROUND_HEIGHT (int32)639 #define GREY_START (IS_RIDDLE ? 21 : 32) #define GREY_END (IS_RIDDLE ? 58 : 63) #define NUM_GREYS (1 + GREY_END - GREY_START) #define FREE_START (GREY_END + 1) #define FREE_END 255 #define NUM_FREE (255 - FREE_START + 1) static HotkeyCB remember_esc_key; void krn_pal_game_task() { g_engine->pal_game_task(); } static int32 screen_height(Buffer *grey_screen) { return imath_min(BACKGROUND_HEIGHT + _G(kernel).letter_box_y, grey_screen->h); } static void grey_fade(RGB8 *pal, int32 to_from_flag, int32 from, int32 to, int32 steps, int32 delay) { RGB8 *working = (RGB8 *)mem_alloc(sizeof(RGB8) * 256, STR_FADEPAL); // perform the fade for (int i = 1; i < steps; i++) { for (int j = from; j <= to; j++) { if (to_from_flag == TO_GREY) { // fade to grey from full color working[j].r = (Byte)((int)pal[j].r + ((((int)_GP(fadeToMe)[j].r - (int)pal[j].r) * i) / steps)); working[j].g = (Byte)((int)pal[j].g + ((((int)_GP(fadeToMe)[j].g - (int)pal[j].g) * i) / steps)); working[j].b = (Byte)((int)pal[j].b + ((((int)_GP(fadeToMe)[j].b - (int)pal[j].b) * i) / steps)); } else if (to_from_flag == TO_COLOR) { // fade from grey to full color working[j].r = (Byte)((int)_GP(fadeToMe)[j].r + ((((int)pal[j].r - (int)_GP(fadeToMe)[j].r) * i) / steps)); working[j].g = (Byte)((int)_GP(fadeToMe)[j].g + ((((int)pal[j].g - (int)_GP(fadeToMe)[j].g) * i) / steps)); working[j].b = (Byte)((int)_GP(fadeToMe)[j].b + ((((int)pal[j].b - (int)_GP(fadeToMe)[j].b) * i) / steps)); } else { //fade from grey to black working[j].r = (Byte)((int)_GP(fadeToMe)[j].r - ((((int)_GP(fadeToMe)[j].r) * i) / steps)); working[j].g = (Byte)((int)_GP(fadeToMe)[j].g - ((((int)_GP(fadeToMe)[j].g) * i) / steps)); working[j].b = (Byte)((int)_GP(fadeToMe)[j].b - ((((int)_GP(fadeToMe)[j].b) * i) / steps)); } } gr_pal_set_range(working, from, to - from + 1); ///set pal 21-255 // Time delay of "delay" milliseconds g_events->delay(delay); } // Eliminate round off error if (to_from_flag == TO_GREY) { gr_pal_set_range(_GP(fadeToMe), from, to - from + 1); ///set pal 21-255 } else if (to_from_flag == TO_COLOR) { gr_pal_set_range(pal, from, to - from + 1); ///set pal 21-255 } else { for (int i = from; i <= to; i++) { pal[i].r = pal[i].g = pal[i].b = 0; } gr_pal_set_range(pal, from, to - from + 1); ///set pal 21-255 } mem_free(working); } // screen is the currently displayed screen // screenPicture is the data to restore the screen with // note: color 0 doesn't fade. static void create_luminance_map(RGB8 *pal) { for (int i = GREY_START; i <= FREE_END; i++) { const Byte luminance = (Byte)((pal[i].r + pal[i].g + pal[i].b) / 3); _GP(fadeToMe)[i].g = luminance; // Orion Burger uses green shading, Riddle uses grey shading _GP(fadeToMe)[i].r = _GP(fadeToMe)[i].b = IS_RIDDLE ? luminance : 0; } } // finds the best matches for the in the greys in the grey ramp range using the free range greys // used to map greys out of the grey ramp area, and then again to map the grey ramp out of the grey ramp area! static void make_translation_table(RGB8 *pal) { for (int32 i = 0; i < NUM_GREYS; i++) { int32 bestMatch = FREE_START; // assume the first of the free indexes is best match to start with int32 minDist = 255; // assume that it's really far away to start with if (!(i & 0x3ff)) { digi_read_another_chunk(); midi_loop(); } // look for best match in the free indexes for the greys in GREY_START-GREY_END range (we need these available) const int32 matchGrey = pal[GREY_START + i].g; // Use green instead of red cause we're having a green screen for (int32 j = FREE_START; j <= FREE_END; j++) { const int32 tryGrey = pal[j].g; if (imath_abs(tryGrey - matchGrey) < minDist) { minDist = imath_abs(tryGrey - matchGrey); bestMatch = j; } if (minDist == 0) break; // no need to continue searching if we found a perfect match } _GP(translation)[i] = (uint8)bestMatch; } } void krn_fade_to_grey(RGB8 *pal, int32 steps, int32 delay) { if (_G(kernel).fading_to_grey) { return; } _G(kernel).fading_to_grey = true; Buffer *grey_screen = _G(gameDrawBuff)->get_buffer(); _GP(fadeToMe) = (RGB8 *)mem_alloc(sizeof(RGB8) * 256, STR_FADEPAL); _GP(trick) = (RGB8 *)mem_alloc(sizeof(RGB8) * 256, STR_FADEPAL); _GP(picPal) = (RGB8 *)mem_alloc(sizeof(RGB8) * 256, STR_FADEPAL); memcpy(_GP(picPal), pal, sizeof(RGB8) * 256); create_luminance_map(pal); grey_fade(pal, TO_GREY, GREY_START, GREY_END, steps, delay); // Make translation table to translate colors using entries 59-255 into 21-58 range for (int32 i = 0; i < (IS_RIDDLE ? 64 : 32); i++) { int32 bestMatch = IS_RIDDLE ? 63 : 65; int32 minDist = 255; for (int32 j = FREE_START; j <= 255; j++) { if (imath_abs((_GP(fadeToMe)[j].r >> 2) - i) < minDist) { minDist = imath_abs((_GP(fadeToMe)[j].r >> 2) - i); bestMatch = j; } if (minDist == 0) // No need to continue searching if we found a perfect match break; } _GP(translation)[i] = (uint8)bestMatch; } // Palette now grey scale. Remap any pixels which are in the range 21-58 to the range 53-255 // because we need to use those palette entries soon uint8 *tempPtr = grey_screen->data; // Note: this loop should be y0 to y1, x0 to x1, not a stride*h loop. for (int32 i = 0; i < (grey_screen->stride * grey_screen->h); i++) { if ((*tempPtr >= GREY_START) && (*tempPtr <= GREY_END)) { // Must move the pixel index to the best match in FREE_START-FREE_END range with _GP(translation) table *tempPtr = _GP(translation)[*tempPtr - GREY_START]; } tempPtr++; if (!(i & 0x3ff)) { _G(digi).task(); _G(midi).task(); } } RestoreScreens(MIN_VIDEO_X, MIN_VIDEO_Y, MAX_VIDEO_X, MAX_VIDEO_Y); // Make new trickPal with grey-scale ramp entries and load it into VGA registers memcpy(_GP(trick), _GP(fadeToMe), sizeof(RGB8) * 256); // trick pal is the greyed version plus the grey ramp overlayed on top const byte grey_step = 256 / NUM_GREYS; byte grey_ramp = 0; for (int32 i = GREY_START; i <= GREY_END; i++) { _GP(trick)[i].g = grey_ramp; _GP(trick)[i].r = _GP(trick)[i].b = IS_RIDDLE ? grey_ramp : 0; grey_ramp += grey_step; } gr_pal_set_range(_GP(trick), GREY_START, NUM_GREYS); remap_buffer_with_luminance_map(grey_screen, 0, 0, grey_screen->w - 1, screen_height(grey_screen) - 1); _G(gameDrawBuff)->release(); RestoreScreens(MIN_VIDEO_X, MIN_VIDEO_Y, MAX_VIDEO_X, MAX_VIDEO_Y); } void krn_fade_from_grey(RGB8 *pal, int32 steps, int32 delay, int32 fadeType) { if (!_G(kernel).fading_to_grey) { return; } // Get the screen Buffer *grey_screen = _G(gameDrawBuff)->get_buffer(); // load original faded greys into the free indexes (no pixels have these indexes yet) gr_pal_set_range(_GP(fadeToMe), FREE_START, NUM_FREE); // Load fadeToMe colors into VGA make_translation_table(_GP(trick)); // This is used in fade_to_grey too! // for every pixel in the screen, move any pixel in the GREY_START-GREY_END range out in to the free range uint8 *tempPtr = grey_screen->data; // note: this loop should be y0 to y1, x0 to x1, not a stride*h loop. for (int32 i = 0; i < (grey_screen->stride * grey_screen->h); ++i) { if (!(i & 0x3ff)) { _G(digi).task(); _G(midi).task(); } // if the pixel is within the GREY range, move it to where the _GP(translation) table says if ((*tempPtr >= GREY_START) && (*tempPtr <= GREY_END)) { *tempPtr = _GP(translation)[*tempPtr - GREY_START]; } tempPtr++; } // Remapped indexes out of grey ramp RestoreScreens(MIN_VIDEO_X, MIN_VIDEO_Y, MAX_VIDEO_X, MAX_VIDEO_Y); // Setting grey ramp indexes back to picture greys gr_pal_set_range(_GP(fadeToMe), GREY_START, NUM_GREYS); // get the rest of the original re-luminance colors //recopy screenPicture to screen to restore original pixels krn_UnsetGreyVideoMode(); RestoreScreens(0, 0, MAX_VIDEO_X, MAX_VIDEO_Y); memcpy(pal, _GP(picPal), sizeof(RGB8) * 256); ws_RefreshWoodscriptBuffer(_G(game_bgBuff)->get_buffer(), &(_G(currentSceneDef).depth_table[0]), _G(screenCodeBuff)->get_buffer(), (uint8 *)&_G(master_palette)[0], _G(inverse_pal)->get_ptr()); _G(game_bgBuff)->release(); _G(inverse_pal)->release(); RestoreScreens(MIN_VIDEO_X, MIN_VIDEO_Y, MAX_VIDEO_X, MAX_VIDEO_Y); grey_fade(pal, fadeType, GREY_START, FREE_END, steps, delay); mem_free((char *)_GP(trick)); mem_free((char *)_GP(fadeToMe)); mem_free((char *)_GP(picPal)); _G(kernel).fading_to_grey = false; _G(gameDrawBuff)->release(); gr_pal_set(_G(master_palette)); } void kernel_examine_inventory_object(const char *picName, RGB8 *pal, int steps, int delay, int32 x, int32 y, int32 triggerNum, const char *digiName, int32 digiTrigger) { remember_esc_key = GetSystemHotkey(KEY_ESCAPE); RemoveSystemHotkey(KEY_ESCAPE); interface_hide(); _GP(exam_saved_hotspots) = _G(currentSceneDef).hotspots; _G(currentSceneDef).hotspots = nullptr; _GP(myFadeTrigger) = kernel_trigger_create(triggerNum); krn_fade_to_grey(pal, steps, delay); _GP(seriesHash) = series_load(picName, -1, pal); // Preload sprite so we can unload it gr_pal_set_range(pal, FREE_START, NUM_FREE); // Set that series colors into VGA RestoreScreens(MIN_VIDEO_X, MIN_VIDEO_Y, MAX_VIDEO_X, MAX_VIDEO_Y); Buffer *grey_screen = _G(gameDrawBuff)->get_buffer(); krn_SetGreyVideoMode( // Grey rectangle 0, 0, MAX_VIDEO_X, screen_height(grey_screen) + _G(kernel).letter_box_y, // Color rectangle x, y, x + ws_get_sprite_width(_GP(seriesHash), 0) - 1, y + ws_get_sprite_height(_GP(seriesHash), 0) - 1); _G(gameDrawBuff)->release(); // Play the sprite series as a loop int32 status; ScreenContext *game_buff_ptr = vmng_screen_find(_G(gameDrawBuff), &status); _GP(seriesAnim8) = series_play_xy(picName, -1, FORWARD, x - game_buff_ptr->x1, y - game_buff_ptr->y1, 100, 0, 7, -1); if (digiName) { digi_play(digiName, 1, 255, digiTrigger); } player_set_commands_allowed(true); cycleEngines(_G(game_bgBuff)->get_buffer(), &(_G(currentSceneDef).depth_table[0]), _G(screenCodeBuff)->get_buffer(), (uint8 *)&_G(master_palette)[0], _G(inverse_pal)->get_ptr(), true); game_pause(true); _G(inverse_pal)->release(); _G(game_bgBuff)->release(); pauseEngines(); } void kernel_examine_inventory_object(const char *picName, int steps, int delay, int32 x, int32 y, int32 triggerNum, const char *digiName, int32 digiTrigger) { kernel_examine_inventory_object(picName, _G(master_palette), steps, delay, x, y, triggerNum, digiName, digiTrigger); } void kernel_unexamine_inventory_object(RGB8 *pal, int steps, int delay) { if (!_GP(seriesAnim8) || _GP(seriesHash) < 0) return; player_set_commands_allowed(false); game_pause(false); unpauseEngines(); terminateMachine(_GP(seriesAnim8)); series_unload(_GP(seriesHash)); _GP(seriesAnim8) = nullptr; _GP(seriesHash) = 0; Buffer *grey_screen = _G(gameDrawBuff)->get_buffer(); krn_SetGreyVideoMode(0, 0, MAX_VIDEO_X, screen_height(grey_screen) + _G(kernel).letter_box_y, -1, -1, -1, -1); _G(gameDrawBuff)->release(); krn_pal_game_task(); krn_fade_from_grey(pal, steps, delay, TO_COLOR); krn_pal_game_task(); // Set in kernel_examine_inventory_object (above) kernel_trigger_dispatchx(_GP(myFadeTrigger)); RestoreScreens(0, 0, MAX_VIDEO_X, MAX_VIDEO_Y); _G(currentSceneDef).hotspots = _GP(exam_saved_hotspots); interface_show(); AddSystemHotkey(KEY_ESCAPE, remember_esc_key); } // This is an inplace remap // fadeToMe must already have been set up to correspond to the image on the screen void remap_buffer_with_luminance_map(Buffer *src, int32 x1, int32 y1, int32 x2, int32 y2) { if ((!src) || (!src->data)) return; // WORKAROUND: Fix original bounding that could result in buffer overruns on final y2 line if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x2 >= src->w) x2 = src->w - 1; if (y2 >= src->h) y2 = src->h - 1; if (x2 <= x1 || y2 <= y1) return; x2 -= x1; y2 -= y1; for (int32 y = 0; y <= y2; y++) { uint8 *ptr = &src->data[(y + y1) * src->stride + x1]; for (int32 x = 0; x <= x2; x++) { // Remap the greyed out pixel to the closest grey in GREY_START to GREY_END range // shift right 3, takes a 255 value and makes it out of 32 (the number of greys in reduced grey ramp) ptr[x] = (uint8)(GREY_START + (_GP(fadeToMe)[ptr[x]].g >> 3)); // Use green instead of red cause we're having a green screen } if (!(y & 0xff)) { _G(digi).task(); _G(midi).task(); } } } void krn_SetGreyVideoMode(int32 grey_x1, int32 grey_y1, int32 grey_x2, int32 grey_y2, int32 color_x1, int32 color_y1, int32 color_x2, int32 color_y2) { _GP(greyAreaX1) = grey_x1; _GP(greyAreaY1) = grey_y1; _GP(greyAreaX2) = grey_x2; _GP(greyAreaY2) = grey_y2; _GP(colorAreaX1) = color_x1; _GP(colorAreaY1) = color_y1; _GP(colorAreaX2) = color_x2; _GP(colorAreaY2) = color_y2; _GP(greyVideoMode) = true; } void krn_UnsetGreyVideoMode() { _GP(greyAreaX1) = -1; _GP(greyAreaY1) = -1; _GP(greyAreaX2) = -1; _GP(greyAreaY2) = -1; _GP(colorAreaX1) = -1; _GP(colorAreaY1) = -1; _GP(colorAreaX2) = -1; _GP(colorAreaY2) = -1; _GP(greyVideoMode) = false; } bool krn_GetGreyMode() { return _GP(greyVideoMode); } void krn_UpdateGreyArea(Buffer *greyOutThisBuffer, int32 scrnX, int32 scrnY, int32 greyX1, int32 greyY1, int32 greyX2, int32 greyY2) { if ((!_GP(greyVideoMode)) || (!greyOutThisBuffer) || (!greyOutThisBuffer->data)) { return; } int32 x1 = imath_max(greyX1 + scrnX, _GP(greyAreaX1)); int32 y1 = imath_max(greyY1 + scrnY, _GP(greyAreaY1)); const int32 x2 = imath_min(greyX2 + scrnX, _GP(greyAreaX2)); int32 y2 = imath_min(greyY2 + scrnY, _GP(greyAreaY2)); if ((x1 > x2) || (y1 > y2)) return; bool finished = false; if (!finished) { if (y1 < _GP(colorAreaY1)) { remap_buffer_with_luminance_map(greyOutThisBuffer, x1 - scrnX, y1 - scrnY, x2 - scrnX, imath_min(y2, _GP(colorAreaY1) - 1) - scrnY); y1 = imath_min(y2, _GP(colorAreaY1)); if (y1 >= y2) finished = true; } } if (!finished) { if (y2 > _GP(colorAreaY2)) { remap_buffer_with_luminance_map(greyOutThisBuffer, x1 - scrnX, imath_max(y1, _GP(colorAreaY2) + 1) - scrnY, x2 - scrnX, y2 - scrnY); y2 = imath_max(y1, _GP(colorAreaY2)); if (y1 >= y2) finished = true; } } if (!finished) { if (x1 < _GP(colorAreaX1)) { remap_buffer_with_luminance_map(greyOutThisBuffer, x1 - scrnX, y1 - scrnY, imath_min(x2, _GP(colorAreaX1) - 1) - scrnX, y2 - scrnY); x1 = imath_min(x2, _GP(colorAreaX1)); if (x1 >= x2) finished = true; } } if (!finished) { if (x2 > _GP(colorAreaX2)) { remap_buffer_with_luminance_map(greyOutThisBuffer, imath_max(x1, _GP(colorAreaX2) + 1) - scrnX, y1 - scrnY, x2 - scrnX, y2 - scrnY); } } } void krn_ChangeBufferLuminance(Buffer *target, int32 percent) { uint8 luminancePal[256]; // Parameter verification if ((!target) || (!target->data)) { return; } if ((percent < 0) || (percent == 100)) { return; } if (percent == 0) { gr_color_set(__BLACK); gr_buffer_rect_fill(target, 0, 0, target->w, target->h); return; } // Calculate the frac16 form of the percent const frac16 fracPercent = (percent * 255) / 100; // Get the palette and the inverse palette RGB8 *pal = &_G(master_palette)[0]; uint8 *inverse_palette = _G(inverse_pal)->get_ptr(); if ((!pal) || (!inverse_palette)) { return; } // Calculate the luminance Pal table for (int32 i = 0; i < 256; i++) { const int32 r = ((((pal[i].r * fracPercent) >> 10) >> 1)) & 0x1f; const int32 g = ((((pal[i].g * fracPercent) >> 10) >> 1)) & 0x1f; const int32 b = ((((pal[i].b * fracPercent) >> 10) >> 1)) & 0x1f; luminancePal[i] = inverse_palette[(r << 10) + (g << 5) + b]; } // Note: this loop should be y0 to y1, x0 to x1, not a stride*h loop. // Loop through every pixel replacing it with the index into the luminance table uint8 *tempPtr = target->data; for (int32 y = 0; y < target->h; y++) { for (int32 x = 0; x < target->stride; x++) { *tempPtr = luminancePal[*tempPtr]; tempPtr++; //pixel = *tempPtr; } } _G(inverse_pal)->release(); } static void pal_fade_callback(frac16 myMessage) { _G(pal_fade_in_progress) = false; kernel_trigger_dispatchx((int32)myMessage); } void pal_fade_init(RGB8 *origPalette, int32 firstPalEntry, int32 lastPalEntry, int32 targetPercent, int32 numTicks, int32 triggerNum) { if ((!origPalette) || (firstPalEntry < 0) || (lastPalEntry > 255) || (firstPalEntry > lastPalEntry)) return; if ((targetPercent < 0) || (targetPercent > 100)) return; _GP(myFadeReq) = true; _GP(myFadeFinished) = false; _GP(myFadeStartTime) = timer_read_60(); _GP(myFadeEndDelayTime) = timer_read_60(); _GP(myFadeStartIndex) = firstPalEntry; _GP(myFadeEndIndex) = lastPalEntry; _GP(myFadeEndTime) = _GP(myFadeStartTime) + numTicks; _GP(myFadeTrigger) = kernel_trigger_create(triggerNum); _GP(myFadeStartPercentFrac) = _GP(myFadeCurrPercentFrac); _GP(myFadePercentFrac) = DivSF16(targetPercent << 16, 100 << 16); // Disable_end_user_hot_keys(); _G(pal_fade_in_progress) = true; } void pal_fade_init(int32 firstPalEntry, int32 lastPalEntry, int32 targetPercent, int32 numTicks, int32 triggerNum) { pal_fade_init(_G(master_palette), firstPalEntry, lastPalEntry, targetPercent, numTicks, triggerNum); } void disable_player_commands_and_fade_init(int trigger) { player_set_commands_allowed(false); pal_fade_init(_G(master_palette), _G(kernel).first_fade, 255, 0, 30, trigger); } static void pal_fade_update(RGB8 *origPalette) { const int32 currTime = timer_read_60(); if (currTime >= _GP(myFadeEndDelayTime)) { // If the delay has expired, fade more frac16 tempFrac2; if (currTime >= _GP(myFadeEndTime)) { tempFrac2 = _GP(myFadePercentFrac); _GP(myFadeStartPercentFrac) = _GP(myFadePercentFrac); _GP(myFadeFinished) = true; } else if (currTime <= _GP(myFadeStartTime)) { return; } else { const frac16 tempFrac = DivSF16((currTime - _GP(myFadeStartTime)) << 16, (_GP(myFadeEndTime) - _GP(myFadeStartTime)) << 16); tempFrac2 = MulSF16(tempFrac, _GP(myFadePercentFrac) - _GP(myFadeStartPercentFrac)) + _GP(myFadeStartPercentFrac); } _GP(myFadeCurrPercentFrac) = tempFrac2; for (int32 i = _GP(myFadeStartIndex); i <= _GP(myFadeEndIndex); i++) { _GP(myFXPalette)[i].r = (Byte)(MulSF16(origPalette[i].r << 16, tempFrac2) >> 16); _GP(myFXPalette)[i].g = (Byte)(MulSF16(origPalette[i].g << 16, tempFrac2) >> 16); _GP(myFXPalette)[i].b = (Byte)(MulSF16(origPalette[i].b << 16, tempFrac2) >> 16); } // Recalculate the end delay time again _GP(myFadeEndDelayTime) = currTime + _GP(myFadeDelayTicks); // Recalculate the end delay time again // Must refresh the DAC _GP(myFadeDACrefresh) = true; } } void clear_DAC() { RGB8 color; color.r = color.b = color.g = 0; for (int i = 0; i < 256; i++) gr_pal_set_entry(i, &color); } void pal_fade_set_start(RGB8 *origPalette, int32 percent) { pal_fade_init(origPalette, _G(kernel).first_fade, 255, percent, 0, (uint)-1); pal_fade_update(origPalette); pal_fx_update(); } void pal_fade_set_start(int32 percent) { pal_fade_set_start(_G(master_palette), percent); } static void pal_cycle_callback(frac16 myMessage) { kernel_trigger_dispatchx((uint32)myMessage); } void pal_cycle_init(int32 firstPalEntry, int32 lastPalEntry, int32 delayTicks, int32 totalTicks, int32 triggerNum) { // Validation if ((firstPalEntry < 0) || (lastPalEntry > 255) || (firstPalEntry > lastPalEntry)) return; if (delayTicks <= 0) return; _GP(myCycleReq) = true; _GP(myCycleFinished) = false; _GP(myCycleDelayTicks) = delayTicks; _GP(myCycleStartTime) = timer_read_60(); _GP(myCycleEndDelayTime) = timer_read_60(); _GP(myCycleStartIndex) = firstPalEntry; _GP(myCycleEndIndex) = lastPalEntry; _GP(myCycleTrigger) = kernel_trigger_create(triggerNum); // Returned when myCycleEndTime is reached if (totalTicks > 0) { // If totalTicks > 0, calculate end time _GP(myCycleEndTime) = _GP(myCycleStartTime) + totalTicks; _GP(myCycleNeverStopCycling) = false; } else if (totalTicks < 0) { // If totalTicks < 0, never stop the cycling _GP(myCycleNeverStopCycling) = true; } else { // If totalTicks is 0, stop cycling now _GP(myCycleReq) = false; _GP(myCycleFinished) = true; } } bool pal_cycle_active() { return _GP(myCycleReq); } void pal_cycle_stop() { _GP(myCycleReq) = false; } void pal_cycle_resume() { _GP(myCycleReq) = true; } static void pal_cycle_update() { const int32 currTime = timer_read_60(); // Get current time if (_GP(myCycleNeverStopCycling) == false) { // If there is an end time to get to... if (currTime >= _GP(myCycleEndTime)) { // See if we have reached it _GP(myCycleFinished) = true; // Mark cycling as finished return; // Return } } else { // See if we should color cycle right now if (currTime >= _GP(myCycleEndDelayTime)) { // If the delay has expired, color cycle RGB8 firstColor; int32 i; // Cycle the master palette firstColor.r = _G(master_palette)[_GP(myCycleStartIndex)].r; // Remember first color firstColor.g = _G(master_palette)[_GP(myCycleStartIndex)].g; firstColor.b = _G(master_palette)[_GP(myCycleStartIndex)].b; for (i = _GP(myCycleStartIndex); i < _GP(myCycleEndIndex); ++i) { // Shift colors down one in palette _G(master_palette)[i].r = _G(master_palette)[i + 1].r; _G(master_palette)[i].g = _G(master_palette)[i + 1].g; _G(master_palette)[i].b = _G(master_palette)[i + 1].b; } _G(master_palette)[_GP(myCycleEndIndex)].r = firstColor.r; // Set last color to the first color _G(master_palette)[_GP(myCycleEndIndex)].g = firstColor.g; _G(master_palette)[_GP(myCycleEndIndex)].b = firstColor.b; // Then cycle the FX palette firstColor.r = _GP(myFXPalette)[_GP(myCycleStartIndex)].r; // Remember first color firstColor.g = _GP(myFXPalette)[_GP(myCycleStartIndex)].g; firstColor.b = _GP(myFXPalette)[_GP(myCycleStartIndex)].b; for (i = _GP(myCycleStartIndex); i < _GP(myCycleEndIndex); ++i) { // Shift colors down one in palette _GP(myFXPalette)[i].r = _GP(myFXPalette)[i + 1].r; _GP(myFXPalette)[i].g = _GP(myFXPalette)[i + 1].g; _GP(myFXPalette)[i].b = _GP(myFXPalette)[i + 1].b; } _GP(myFXPalette)[_GP(myCycleEndIndex)].r = firstColor.r; // Set last color to the first color _GP(myFXPalette)[_GP(myCycleEndIndex)].g = firstColor.g; _GP(myFXPalette)[_GP(myCycleEndIndex)].b = firstColor.b; // Recalculate the end delay time again _GP(myCycleEndDelayTime) = currTime + _GP(myCycleDelayTicks); // Recalculate the end delay time again // must refresh the DAC _GP(myCycleDACrefresh) = true; } } } void pal_fx_update() { int32 startA = 0, endA = 0, startB = 0, endB = 0, startDAC = 0, endDAC = 0; if (!_GP(myCycleReq) && !_GP(myFadeReq)) // Crap out quickly if no effects required return; // Perform any effect required and track index ranges if (_GP(myCycleReq)) { pal_cycle_update(); // Do the cycling (cycles master_palette and _GP(myFXPalette)) if (_GP(myCycleDACrefresh)) { // If it needs the DAC to be refreshed, startA = _GP(myCycleStartIndex); // remember the range endA = _GP(myCycleEndIndex); _GP(myCycleDACrefresh) = false; } } if (_GP(myFadeReq)) { pal_fade_update(&_G(master_palette)[0]); // Do the fading (sets myFXPalette to faded master_palette) if (_GP(myFadeDACrefresh)) { // If it needs the DAC to be refreshed, startB = _GP(myFadeStartIndex); // remember the range endB = _GP(myFadeEndIndex); _GP(myFadeDACrefresh) = false; } } // Check ranges to perform minimum calls of gr_pal_set_range(). // This was originally done to minimize snow on monitor due to repeated OUT instructions if (endA < startB || endB < startA) { // if A and B ranges don't overlap if (!(startA == 0 && endA == 0)) // if this is not the degenerate case (just the transparent color) gr_pal_set_range(&_GP(myFXPalette)[0], startA, endA - startA + 1); // set A range of the DAC if (!(startB == 0 && endB == 0)) // if this is not the degenerate case (just the transparent color) gr_pal_set_range(&_GP(myFXPalette)[0], startB, endB - startB + 1); // set B range of the DAC } else { // They overlap, so find the extent of the overlap (startA < startB) ? (startDAC = startA) : (startDAC = startB); // which start is less (endA > endB) ? (endDAC = endA) : (endDAC = endB); // which end is more if (!(startDAC == 0 && endDAC == 0)) // if this is not the degenerate case (just the transparent color) gr_pal_set_range(&_GP(myFXPalette)[0], startDAC, endDAC - startDAC + 1); // set the whole range of the DAC } // Turn off flags and call callbacks if effects are finished if (_GP(myFadeReq) && _GP(myFadeFinished)) { _GP(myFadeReq) = false; pal_fade_callback(_GP(myFadeTrigger)); } if (_GP(myCycleReq) && _GP(myCycleFinished)) { _GP(myCycleReq) = false; pal_cycle_callback(_GP(myCycleTrigger)); } } void DAC_tint_range(const RGB8 *tintColor, int32 percent, int32 firstPalEntry, int32 lastPalEntry, bool transparent) { int32 i; int32 r, g, b, dr, dg, db; RGB8 color, targetColor; if ((firstPalEntry < 0) || (lastPalEntry > 255) || (firstPalEntry > lastPalEntry)) { // This should generate an error term_message("*** palette index error"); return; } term_message("Color tint DAC to: %d %d %d, %d percent, range (%d - %d)", tintColor->r, tintColor->g, tintColor->b, percent, firstPalEntry, lastPalEntry); percent = DivSF16(percent << 16, 100 << 16); // convert percent to frac16 format targetColor.r = tintColor->r; targetColor.g = tintColor->g; targetColor.b = tintColor->b; term_message("Doing palette....."); if (!transparent) { for (i = firstPalEntry; i <= lastPalEntry; ++i) { // Calculate deltas for RGB's and put them in frac16 format dr = (targetColor.r - _G(master_palette)[i].r) << 16; dg = (targetColor.g - _G(master_palette)[i].g) << 16; db = (targetColor.b - _G(master_palette)[i].b) << 16; // New = orig + (delta * percent) r = _G(master_palette)[i].r + (MulSF16(percent, dr) >> 16); g = _G(master_palette)[i].g + (MulSF16(percent, dg) >> 16); b = _G(master_palette)[i].b + (MulSF16(percent, db) >> 16); // Check for under/overflow r = CLIP(r, int32(0), int32(255)); g = CLIP(g, int32(0), int32(255)); b = CLIP(b, int32(0), int32(255)); color.r = (byte)r; color.g = (byte)g; color.b = (byte)b; gr_pal_set_entry(i, &color); // Set the new color to the DAC } } else { // This is for filtering colors. For example, a completely red filter // (255, 0, 0) will block out the blue and green parts of the palette. // 50% of the same filter will block out only 50% of the blue and // green, but leaving all of the rest blue. for (i = firstPalEntry; i <= lastPalEntry; ++i) { // Converting rgb to a frac16 ( << 16) dividing by 256 ( >> 8) // (the range of the palette values) const int32 percent_r = (targetColor.r) << 8; const int32 percent_g = (targetColor.g) << 8; const int32 percent_b = (targetColor.b) << 8; // This is the difference between the color and the full effect // of the filter at 100%, as a frac16. dr = (_G(master_palette)[i].r << 16) - (MulSF16(percent_r, _G(master_palette)[i].r << 16)); dg = (_G(master_palette)[i].g << 16) - (MulSF16(percent_g, _G(master_palette)[i].g << 16)); db = (_G(master_palette)[i].b << 16) - (MulSF16(percent_b, _G(master_palette)[i].b << 16)); // Scaling the effect to the right percentage. This is a frac16. dr = MulSF16(dr, percent); dg = MulSF16(dg, percent); db = MulSF16(db, percent); // Subtract the result to palette. r = (_G(master_palette)[i].r - (dr >> 16)); g = (_G(master_palette)[i].g - (dg >> 16)); b = (_G(master_palette)[i].b - (db >> 16)); // Check for under/overflow r = CLIP(r, int32(0), int32(255)); g = CLIP(g, int32(0), int32(255)); b = CLIP(b, int32(0), int32(255)); color.r = (byte)r; color.g = (byte)g; color.b = (byte)b; gr_pal_set_entry(i, &color); // Set new colors to DAC. } } } void DAC_restore() { term_message("DAC restored"); gr_pal_set_range(&_G(master_palette)[0], 0, 256); } } // namespace M4