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 TRC ← NEW[TRC],
interleaved: REF TRC ← NEW[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 BOOLEAN ← ALL[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: PROCESS ← FORK 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 BOOL ← ALL[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:
ROPE ←
NIL, ww:
INTEGER ← trcWidth, deltaThisY:
INTEGER ← entryVSpace, newLine:
BOOL ←
TRUE] ~ {
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
ROPE ←
LIST["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: BOOLEAN ← TRUE;
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[];
};
}.