ColorEditImpl.mesa
Implements tool to display AIS files on color monitor
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Last Edited by: Nickell, April 25, 1985 7:31:59 am PST
Last Edited by: Rumph, August 16, 1984 11:08:46 am PDT
Eric Nickell January 20, 1986 0:54:49 am PST
DIRECTORY
AdjustColorStd,
AdjustColor USING [AdjustColors, ClientDataForFlavor, NewGamma, RegisterAdjustColorTool, RequestControl, TRC],
AISTRCViewers USING [AISTRCViewer, Create, Set],
Atom USING [GetPropFromList, PutPropOnList],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
ColorEditViewers USING [colorBrushColorDistance, brushSize],
ColorTransforms USING [Transform],
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Parse],
Containers USING [Container, Create],
Convert USING [Error, RealFromRope, RopeFromAtom, RopeFromCard, RopeFromReal],
Icons USING [IconFlavor, NewIconFromFile],
Imager USING [black],
ImagerFont USING [Find, Font],
KnobAttach USING [Attach, Create, KnobAttachViewer],
Labels USING [Create, Label, Set],
MessageWindow USING [Append, Blink],
Process USING [Detach, InitializeCondition, priorityBackground, SecondsToTicks, SetPriority],
Real USING [RoundC],
Rope USING [Cat, ROPE],
Rules USING [Create],
Sliders USING [Create, GetContents, SetContents, Slider, SliderProc],
VFonts USING [StringWidth],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerOps USING [OpenIcon, PaintViewer, SetOpenHeight],
ViewerTools USING [GetContents, MakeNewTextViewer, SetContents];
ColorEditImpl: CEDAR MONITOR
IMPORTS AdjustColor, AISTRCViewers, Atom, ColorEditViewers, Buttons, Commander, CommandTool, Containers, Convert, Icons, Imager, ImagerFont, KnobAttach, Labels, MessageWindow, Process, Real, Rope, Rules, Sliders, VFonts, ViewerEvents, ViewerOps, ViewerTools
EXPORTS AdjustColorStd
~ {
OPEN AdjustColorStd, ATV: AISTRCViewers;
Types and Constants
ROPE: TYPE ~ Rope.ROPE;
Histogram: TYPE ~ AISUtils.Histogram;
Viewer: TYPE ~ ViewerClasses.Viewer;
Separation: TYPE ~ {red, green, blue};
Private: TYPE ~ RECORD [  --Private data for each flavor
v: Buttons.Button,   --The button which activates this viewer
trc: REF TRCNEW[TRC],
interleaved: REF TRCNEW[TRC],
settings: REF Settings
];
ColorAdjustSliders: TYPE ~ REF ColorAdjustSlidersRec;
ColorAdjustSlidersRec: TYPE ~ ARRAY Control OF Sliders.Slider;
TransformCoords: TYPE ~ RECORD [from, to: NAT];
Global Data
entryVSpace: CARDINAL = 8;
entryHSpace: CARDINAL = 2;
hMargin: CARDINAL = 5; --Spacing between buttons on a line
vMargin: CARDINAL = 5; --Spacing between rows of buttons
graphInset: CARDINAL = 110;
buttonHeight: CARDINAL ~ 20;
buttonWidth: CARDINAL ← 0;
trcWidth: CARDINAL ← 200;
trcHeight: CARDINAL ← trcWidth;
histWidth: CARDINAL ← trcWidth;
histHeight: CARDINAL ← histWidth/4;
sliderHeight: CARDINAL ~ 15;
sliderKnobOffset: CARDINAL ← sliderHeight + 1;
sliderWidth: CARDINAL ← trcWidth;
typicalWidth: CARDINAL ~ 424;
flavorButtonX, flavorButtonY: CARDINAL; --Place to put next flavor button
colorEditToolIcon: Icons.IconFlavor ~ Icons.NewIconFromFile["ColorEdit.icons", 0];
This data is monitored
viewer: Containers.Container ← NIL;  --The actual ColorEdit Tool viewer
hist: AISHistogramViewers.AISHistoViewer;  --The child histogram viewers
stdInControl: StdAdjuster;
colorAdjust: ColorAdjustSliders ← NEW[ColorAdjustSlidersRec];
transform: ColorTransforms.Transform ← [[1,0,0],[0,1,0],[0,0,1],[0,0,0]];
ToggleButton: TYPE ~ {alreadyLog, invertIn, invertOut};
toggleButton: PACKED ARRAY ToggleButton OF BOOLEANALL[FALSE];
NobodyInControl: CONDITION;
nSliders: CARDINAL ~ ORD[LAST[Control]] - ORD[FIRST[Control]] + 1;
fontHeightGuess: CARDINAL ~ 15;
sliderNames: ARRAY Control OF ROPE ← [
"Darkness",
"Contrast",
"White Point",
"Black Point"
];
Public Procedures
stdAdjusters: LIST OF StdAdjuster ← NIL;
RegisterStdAdjuster: PUBLIC ENTRY PROC [adjuster: StdAdjusterRec, initialSettings: Settings ← [.5, .5, 1.0, 0.0]] ~ {
ENABLE UNWIND => NULL;
clientData: StdAdjuster ← NEW[StdAdjusterRec ← adjuster];
private: REF Private ← NEW[Private ← [
settings: NEW[Settings ← initialSettings],
v: NewFlavorButton[flavor: adjuster.flavor]
]];
Calculate initial TRC
FOR i: CARDINAL IN [0..256) DO
private.trc[i] ← private.interleaved[i] ← i;
ENDLOOP;
IF clientData.setProc#NIL THEN {
private.trc ← clientData.setProc[initialSettings, ALL[TRUE]];
};
clientData.private ← private;  --Remember the initial settings
AdjustColor.RegisterAdjustColorTool[[
flavor: adjuster.flavor,
byebye: AClientIsLosingControl,
clientData: clientData
]];
stdAdjusters ← CONS[clientData, stdAdjusters]; --Remember in case need to build a new display tool someday
};
RequestControl: PUBLIC ENTRY PROC [flavor: Flavor] RETURNS [granted: BOOLEAN] ~ {
ENABLE UNWIND => NULL;
granted ← AdjustColor.RequestControl[flavor];
IF granted THEN {
adjuster: StdAdjuster ← NARROW[AdjustColor.ClientDataForFlavor[flavor]];
private: REF Private ← NARROW[adjuster.private, REF Private];
WHILE stdInControl#NIL DO--Wait for partner to clear out
WAIT NobodyInControl;
ENDLOOP;
stdInControl ← adjuster;
SetAdjustmentsInternal[private.settings^];
Buttons.SetDisplayStyle[private.v, $WhiteOnBlack];
};
};
ClientDataForName: PUBLIC PROC [flavor: Flavor] RETURNS [clientData: REF] ~ {
We've gone and stashed the data with the guy we're a client of, so fetch it.
RETURN [NARROW[AdjustColor.ClientDataForFlavor[flavor], StdAdjuster].clientData];
};
AClientIsLosingControl: ENTRY PROC [from, to: Flavor] ~ {
ENABLE UNWIND => NULL;
client: StdAdjuster ← NARROW[AdjustColor.ClientDataForFlavor[from]];
private: REF Private ← NARROW[client.private, REF Private];
Buttons.SetDisplayStyle[private.v, $BlackOnWhite];
stdInControl ← NIL;  --Note that none of our clients controls color map
IF client.byeProc#NIL THEN {
The call-back procedure is forked off, so that our clients may monitor such information, and properly set up WAIT's, etc. If we were to use this process, the MONITOR mechanism could deadlock.
p: PROCESSFORK client.byeProc[from, to];
};
NOTIFY NobodyInControl;
};
Adjust Color Procedures
normalSettings: Settings ~ [0.5, 0.5, 1.0, 0.0];
NormalizeAdjustments: PUBLIC PROC [] ~ {
SetAdjustments[settings: normalSettings];
};
SetAdjustments: PUBLIC ENTRY PROC [settings: Settings] ~ {
ENABLE UNWIND => NULL;
SetAdjustmentsInternal[settings];
};
SetAdjustmentsInternal: INTERNAL PROC [settings: Settings] ~ {
FOR control: Control IN Control DO
Sliders.SetContents[slider: colorAdjust[control], contents: settings[control]];
ENDLOOP;
Adjust[];
};
NormalizeButtonProc: Buttons.ButtonProc ~ {
NormalizeAdjustments[];
};
SelectFlavor: Buttons.ButtonProc ~ {
flavor: Flavor ← NARROW[clientData];
[] ← RequestControl[flavor];
};
AdjustColorBrushDistance: Sliders.SliderProc = {
[slider: Sliders.Slider, reason: Sliders.Reason, value: Sliders.NormalizedSliderValue, clientData: REF ANY ← NIL]
label: ViewerClasses.Viewer ~ NARROW[clientData];
distance: CARDINAL;
IF value>1.0 OR value<0.0 THEN ERROR;
distance ← Real.RoundC[value*value*64];
ColorEditViewers.colorBrushColorDistance ← distance;
IF label#NIL THEN Labels.Set[label: label, value: Convert.RopeFromCard[from: distance]];
};
AdjustColorBrushRange: Sliders.SliderProc = {
[slider: Sliders.Slider, reason: Sliders.Reason, value: Sliders.NormalizedSliderValue, clientData: REF ANY ← NIL]
label: ViewerClasses.Viewer ~ NARROW[clientData];
distance: CARDINAL;
IF value>1.0 OR value<0.0 THEN ERROR;
distance ← Real.RoundC[value*value*64];
ColorEditViewers.brushSize ← distance;
IF label#NIL THEN Labels.Set[label: label, value: Convert.RopeFromCard[from: distance]];
};
DoAdjust: ENTRY Sliders.SliderProc = {
[slider: Sliders.Slider, reason: Sliders.Reason, value: Sliders.NormalizedSliderValue, clientData: REF ANY ← NIL]
ENABLE UNWIND => NULL;
label: ViewerClasses.Viewer ~ NARROW[clientData];
Adjust[];
IF label#NIL THEN Labels.Set[label: label, value: Convert.RopeFromReal[from: value]];
};
Adjust: INTERNAL PROC [] ~ {
IF stdInControl # NIL AND stdInControl.setProc # NIL THEN {
whatChanged: ARRAY Control OF BOOLALL[TRUE]; --Hopefully, later, this can be optimized better
private: REF Private ← NARROW[stdInControl.private];
private.trc ← stdInControl.setProc[private.settings^ ← GetSettings[colorAdjust], whatChanged];
FOR index: CARDINAL ← 1, index+2 WHILE index<256 DO
private.interleaved[index] ← private.trc[index];
ENDLOOP;
[] ← AdjustColor.AdjustColors[stdInControl.flavor, private.interleaved, private.interleaved, private.interleaved];
trcUpdated ← TRUE;
NOTIFY TRCUpdated;
};
};
GetSettings: INTERNAL PROC [colorAdjust: ColorAdjustSliders] RETURNS [settings: Settings] ~ {
FOR control: Control IN Control DO
settings[control] ← Sliders.GetContents[colorAdjust[control]];
ENDLOOP;
};
CurrentTRC: PUBLIC PROC RETURNS [trc: REF TRC] ~ {
private: REF Private ~ NARROW[stdInControl.private];
RETURN [IF private=NIL THEN NIL ELSE private.trc];
};
Color Edit Tool Implementation
NoColorEditTool: PROC RETURNS [BOOLEAN] ~ INLINE {
RETURN [viewer=NIL OR viewer.destroyed]
};
MakeColorEditToolCommand: Commander.CommandProc = BEGIN
RETURN [msg: MakeColorEditTool[]]
END;
MakeColorEditTool: ENTRY PROC RETURNS [msg: ROPE] ~ {
ENABLE UNWIND => NULL;
height: CARDINAL;
IF viewer#NIL AND ~viewer.destroyed THEN {
ViewerOps.OpenIcon[viewer];
RETURN [msg: "Opened existing Color Edit Tool.\n"]
};
viewer ← Containers.Create[[
name: "ColorEditTool",
column: right,
iconic: TRUE,
icon: colorEditToolIcon,
scrollable: TRUE ]];
height ← MakeColorEditViewer[];
ViewerOps.SetOpenHeight[viewer, height];
SetAdjustmentsInternal[normalSettings];
};
ColorEditTextLosingInputFocus: ENTRY ViewerEvents.EventProc = {
[viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL ← FALSE]
GetValue: PROC RETURNS [value: REAL] ~ {
value ← Convert.RealFromRope[r: contents ! Convert.Error => {
MessageWindow.Append[message: Rope.Cat[contents, "not a legal value."], clearFirst: TRUE];
MessageWindow.Blink[];
value ← 0;
ViewerTools.SetContents[viewer: viewer, contents: "0"];
CONTINUE;
}];
};
id: REF ~ Atom.GetPropFromList[propList: viewer.props, prop: $ColorEditTool];
contents: ROPE ~ ViewerTools.GetContents[viewer: viewer];
WITH id SELECT FROM
tCoord: REF TransformCoords => transform[tCoord.from][tCoord.to] ← GetValue[];
atom: ATOM => {
SELECT atom FROM
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
ToggleButtonProc: Buttons.ButtonProc = {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
v: Viewer ~ NARROW[parent];
button: ToggleButton ~ NARROW[clientData, REF ToggleButton]^;
Buttons.SetDisplayStyle[button: v, style: IF toggleButton[button] ← ~toggleButton[button] THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
MakeColorEditViewer: INTERNAL PROC [] RETURNS [height: CARDINAL] ~ {
kavs: ARRAY Control OF KnobAttach.KnobAttachViewer ← ALL[NIL];
MakeRule: PROC ~ {
[] ← Rules.Create[info: [wx: entryHSpace, wy: thisY, wh: 2, ww: typicalWidth, parent: viewer], color: Imager.black];
thisY ← thisY + entryVSpace;
};
MakeTitle: PROC [title: ROPE] ~ {
font: ImagerFont.Font ~ ImagerFont.Find["Xerox/TiogaFonts/TimesRoman14B"];
tempWidth: CARDINAL ← VFonts.StringWidth[string: title, font: font];
prev ← Labels.Create[info: [name: title, wx: (typicalWidth - tempWidth)/2, wy: thisY, parent: viewer, border: FALSE], font: font];
thisY ← thisY + 14 + entryVSpace; --The 14 comes from TimesRoman14
};
MakeTextInputViewer: PROC [id: REF ANY, title, initialContents: ROPENIL, ww: INTEGER ← trcWidth, deltaThisY: INTEGER ← entryVSpace, newLine: BOOLTRUE] ~ {
IF title#NIL THEN {
tempWidth: CARDINAL ← VFonts.StringWidth[string: title];
label: Labels.Label ← Labels.Create[ info: [name: title, wx: thisX - tempWidth - 2*hMargin, wy: thisY, parent: viewer, border: FALSE]];
};
prev ← ViewerTools.MakeNewTextViewer[info: [props: Atom.PutPropOnList[propList: NIL, prop: $ColorEditTool, val: id], wx: thisX, wy: thisY, ww: ww, wh: buttonHeight, parent: viewer, scrollable: FALSE, data: initialContents]];
IF newLine THEN {thisX ← entryHSpace + graphInset; thisY ← thisY + prev.wh + deltaThisY} ELSE thisX ← thisX + prev.ww;
[] ← ViewerEvents.RegisterEventProc[proc: ColorEditTextLosingInputFocus, event: killInputFocus, filter: prev, before: FALSE];
};
MakeToggleButton: PROC [label: ROPE, button: ToggleButton, initialState: BOOLEAN] ~ {
prev ← Buttons.Create[info: [name: label, wx: thisX, wy: thisY, parent: viewer], proc: ToggleButtonProc, clientData: NEW[ToggleButton ← button]];
Buttons.SetDisplayStyle[button: prev, style: IF toggleButton[button] ← initialState THEN $WhiteOnBlack ELSE $BlackOnWhite];
thisX ← thisX + prev.ww + hMargin;
};
MakeSliderWithTitle: PROC [title: ROPE, sliderProc: Sliders.SliderProc ← DoAdjust] RETURNS [slider: Sliders.Slider, kav: KnobAttach.KnobAttachViewer] ~ {
tempWidth: CARDINAL ← VFonts.StringWidth[string: title];
value: Labels.Label ← Labels.Create[info: [wx: 2*entryHSpace + graphInset + sliderWidth, ww: 50, wy: thisY, parent: viewer, border: FALSE], paint: FALSE];
label: Labels.Label ← Labels.Create[ info: [name: title, wx: entryHSpace + graphInset - sliderKnobOffset - 2*hMargin - tempWidth, wy: thisY, parent: viewer, border: FALSE]];
slider ← Sliders.Create[
info: [ wx: entryHSpace + graphInset, wy: thisY, ww: sliderWidth, wh: sliderHeight, parent: viewer],
sliderProc: sliderProc,
orientation: horizontal,
value: 0.5,
clientData: value
];
kav ← KnobAttach.Create[
info: [ wx: entryHSpace + graphInset - sliderKnobOffset, wy: thisY, ww: sliderHeight, wh: sliderHeight, parent: viewer],
turnProc: sliderProc,
slider: slider,
clientData: value
];
thisY ← thisY + slider.wh + entryVSpace;
};
prev: ViewerClasses.Viewer;
thisX: INT ← entryHSpace;
thisY: INT ← entryVSpace;
TRC Viewer
MakeTitle["Tone Reproduction Curve"];
prev ← Labels.Create [info: [name: "Tone Reproduction Curve", wx: thisX, wy: thisY, parent: viewer, column: right, border: FALSE ]];
thisX ← entryHSpace + graphInset;
prev ← ATV.Create[[ wx: thisX, wy: thisY, wh: trcHeight, ww: trcWidth, parent: viewer, border: TRUE ]];
thisX ← entryHSpace; -- Continue on next line
thisY ← thisY + prev.wh + entryVSpace;
TRUSTED {Process.Detach[FORK UpdateTRCProcess[prev]]};
Set up the buttons to control the sliders
prev ← Buttons.Create[
info: [name: "Normalize", wx: thisX, wy: thisY, parent: viewer, border: TRUE ],
proc: NormalizeButtonProc,
guarded: FALSE,
fork: TRUE
];
flavorButtonX ← thisX + prev.ww + hMargin;
flavorButtonY ← thisY;
thisX ← entryHSpace; -- Continue on next line
thisY ← thisY + prev.wh + entryVSpace;
Build the various sliders
FOR control: Control IN Control DO
[colorAdjust[control], kavs[control]] ← MakeSliderWithTitle[sliderNames[control]];
ENDLOOP;
KnobAttach.Attach[kavs[dark], left];
KnobAttach.Attach[kavs[contrast], right];
Add the buttons for the various flavors
FOR each: LIST OF StdAdjuster ← stdAdjusters, each.rest UNTIL each=NIL DO
private: REF Private ← NARROW[each.first.private];
private.v ← NewFlavorButton[each.first.flavor];
ENDLOOP;
Color brush stuff
MakeRule[];
MakeTitle["Brush Parameters"];
[] ← MakeSliderWithTitle["Brush Size", AdjustColorBrushRange];
[] ← MakeSliderWithTitle["Color Distance", AdjustColorBrushDistance];
height ← thisY ← thisY + entryVSpace;
Color correct
MakeRule[];
MakeTitle["Color Correction Transform"];
thisX ← entryHSpace + graphInset;
FOR each: LIST OF ROPELIST["Red", "Green", "Blue", "Offset"], each.rest UNTIL each=NIL DO
prev ← Labels.Create[info: [name: each.first, wx: thisX, wy: thisY, parent: viewer, border: FALSE]];
thisX ← thisX + trcWidth/4-hMargin + hMargin;
ENDLOOP;
thisX ← entryHSpace + graphInset;
thisY ← thisY + prev.wh + 2;
FOR to: NAT IN [0..3) DO
FOR from: NAT IN [0..4) DO
id: REF TransformCoords ~ NEW[TransformCoords ← [from: from, to: to]];
MakeTextInputViewer[id: id, title: IF from=0 THEN SELECT to FROM 0 => "To Red", 1 => "To Green", 2 => "To Blue", ENDCASE => "Error" ELSE NIL, initialContents: Convert.RopeFromReal[from: transform[from][to]], ww: trcWidth/4, newLine: from=3, deltaThisY: IF to=2 THEN entryVSpace ELSE 0];
ENDLOOP;
ENDLOOP;
MakeToggleButton["Log Data", alreadyLog, TRUE];
MakeToggleButton["Invert in", invertIn, TRUE];
MakeToggleButton["out", invertOut, FALSE];
};
NewFlavorButton: PROC [flavor: Flavor] RETURNS [b: Buttons.Button] ~ {
IF NoColorEditTool[] THEN RETURN; --Nothing we can do...
b ← Buttons.Create[
info: [
name: Convert.RopeFromAtom[flavor, FALSE],
parent: viewer,
wx: flavorButtonX,
wy: flavorButtonY
],
proc: SelectFlavor,
clientData: flavor,
fork: TRUE
];
flavorButtonX ← flavorButtonX + b.ww + hMargin;
};
Wait2Seconds: CONDITION;
TRCUpdated: CONDITION;
trcUpdated: BOOLEANTRUE;
UpdateTRCProcess: ENTRY PROC [v: ATV.AISTRCViewer] ~ {
The process is responsible for keeping the TRC viewer handed to it in good shape.
ENABLE UNWIND => NULL;
Process.SetPriority[Process.priorityBackground];
TRUSTED {Process.InitializeCondition[@Wait2Seconds, Process.SecondsToTicks[2]]};
UNTIL v.destroyed DO
UNTIL trcUpdated DO
WAIT TRCUpdated;
ENDLOOP;
IF ~v.destroyed AND stdInControl.private#NIL THEN {
private: REF Private ~ NARROW[stdInControl.private];
AISTRCViewers.Set[v, private.trc];
ViewerOps.PaintViewer[viewer: v, hint: client];
trcUpdated ← FALSE;
};
WAIT Wait2Seconds;
ENDLOOP;
};
TakeHistogram: Buttons.ButtonProc ~ {
histogram: AISUtils.Histogram;
histogram ← AISUtils.TakeHistogram[ColorEditViewers.GetWRef[ColorEditViewers.CurrentViewer[]] ! ANY => GOTO NoHistogram];
AISHistogramViewers.Set[hist, histogram];
ViewerOps.PaintViewer[hist, all];
EXITS
NoHistogram => {
MessageWindow.Append["Select AIS Viewer first.", TRUE];
MessageWindow.Blink[];
};
};
SetGamma: Commander.CommandProc = {
[cmd: Commander.Handle] RETURNS [result: REF ANY ← NIL, msg: ROPE ← NIL]
arg: CommandTool.ArgumentVector ~ CommandTool.Parse[cmd: cmd];
IF arg.argc#2 THEN GOTO Failure;
AdjustColor.NewGamma[Convert.RealFromRope[r: arg[1] ! Convert.Error => GOTO Failure]];
EXITS Failure => RETURN [msg: "Usage: Gamma <realValue>", result: $Failure];
};
Init: PROC ~ {
Commander.Register[key: "MakeColorEditTool", proc: MakeColorEditToolCommand, doc: "Create a Color Edit Tool" ];
Commander.Register[key: "Gamma", proc: SetGamma, doc: "Set a new value of gamma for the 24-bit color maps (Gamma <realValue ← 2.2>)" ];
[] ← MakeColorEditTool[];
};
Init[];
}.