Files
scummvm-cursorfix/engines/m4/graphics/krn_pal.cpp
2026-02-02 04:50:13 +01:00

863 lines
29 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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