DIRECTORY
Basics USING [BITSHIFT],
ImagerColorMap USING [FullMapProc, Gun, MapEntry, MapProc, NotifierRep, State, StateRep],
Process USING [Detach],
Real USING [Round, RoundC],
RealFns USING [Power],
Terminal USING [ChannelValue, ColorMode, ColorValue, FrameBuffer, GetBlueMap, GetColor, GetColorBitmapState, GetColorFrameBufferA, GetColorFrameBufferB, GetColorMode, GetGreenMap, GetRedMap, SetBlueMap, SetColor, SetGreenMap, SetRedMap, Virtual];
~
BEGIN
State: TYPE ~ ImagerColorMap.State;
StateRep: TYPE ~ ImagerColorMap.StateRep;
NotifierRep: TYPE ~ ImagerColorMap.NotifierRep;
MapEntry: TYPE ~ ImagerColorMap.MapEntry;
MapProc: TYPE ~ ImagerColorMap.MapProc;
FullMapProc: TYPE ~ ImagerColorMap.FullMapProc;
Gun: TYPE ~ ImagerColorMap.Gun;
ChannelValue: TYPE ~ Terminal.ChannelValue;
ColorValue: TYPE ~ Terminal.ColorValue;
Virtual:
TYPE ~ Terminal.Virtual;
TerminalData: TYPE ~ REF TerminalDataRep;
TerminalDataRep:
TYPE ~
RECORD [
vt: Virtual,
mode: Terminal.ColorMode,
frameID: ARRAY {A, B} OF FrameID,
state: State,
shared: PACKED ARRAY [0..1024) OF BOOL,
gamma: REAL
];
FrameID:
TYPE ~
RECORD [
base: LONG POINTER ← NIL,
wordsPerLine: NAT ← 0,
bitsPerPixel: NAT ← 0,
width: NAT ← 0,
height: NAT ← 0
];
IDFromFrame:
PROC [frame: Terminal.FrameBuffer]
RETURNS [id: FrameID ← []] ~ {
IF frame = NIL THEN NULL
ELSE id ← [frame.base, frame.wordsPerLine, frame.bitsPerPixel, frame.width, frame.height];
};
terminalDataList:
LIST
OF TerminalData ←
NIL;
FindData:
INTERNAL
PROC [vt: Virtual]
RETURNS [TerminalData] ~ {
FOR p:
LIST
OF TerminalData ← terminalDataList, p.rest
UNTIL p =
NIL
DO
IF p.first = NIL THEN NULL
ELSE IF Terminal.GetColorBitmapState[vt] = none THEN p.first ← NIL
ELSE IF Terminal.GetColorMode[vt] # p.first.mode THEN p.first ← NIL
ELSE {
frame: Terminal.FrameBuffer ← Terminal.GetColorFrameBufferA[vt];
IF IDFromFrame[frame] # p.first.frameID[A] THEN p.first ← NIL
ELSE
IF p.first.mode.bitsPerPixelChannelB#0
OR p.first.mode.full
THEN {
frame ← Terminal.GetColorFrameBufferB[vt];
IF IDFromFrame[frame] # p.first.frameID[B] THEN p.first ← NIL
};
};
ENDLOOP;
WHILE terminalDataList#
NIL
AND terminalDataList.first =
NIL
DO
terminalDataList ← terminalDataList.rest;
ENDLOOP;
FOR p:
LIST
OF TerminalData ← terminalDataList, p.rest
UNTIL p =
NIL
DO
WHILE p.rest#
NIL
AND p.rest.first =
NIL
DO
p.rest ← p.rest.rest;
ENDLOOP;
ENDLOOP;
FOR p:
LIST
OF TerminalData ← terminalDataList, p.rest
UNTIL p =
NIL
DO
IF p.first # NIL AND vt = p.first.vt THEN RETURN [p.first];
ENDLOOP;
IF Terminal.GetColorBitmapState[vt] # none
THEN {
frameA: Terminal.FrameBuffer ~ Terminal.GetColorFrameBufferA[vt];
frameB: Terminal.FrameBuffer ~ Terminal.GetColorFrameBufferB[vt];
state: State ~ NEW[StateRep ← [next: NIL, first: 0, count: 0]];
t: TerminalData ~
NEW[TerminalDataRep ← [
vt: vt,
mode: Terminal.GetColorMode[vt],
frameID: [IDFromFrame[frameA], IDFromFrame[frameB]],
state: state,
shared: ALL[FALSE],
gamma: 1.0
]];
terminalDataList ← CONS[t, terminalDataList];
RETURN [t];
};
RETURN [NIL];
};
SetGamma:
PUBLIC
ENTRY
PROC [vt: Virtual, gamma:
REAL] ~ {
data: TerminalData ~ FindData[vt];
IF data#NIL THEN data.gamma ← gamma;
};
GetGamma:
PUBLIC
ENTRY
PROC [vt: Virtual]
RETURNS [gamma:
REAL ← 1.0] ~ {
data: TerminalData ~ FindData[vt];
IF data#NIL THEN gamma ← data.gamma;
};
ApplyGamma:
PUBLIC
PROC [v:
REAL, gamma:
REAL]
RETURNS [ColorValue] ~ {
g: REAL ~ MIN[MAX[gamma, 0.01], 100.0];
uncorrected: REAL ~ MIN[MAX[v, 0.0], 1.0];
corrected: REAL ~ RealFns.Power[uncorrected, 1.0/g];
RETURN [Real.Round[MIN[MAX[corrected, 0.0], 1.0]*ColorValue.LAST]];
};
ApplyGammaInverse:
PUBLIC
PROC [colorValue: ColorValue, gamma:
REAL]
RETURNS [
REAL] ~ {
g: REAL ~ MIN[MAX[gamma, 0.01], 100.0];
corrected: REAL ~ REAL[colorValue]/REAL[ColorValue.LAST];
uncorrected: REAL ~ RealFns.Power[corrected, g];
RETURN [uncorrected];
};
IndexFromChannels:
PROC [a, b: ChannelValue, mode: Terminal.ColorMode]
RETURNS [
CARDINAL] ~ {
IF mode.full THEN RETURN [a]
ELSE RETURN [Basics.BITSHIFT[a, mode.bitsPerPixelChannelB] + b];
};
ChannelValues: TYPE ~ RECORD[a, b: ChannelValue];
ChannelsFromIndex:
PROC [index:
CARDINAL, mode: Terminal.ColorMode]
RETURNS [ChannelValues] ~ {
IF mode.full THEN RETURN [[index, 0]]
ELSE {
a: ChannelValue ← Basics.BITSHIFT[index, -(mode.bitsPerPixelChannelB)];
b: ChannelValue ← index-Basics.BITSHIFT[a, mode.bitsPerPixelChannelB];
RETURN [[a, b]];
};
};
Change:
PUBLIC
ENTRY
PROC [vt: Virtual, action:
PROC[set: MapProc]] ~ {
ENABLE UNWIND => NULL;
data: TerminalData ~ FindData[vt];
changed: PACKED ARRAY [0..1024) OF BOOL ← ALL[FALSE];
maxChanged: NAT ← 0;
minChanged: NAT ← 1024;
Set:
PROC [a, b: ChannelValue, red, green, blue: ColorValue, shared:
BOOL] ~ {
index: CARDINAL ~ IndexFromChannels[a, b, data.mode];
Terminal.SetColor[vt: vt, aChannelValue: a, bChannelValue: b, red: red, green: green, blue: blue];
IF index < 1024
THEN {
data.shared[index] ← shared;
changed[index] ← TRUE;
};
IF index+1 > maxChanged THEN maxChanged ← index+1;
IF index < minChanged THEN minChanged ← index;
};
IF data #
NIL
AND
NOT data.mode.full
THEN {
c: NAT ← NAT.LAST;
action[Set];
c ← minChanged;
WHILE c < maxChanged
DO
d: NAT ← c;
WHILE d < maxChanged AND changed[d] DO d ← d + 1 ENDLOOP;
data.state.first ← c;
data.state.count ← d-c;
data.state.next ← NEW[StateRep ← [next: NIL, first: 0, count: 0]];
data.state ← data.state.next;
c ← d;
WHILE c < maxChanged AND NOT changed[c] DO c ← c + 1 ENDLOOP;
ENDLOOP;
};
TRUSTED {Process.Detach[FORK NotifyChanges]};
};
ChangeFull:
PUBLIC
ENTRY
PROC [vt: Virtual, action:
PROC[set: FullMapProc]] ~ {
ENABLE UNWIND => NULL;
data: TerminalData ~ FindData[vt];
changed: PACKED ARRAY ChannelValue OF BOOL ← ALL[FALSE];
maxChanged: NAT ← 0;
minChanged: NAT ← 1024;
Set:
PROC [gun: Gun, in: ChannelValue, out: ColorValue] ~ {
index: CARDINAL ~ in;
SELECT gun
FROM
red => {Terminal.SetRedMap[vt, in, out]};
green => {Terminal.SetGreenMap[vt, in, out]};
blue => {Terminal.SetBlueMap[vt, in, out]};
ENDCASE => NULL;
changed[in] ← TRUE;
IF in+1 > maxChanged THEN maxChanged ← in+1;
IF in < minChanged THEN minChanged ← in;
};
IF data #
NIL
AND data.mode.full
THEN {
c: NAT ← NAT.LAST;
action[Set];
c ← minChanged;
WHILE c < maxChanged
DO
d: NAT ← c;
WHILE d < maxChanged AND changed[d] DO d ← d + 1 ENDLOOP;
data.state.first ← c;
data.state.count ← d-c;
data.state.next ← NEW[StateRep ← [next: NIL, first: 0, count: 0]];
data.state ← data.state.next;
c ← d;
WHILE c < maxChanged AND NOT changed[c] DO c ← c + 1 ENDLOOP;
ENDLOOP;
};
TRUSTED {Process.Detach[FORK NotifyChanges]};
};
Shared:
PUBLIC
ENTRY
PROC [vt: Virtual, a, b: ChannelValue]
RETURNS [
BOOL] ~ {
data: TerminalData ~ FindData[vt];
IF data = NIL THEN RETURN [FALSE]
ELSE {
index: CARDINAL ~ IndexFromChannels[a, b, data.mode];
IF index < 1024 THEN RETURN [data.shared[index]]
ELSE RETURN [FALSE];
};
};
StateHistory:
TYPE ~
RECORD [
state: State ← NIL,
count: INT ← 0,
data: TerminalData ← NIL
];
GetStateHistory:
INTERNAL
PROC [vt: Virtual, since: State]
RETURNS [s: StateHistory ← []] ~ {
s.data ← FindData[vt];
IF s.data #
NIL
THEN {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
nMap: [0..1024] ~ IF mode.full THEN 256 ELSE Basics.BITSHIFT[1, mode.bitsPerPixelChannelA+mode.bitsPerPixelChannelB];
p: State ← s.state ← IF since = NIL THEN NEW[StateRep ← [next: s.data.state, first: 0, count: nMap]] ELSE since;
UNTIL p=
NIL
OR p = s.data.state
DO
s.count ← s.count + 1;
p ← p.next;
ENDLOOP;
IF p = NIL THEN RETURN [GetStateHistory[vt, NIL]];
};
};
GetChanges:
PUBLIC
ENTRY
PROC [vt: Virtual, since: State, change: MapProc]
RETURNS [new: State] ~ {
ENABLE UNWIND => NULL;
stateHistory: StateHistory ← GetStateHistory[vt, since];
changed: PACKED ARRAY [0..1024) OF BOOL ← ALL[FALSE];
WHILE stateHistory.count # 0
DO
mode: Terminal.ColorMode ~ stateHistory.data.mode;
FOR index:
CARDINAL
IN [stateHistory.state.first..stateHistory.state.count)
DO
chan: ChannelValues ~ ChannelsFromIndex[index, mode];
red, green, blue: ColorValue;
[red, green, blue] ← Terminal.GetColor[vt, chan.a, chan.b];
IF
NOT changed[index]
THEN {
change[chan.a, chan.b, red, green, blue, stateHistory.data.shared[index]];
changed[index] ← TRUE;
};
ENDLOOP;
stateHistory.state ← stateHistory.state.next;
stateHistory.count ← stateHistory.count - 1;
ENDLOOP;
RETURN [stateHistory.state];
};
GetFullChanges:
PUBLIC
ENTRY
PROC [vt: Virtual, since: State, change: FullMapProc]
RETURNS [new: State] ~ {
ENABLE UNWIND => NULL;
stateHistory: StateHistory ← GetStateHistory[vt, since];
changed: PACKED ARRAY [0..256) OF BOOL ← ALL[FALSE];
WHILE stateHistory.count # 0
DO
FOR index:
CARDINAL
IN [stateHistory.state.first..stateHistory.state.count)
DO
IF
NOT changed[index]
THEN {
change[gun: red, in: index, out: Terminal.GetRedMap[vt, index]];
change[gun: green, in: index, out: Terminal.GetGreenMap[vt, index]];
change[gun: blue, in: index, out: Terminal.GetBlueMap[vt, index]];
changed[index] ← TRUE;
};
ENDLOOP;
stateHistory.state ← stateHistory.state.next;
stateHistory.count ← stateHistory.count - 1;
ENDLOOP;
RETURN [stateHistory.state];
};
SetFullColorMap:
PROC [vt: Virtual, gamma:
REAL ← 2.2] ~ {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
stdGrayFullAction:
PROC[set: FullMapProc] ~ {
r: REAL ← 1.0/255.0;
FOR i: ChannelValue
IN ChannelValue
DO
out: ColorValue ~ ApplyGamma[i*r, gamma];
set[gun: red, in: i, out: out];
set[gun: green, in: i, out: out];
set[gun: blue, in: i, out: out];
ENDLOOP;
};
ChangeFull[vt, stdGrayFullAction];
};
standard:
ARRAY [0..3]
OF
LIST
OF MapEntry ← InitColorMapEntries[];
StandardColorMapEntries:
PUBLIC
PROC [bitsPerPixel: [1..8]]
RETURNS [
LIST
OF MapEntry] ~ {
lgBitsPerPixel: NAT ~ SELECT bitsPerPixel FROM 1=>0, 2=>1, 4=>2, 8=>3, ENDCASE => ERROR;
RETURN [standard[lgBitsPerPixel]]
};
InitColorMapEntries:
PROC
RETURNS [s:
ARRAY [0..3]
OF
LIST
OF MapEntry] ~ {
FOR lgBitsPerPixel:
NAT
IN [0..3]
DO
bitsPerPixel: NAT ~ Basics.BITSHIFT[1, lgBitsPerPixel];
maxPixel: CARDINAL ~ Basics.BITSHIFT[1, bitsPerPixel]-1;
list: LIST OF MapEntry ← NIL;
SetColor:
PROC [a: ChannelValue, r, g, b:
REAL] ~ {
red: ColorValue ~ Real.RoundC[r*ChannelValue.LAST];
green: ColorValue ~ Real.RoundC[g*ChannelValue.LAST];
blue: ColorValue ~ Real.RoundC[b*ChannelValue.LAST];
list ← CONS[[a, red, green, blue], list];
};
SetComplements:
PROC [a: ChannelValue, r, g, b:
REAL] ~ {
SetColor[a, r, g, b];
SetColor[maxPixel-a, 1.0-r, 1.0-g, 1.0-b];
};
Do:
PROC ~ {
k: NAT ← 0;
IF lgBitsPerPixel = 0
THEN {
SetComplements[0, 1, 1, 1];
RETURN;
};
IF lgBitsPerPixel = 1
THEN {
SetColor[0, 0, 0, 0];
SetColor[1, 0, 0, 1];
SetColor[2, 1, 0, 0];
SetColor[3, 0, 1, 0];
RETURN;
};
IF lgBitsPerPixel = 2
THEN {
SetComplements[0, 0, 0, 0];
SetComplements[1, 0, 0, 1];
SetComplements[2, 0, 1, 0];
SetComplements[3, 1, 0, 0];
SetComplements[6, .333, .333, .333];
SetComplements[5, 0, 0, .5];
SetComplements[6, 0, .5, 0];
SetComplements[7, .5, 0, 0];
RETURN;
};
FOR b:
NAT
IN [0..4)
DO
FOR g:
NAT
IN [0..6)
DO
FOR r:
NAT
IN [0..5)
DO
a: NAT ~ (r*6+g)*4+b;
IF a < 60 THEN SetComplements[a, r/4.0, g/5.0, b/3.0];
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
Do[];
s[lgBitsPerPixel] ← list;
ENDLOOP;
};
SetStandardColorMap:
PUBLIC
PROC [vt: Virtual, gamma:
REAL ← 2.2] ~ {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
IF mode.full THEN SetFullColorMap[vt, gamma]
ELSE {
SetStandardGrayMap[vt, gamma]; -- fill unused entries
LoadEntries[vt, StandardColorMapEntries[mode.bitsPerPixelChannelA], gamma];
};
};
SetStandardColorMap: PUBLIC PROC [vt: Virtual, gamma: REAL ← 2.2] ~ {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
IF mode.full THEN SetFullColorMap[vt, gamma]
ELSE IF mode.bitsPerPixelChannelA=1 THEN SetStandardGrayMap[vt, gamma]
ELSE {
blueCV: NAT ~ 2;
redCV: NAT ~ 4;
magentaCV: NAT ~ 6;
greenCV: NAT ~ 8;
cyanCV: NAT ~ 10;
yellowCV: NAT ~ 12;
size: NAT ~ Basics.BITSHIFT[1, mode.bitsPerPixelChannelA];
bSize: NAT ~ Basics.BITSHIFT[1, mode.bitsPerPixelChannelB];
max: NAT ~ size-1;
colorAction: PROC[set: MapProc] ~ {
c: NAT ← 0;
SetColor: PROC [a: ChannelValue, r, g, b: REAL, shared: BOOL ← TRUE] = {
red: ColorValue ~ ApplyGamma[r, gamma];
green: ColorValue ~ ApplyGamma[g, gamma];
blue: ColorValue ~ ApplyGamma[b, gamma];
FOR b: NAT IN [0..bSize) DO
set[a: a, b: b, red: red, green: green, blue: blue, shared: shared];
ENDLOOP;
};
GrayWedge: PROC ~ {
color: REAL ← 0;
step: REAL ← 0;
nentries: CARDINAL ← 0;
bitsPerPixel: CARDINAL ← Terminal.GetColorMode[vt].bitsPerPixelChannelA;
SELECT Terminal.GetColorMode[vt].bitsPerPixelChannelA FROM
1 => {step ← 1.0; nentries ← 2};
2 => {step ← 1.0/7; nentries ← 4};
4 => {step ← 1.0/15; nentries ← 16};
8 => {step ← 1.0/255; nentries ← 256};
ENDCASE => RETURN;
FOR i: CARDINAL IN [0..nentries) DO
SetColor[i, color, color, color];
color ← color+step;
ENDLOOP;
};
GrayWedge[];
IF Terminal.GetColorMode[vt].bitsPerPixelChannelA < 4 THEN RETURN;
FOR i: ChannelValue IN [2..256) DO
IF i MOD 2 = 0 THEN SetColor[i, 0, 0, 0, FALSE];
ENDLOOP;
SetColor[(c ← c + 2), 0, 0, 1]; --blue
SetColor[(c ← c + 2), 1, 0, 0]; --red
SetColor[(c ← c + 2), 1, 0, 1]; --magenta
SetColor[(c ← c + 2), 0, 1, 0]; --green
SetColor[(c ← c + 2), 0, 1, 1]; --cyan
SetColor[(c ← c + 2), 1, 1, 0]; --yellow
SetColor[(c ← c + 2), 0.075, 0.5, 0.3]; -- darkBrown
SetColor[(c ← c + 2), 0.075, 0.5, 0.5]; -- brown
SetColor[(c ← c + 2), 0.075, 0.4, 0.8]; -- tan
SetColor[(c ← c + 2), 0.0, 1.0, 0.4]; -- darkRed
SetColor[(c ← c + 2), 0.0, 0.5, 1.0]; -- lightRed
SetColor[(c ← c + 2), 0.075, 1.0, 1.0]; -- orange
SetColor[(c ← c + 2), 0.167, 1, 0.5]; -- darkYellow
SetColor[(c ← c + 2), 0.167, 0.6, 1.0]; -- lightYellow
SetColor[(c ← c + 2), 0.333, 1.0, 0.4]; -- darkGreen
SetColor[(c ← c + 2), 0.333, 0.5, 1.0]; -- lightGreen
SetColor[(c ← c + 2), 0.67, 1.0, 0.5]; -- darkBlue
SetColor[(c ← c + 2), 0.67, 0.4, 1.0]; -- lightBlue
SetColor[(c ← c + 2), 0.776, 1, 0.8]; -- purple
SetColor[(c ← c + 2), 0.776, 0.5, 0.8]; -- violet
SetColor[(c ← c + 2), 0.833, 0.4, 1.0]; -- pink
SetColor[(c ← c + 2), 0.0, 0.0, 0.3]; -- darkGray
SetColor[(c ← c + 2), 0.0, 0.0, 0.77]; -- lightGray
};
Change[vt, colorAction];
};
SetGamma[vt, gamma];
};
LoadEntries:
PUBLIC
PROC [vt: Virtual, mapEntries:
LIST
OF MapEntry, gamma:
REAL ← 2.2, b: ChannelValue ← 0, shared:
BOOL ←
TRUE] ~ {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
IF mode.full THEN NULL
ELSE {
mapAction:
PROC[set: MapProc] ~ {
r: REAL ← 1.0/LAST[ColorValue];
FOR m:
LIST
OF MapEntry ← mapEntries, m.rest
UNTIL m =
NIL
DO
red: ColorValue ~ ApplyGamma[m.first.red*r, gamma];
green: ColorValue ~ ApplyGamma[m.first.green*r, gamma];
blue: ColorValue ~ ApplyGamma[m.first.blue*r, gamma];
set[a: m.first.mapIndex, b: b, red: red, green: green, blue: blue, shared: shared];
ENDLOOP;
};
Change[vt, mapAction];
};
SetGamma[vt, gamma];
};
SetStandardGrayMap:
PUBLIC
PROC [vt: Virtual, gamma:
REAL ← 2.2] ~ {
mode: Terminal.ColorMode ~ Terminal.GetColorMode[vt];
IF mode.full THEN SetFullColorMap[vt, gamma]
ELSE
IF mode.bitsPerPixelChannelA=1
THEN {
min: Terminal.ColorValue ~ Terminal.ColorValue.FIRST;
max: Terminal.ColorValue ~ Terminal.ColorValue.LAST;
bwAction:
PROC[set: MapProc] ~ {
set[a: 0, red: max, green: max, blue: max]; -- white
set[a: 1, red: min, green: min, blue: min]; -- black
};
Change[vt, bwAction];
}
ELSE {
size: NAT ~ Basics.BITSHIFT[1, mode.bitsPerPixelChannelA];
bSize: NAT ~ Basics.BITSHIFT[1, mode.bitsPerPixelChannelB];
max: NAT ~ size-1;
grayAction:
PROC[set: MapProc] ~ {
r: REAL ← 1.0/MAX[max, 1];
FOR b:
NAT
IN[0..bSize)
DO
FOR i:
NAT
IN[0..size)
DO
out: ColorValue ~ ApplyGamma[i*r, gamma];
set[a: i, b: b, red: out, green: out, blue: out];
ENDLOOP;
ENDLOOP;
};
Change[vt, grayAction];
};
SetGamma[vt, gamma];
};
NotifyChanges:
PROC ~ {
FOR p:
LIST
OF NotifierRep ← GetNotifiers[], p.rest
UNTIL p =
NIL
DO
p.first.proc[p.first.data ! ABORTED => CONTINUE];
ENDLOOP;
};
notifier:
LIST
OF NotifierRep ←
NIL;
GetNotifiers:
ENTRY
PROC
RETURNS [
LIST
OF NotifierRep] ~ {
RETURN [notifier];
};
Register:
PUBLIC
ENTRY
PROC [colorMapChanged: NotifierRep] ~ {
notifier ← CONS[colorMapChanged, notifier];
};
UnRegister:
PUBLIC
ENTRY
PROC [colorMapChanged: NotifierRep] ~ {
p: LIST OF NotifierRep ← notifier;
notifier ← NIL;
WHILE p #
NIL
DO
IF p.first = colorMapChanged THEN p ← p.rest
ELSE {t: LIST OF NotifierRep ← p.rest; p.rest ← notifier; notifier ← p; p ← t};
ENDLOOP;
};