...
 
Commits (2)
......@@ -50,7 +50,7 @@ Esc
Normal text input
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is accepting text input, add the character there.
+ If choicematic is active in that box, scan from current highlight onward to
the first choice beginning with the entered character, and highlight that.
......@@ -59,47 +59,58 @@ Normal text input
Delete/backspace
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is accepting text input, delete some text in the box.
+ If choicematic is active in that box, and not on top choice level, go up
a level.
Home/end
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is accepting text input, move the caret.
+ If choicematic is active in that box, move the highlight.
+ If the box is freely scrollable, scroll the box to top or bottom.
Ctrl+Home/end
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is freely scrollable, scroll the box to top or bottom.
Pageup/pagedown
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If choicematic is active in that box, move the highlight.
+ If the box is freely scrollable, scroll the box by a pageful.
Cursor keys left/right
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is accepting text input, move the caret.
+ If choicematic is active in that box and has more than one column, move
the highlight.
- If mouseoverables without -mouseonly exist, find the closest left/right
from current mousexy and mouseon it, mouseoffing current overable if any.
Ctrl+cursor keys left/right
- If textboxes are hidden, ignore.
- For each box from topmost...
+ If the box is accepting text input, move the caret.
Cursor keys up/down
- If textboxes are hidden, ignore.
- If the debug console is visible, bring up commands from command history.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If choicematic is active in that box, move the highlight.
+ If the box is freely scrollable, scroll the box up/down.
- If mouseoverables without -mouseonly exist, find the closest up/down from
current mousexy and mouseon it, mouseoffing current overable if any.
Ctrl+cursor keys up/down
- For each box from topmost...
+ If choicematic is active in that box, highlight the next choice in the given
direction.
+ If the box is freely scrollable, scroll the box up/down.
Alt-Enter
- Toggle fullscreen mode.
......@@ -164,7 +175,7 @@ Ctrl-V
- Otherwise behave like Ctrl-I.
Ctrl-W [console port only]
- Switches between RGB and LXY palette mixing modes.
- Switches between RGB, LXY, and Truecolor palette mixing modes.
Ctrl-XYZZY
- Typing this enables debug mode.
......@@ -215,7 +226,7 @@ Left/Right/Up/Down
Left stick
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If choicematic is active in this box and has more than one column, and the
stick points predominantly left or right, move the highlight. Delay between
moves depends on stick position, with 1000ms at center, 120ms? at extreme.
......@@ -230,21 +241,20 @@ Left stick
then the stick. The poll command returns an axis value to the script,
-32k to +32k, where cursor keys and direction buttons are always at digital
extremes; the script can decide how to interpret the value for itself.)
- For each box (box 0 first, then from top down)...
- For each box from topmost...
+ If the box is freely scrollable, scroll the box up/down.
Right stick
- If textboxes are hidden, ignore.
- For each box (box 0 first, then from top down)...
- If the box is freely scrollable, scroll the box up/down.
- For each box from topmost...
+ If the box is freely scrollable, scroll the box up/down.
Mouse
-----
The mouse interacts with only the topmost textbox that the cursor is over, in
case there are multiple boxes over each other. For this purpose, box 0 is
always topmost when visible; otherwise the highest-numbered box is topmost.
case there are multiple boxes over each other.
Mouse movement
- If textboxes are visible, and the mouse is over a box, and the topmost
......@@ -284,10 +294,7 @@ Mouse wheel
choice up/down.
- If the topmost mouseovered box is scrollable, scroll the box up/down. If
a scroll effect is already live, scroll further at triple speed.
- For each box (box 0 first, then from top down)...
+ If choicematic is active in that box, highlight the next choice in the given
direction.
+ If the box is freely scrollable, scroll the box up/down.
- Otherwise do the same as Ctrl+cursor keys up/down
Metamenu
......@@ -411,22 +418,6 @@ Interface sounds - checkbox.
neither. Also, voiceovers don't exist.)
Textboxes, originally
=====================
Most games in framed mode have a box of 512x64. 16px font, no linespacing.
Twilight has 496x64. It has a 16px font, with no linespacing.
Marilyn has 500x100.
Mayclub is 600x80. It has a 16px font, with 4 pixels extra linespacing.
Nocturne is 512x80. It has a 16px font, with 4 pixels extra linespacing.
Eve is 416x57. It has a 16px font, with 3 pixels extra linespacing.
Angelscollection 2 has 544x64.
Angelscollection 1 has 592x48. (add a decomp hack to extend height to 64?)
Sakura has viewarea at 80,16; size 480x296. Textbox at 64,328; size 512x64.
Viewarea 8192,2621; size 49152x48497. Textbox 6554,53740; size 52429x10486.
Saving and loading (ideas)
==================
......
......@@ -69,8 +69,8 @@ begin
// Ctrl-W
if com = chr(0) + chr(4) + chr($17) then begin
sakuparam.lxyMix := NOT sakuparam.lxyMix;
InitXPal;
sakuparam.palettemode := EPaletteMode((byte(sakuparam.palettemode) + 1) mod byte(PM_MODECOUNT));
Rendermatic.InitPalette;
Rendermatic.Reset;
for i := high(BoxHub.textbox) downto 0 do
if BoxHub.textbox[i].isHidden = FALSE then BoxHub.textbox[i].boxNeedsRedraw := TRUE;
......@@ -297,7 +297,7 @@ begin
// Hide the console cursor. Set the console colors to an expected default.
CrtShowCursor(FALSE);
SetColor($0007);
InitXPal;
Rendermatic.InitPalette;
log('Game window size: ' + sysvar.windowSize.ToString);
UpdateCoscosTable(sysvar.windowSize.w + sysvar.windowSize.h);
......@@ -337,7 +337,7 @@ begin
overrideWindowSize.w := 0;
overrideWindowSize.h := 0;
frameDelay := 0; autoTest := FALSE;
lxyMix := FALSE;
palettemode := PM_LXYCHROMA;
end;
repeat
......@@ -354,8 +354,6 @@ begin
'-y': sakuparam.overrideWindowSize.w := valx(GetNextParam);
'-autotest': sakuparam.autoTest := TRUE;
'-framedelay': sakuparam.frameDelay := valx(GetNextParam);
'-lxy': sakuparam.lxyMix := TRUE;
'-rgb': sakuparam.lxyMix := FALSE;
'-v', '-version':
begin
writeln(GetSuperSakuraVersion);
......@@ -389,8 +387,6 @@ begin
writeln('-y=n Override window height, set to n pixels');
writeln('-autotest Feed rapid random input into the engine');
writeln('-framedelay=n Disable the frame limiter, skip n msec every frame');
writeln('-rgb Use RGB palette mixing');
writeln('-lxy Use LXY palette mixing');
writeln('-list Print game compatibility list');
writeln('-help Print this help');
end;
......@@ -121,7 +121,7 @@ var i : dword;
setlength(scrollbar, boxSizeP_r.h);
// Add the up and down arrows, or = if unscrollable.
c := style.scrollbar.capColor;
scrollbar[0].ccolor := (xpal[c.r shr 4][c.g shr 4][c.b shr 4] and $F) + backpal shl 4;
scrollbar[0].ccolor := (Rendermatic.palette[c.r shr 4][c.g shr 4][c.b shr 4].color1) + backpal shl 4;
if contentWinScrollOfsP > 0 then
scrollbar[0].ccode := chr($E2) + chr($86) + chr($91)
else
......@@ -134,7 +134,7 @@ var i : dword;
// Add the trough, using a checkerboard block.
c := style.scrollbar.troughColor;
dword(c) := (xpal[c.r shr 4][c.g shr 4][c.b shr 4] and $F) + backpal shl 4;
dword(c) := (Rendermatic.palette[c.r shr 4][c.g shr 4][c.b shr 4].color1) + backpal shl 4;
sy := boxSizeP_r.h - 2;
while sy <> 0 do begin
scrollbar[sy].ccolor := dword(c);
......@@ -147,7 +147,7 @@ var i : dword;
sy := contentFullHeightP - contentWinSizeP.h;
sy := ((boxSizeP_r.h - 3) * contentWinScrollOfsP + sy shr 1) div sy + 1;
c := style.scrollbar.thumbColor;
scrollbar[sy].ccolor := (xpal[c.r shr 4][c.g shr 4][c.b shr 4] and $F) shl 4 + backpal;
scrollbar[sy].ccolor := (Rendermatic.palette[c.r shr 4][c.g shr 4][c.b shr 4].color1) shl 4 + backpal;
scrollbar[sy].ccode := ' ';
end;
......@@ -160,7 +160,7 @@ var i : dword;
if dword(textcolor) = dword(c) then isvisible := FALSE;
dword(textcolor) := dword(c);
if (isvisible) or (force) then begin
textpal := xpal[textcolor.r shr 4][textcolor.g shr 4][textcolor.b shr 4] and $F;
textpal := Rendermatic.palette[textcolor.r shr 4][textcolor.g shr 4][textcolor.b shr 4].color1;
if (invertcolors) then
SetColor(backpal + textpal shl 4)
else
......@@ -281,7 +281,7 @@ begin
or (boxclipping.top + boxclipping.bottom >= boxSizeP_r.h) then exit;
// Calculate the drawable location and size of the box, and prepare to draw.
backpal := xpal[style.baseColor[0].r shr 4][style.baseColor[0].g shr 4][style.baseColor[0].b shr 4] and $F;
backpal := Rendermatic.palette[style.baseColor[0].r shr 4][style.baseColor[0].g shr 4][style.baseColor[0].b shr 4].color1;
_NewColor(style.textColor, TRUE, TRUE);
inc(distfromedge.left, boxclipping.left);
......
......@@ -39,6 +39,10 @@ type ETransitionType = (TT_INSTANT = 0, TT_WIPEFROMLEFT = 1, TT_RAGGEDWIPE = 2,
// Blend modes:
type EBlendMode = (BM_NORMAL = 0, BM_HARDLIGHT = 1);
{$ifdef sakucon}
type EPaletteMode = (PM_LXYCHROMA, PM_LXYLUMA, PM_RGB, PM_TRUECOLOR, PM_MODECOUNT);
{$endif}
// Combinable values for sysvar.keysdown:
const
KEYVAL_DOWN = 1;
......@@ -226,10 +230,10 @@ var sakuparam : record
overrideWindowSize : TSizeP;
frameDelay : dword; // 0 = auto
autotest : boolean;
{$ifdef sakucon} {$note todo: Add 24-bit RGB mixing, rename to colormode : EColorMode}
{$ifdef sakucon} {$note todo: Add 24-bit RGB mixing}
// Also in windows 10 console!
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#Output_Sequences
lxymix : boolean; // use LXY mixing instead of RGB
palettemode : EPaletteMode;
{$endif}
end;
......
......@@ -17,222 +17,12 @@
{ along with SuperSakura. If not, see <https://www.gnu.org/licenses/>. }
{ }
{$note todo: Merge head-x files into something else, they're too small}
procedure LogError(const t : UTF8string);
begin
log('[!] ' + t);
end;
type LXYtriplet = record
luma : byte; x, y : longint;
end;
function RGBtoLXY(r, g, b : byte) : LXYtriplet;
// This converts an sRGB value to a kind of YCH space, great for perceptual comparisons. The first component is L', but the C
// and H are further transformed to x and y coordinates on a hue/saturation hexagon.
var multi, mulcomp : single;
c : byte;
msc : (red, green, blue);
mscval, lscval : byte;
// On a hexagon with red on the right, green top left, and blue lower left:
// red = 1.0, 0.0 = 255, 0
// green = -0.5, 0.866 = -128, 221
// blue = -0.5, -0.866 = -128, -221
begin
// This converts sRGB to BT.709 L'. Both in and out are in range 0..255.
RGBtoLXY.luma := (6966 * r + 23436 * g + 2366 * b + 16383) shr 15;
// Greyscale?
if (r = g) and (r = b) then begin
RGBtoLXY.x := 0; RGBtoLXY.y := 0;
exit;
end;
// Get chroma/saturation, where in-range 0..255, out-range 1..255.
msc := red; mscval := r;
if g > r then begin
msc := green; mscval := g;
end;
if b > mscval then begin
msc := blue; mscval := b;
end;
lscval := r;
if g < r then lscval := g;
if b < lscval then lscval := b;
c := mscval - lscval;
// Translate hue into x and y coordinates, range -255..255.
case msc of
red:
if g > b then begin
// red-yellow
multi := (g - b) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(255 * mulcomp + 128 * multi);
RGBtoLXY.y := round(0 * mulcomp + 221 * multi);
end
else begin
// red-magenta
multi := (b - g) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(255 * mulcomp + 128 * multi);
RGBtoLXY.y := round(0 * mulcomp + -221 * multi);
end;
green:
if r > b then begin
// green-yellow
multi := (r - b) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(-128 * mulcomp + 128 * multi);
RGBtoLXY.y := 221;
end
else begin
// green-cyan
multi := (b - r) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(-128 * mulcomp + -255 * multi);
RGBtoLXY.y := round(221 * mulcomp + 0 * multi);
end;
blue:
if g > r then begin
// blue-cyan
multi := (g - r) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(-128 * mulcomp + -255 * multi);
RGBtoLXY.y := round(-221 * mulcomp + 0 * multi);
end
else begin
// blue-magenta
multi := (r - g) / c;
mulcomp := 1 - multi;
RGBtoLXY.x := round(-128 * mulcomp + 128 * multi);
RGBtoLXY.y := -221;
end;
end;
// Apply the saturation as a multiplier toward 0,0.
with RGBtoLXY do begin
if x >= 0 then
x := (x * c + 128) div 255
else
x := (x * c - 128) div 255;
if y >= 0 then
y := (y * c + 128) div 255
else
y := (y * c - 128) div 255;
end;
end;
function DiffRGB(c1, c2 : RGBAquad) : dword;
// Returns the squared difference between two RGBquad colors.
var r, g, b : dword;
begin
r := abs(c1.r - c2.r) * 3;
g := abs(c1.g - c2.g) * 4;
b := abs(c1.b - c2.b) * 2;
DiffRGB := r * r + g * g + b * b;
end;
function DiffLXY(c1, c2 : LXYtriplet) : dword;
var ld, x, y : dword;
begin
ld := abs(c1.luma - c2.luma) * 2;
x := abs(c1.x - c2.x) * 3;
y := abs(c1.y - c2.y) * 3;
DiffLXY := ld * ld + (x * x + y * y);
end;
var xpal : array[0..15] of array[0..15] of array[0..15] of byte;
procedure InitXPal;
var rr, gg, bb : byte;
mylxy : LXYtriplet;
conpalhsl : array[0..$F] of LXYtriplet;
rrr, ggg, bbb : dword;
mycol, palcol : RGBAquad;
i, j : dword;
nearest : byte;
bestscore : dword;
begin
// Acquire the exact RGB palette used by our console, if possible.
GetConsolePalette;
if sakuparam.lxymix then
log('Using LXY mixing with palette:')
else
log('Using RGB mixing with palette:');
for i := 0 to 15 do
log(strcat('%: &&&', [i, crtpalette[i].r, crtpalette[i].g, crtpalette[i].b]));
if sakuparam.lxymix then begin
// Convert the console palette to HSL.
for i := 0 to 15 do
conpalhsl[i] := RGBtoLXY(crtpalette[i].r, crtpalette[i].g, crtpalette[i].b);
// Due to our 16-color limit, no point doing a full 8-bit per channel palette mapping. 4 bits per channel is plenty.
// Loop through every possible color in a 4-bit RGB space.
for rr := 0 to 15 do
for gg := 0 to 15 do
for bb := 0 to 15 do begin
// Convert the 4-bit sRGB color to 8-bit sRGB.
mylxy := RGBtoLXY(rr * 255 div 15, gg * 255 div 15, bb * 255 div 15);
// Find the closest console palette color to this color.
nearest := 0; bestscore := $FFFFFFFF;
for i := 0 to 15 do begin
j := DiffLXY(mylxy, conpalhsl[i]);
if j < bestscore then begin
nearest := i; bestscore := j;
end;
end;
// Check which other color the closest color mixes with 50-50 to get the closest match. This could be
// itself. The true dithering midpoint is found by doing the mix in linear RGB space, then returning to
// sRGB.
for i := 0 to 15 do begin
rrr := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].r] + lut_SRGBtoLinear[crtpalette[i].r]) shr 1];
ggg := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].g] + lut_SRGBtoLinear[crtpalette[i].g]) shr 1];
bbb := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].b] + lut_SRGBtoLinear[crtpalette[i].b]) shr 1];
j := DiffLXY(mylxy, RGBtoLXY(rrr, ggg, bbb));
if j <= bestscore then begin
xpal[rr][gg][bb] := nearest + i shl 4;
bestscore := j;
end;
end;
end;
end
else begin
for rr := 0 to 15 do
for gg := 0 to 15 do
for bb := 0 to 15 do begin
// Convert the 4-bit sRGB color to 8-bit sRGB.
mycol.r := rr * 255 div 15;
mycol.g := gg * 255 div 15;
mycol.b := bb * 255 div 15;
// Find the closest console palette color to this color.
nearest := 0; bestscore := $FFFFFFFF;
for i := 0 to 15 do begin
palcol.r := crtpalette[i].r;
palcol.g := crtpalette[i].g;
palcol.b := crtpalette[i].b;
j := DiffRGB(mycol, palcol);
if j < bestscore then begin
nearest := i; bestscore := j;
end;
end;
// Check which other color the closest color mixes with 50-50 to get the closest match.
for i := 0 to 15 do begin
palcol.r := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].r] + lut_SRGBtoLinear[crtpalette[i].r]) shr 1];
palcol.g := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].g] + lut_SRGBtoLinear[crtpalette[i].g]) shr 1];
palcol.b := lut_LineartoSRGB[(lut_SRGBtoLinear[crtpalette[nearest].b] + lut_SRGBtoLinear[crtpalette[i].b]) shr 1];
j := DiffRGB(mycol, palcol);
if j <= bestscore then begin
xpal[rr][gg][bb] := nearest + i shl 4;
bestscore := j;
end;
end;
end;
end;
end;
procedure SetProgramName(const newnamu : UTF8string); inline;
begin
CrtSetTitle(newnamu);
......
This diff is collapsed.
......@@ -19,7 +19,16 @@
// Namespace object for the Rendermatic. It builds output frames from graphic assets and framebuffer effects.
type TRendermatic = object
{$ifndef sakucon}
{$ifdef sakucon}
// Lookup table for converting 24-bit colors into colorful ASCII approximations. CC1 is the single nearest, CC2 is either
// the same or something that gives an even better result at a 50-50 dither.
type TPaletteEntry = packed record
color1, color2 : byte;
char1, char2 : char;
end;
type PPaletteEntry = ^TPaletteEntry;
var palette : array[0..$F] of array[0..$F] of array[0..$F] of TPaletteEntry;
{$else}
SDL : record
mainWindowH : PSDL_Window;
rendererH : PSDL_Renderer;
......@@ -46,6 +55,7 @@ type TRendermatic = object
procedure RenderEverything;
procedure AddRefresh(x1p, y1p, x2p, y2p : longint);
{$ifdef sakucon}
procedure InitPalette;
procedure RemoveRefresh(x1p, y1p, x2p, y2p : longint);
procedure BlitzAscii(x1, y1, x2, y2 : longint);
{$else}
......