<> <> <> <> <> <> 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; <> ROPE: TYPE ~ Rope.ROPE; <> 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]; <> 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]; <> viewer: Containers.Container _ NIL; --The actual ColorEdit Tool viewer <> 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" ]; <> 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] ]]; <> 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] ~ { <> 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 { <> p: PROCESS _ FORK client.byeProc[from, to]; }; NOTIFY NobodyInControl; }; <> 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]; }; <> 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; <> MakeTitle["Tone Reproduction Curve"]; <> 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]]}; <> 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; <> FOR control: Control IN Control DO [colorAdjust[control], kavs[control]] _ MakeSliderWithTitle[sliderNames[control]]; ENDLOOP; KnobAttach.Attach[kavs[dark], left]; KnobAttach.Attach[kavs[contrast], right]; <> 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; <> MakeRule[]; MakeTitle["Brush Parameters"]; [] _ MakeSliderWithTitle["Brush Size", AdjustColorBrushRange]; [] _ MakeSliderWithTitle["Color Distance", AdjustColorBrushDistance]; <<>> height _ thisY _ thisY + entryVSpace; <> 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] ~ { <> 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; }; <> <> < GOTO NoHistogram];>> <> <> <> < {>> <> <> <<};>> <<};>> 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 ", 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 )" ]; [] _ MakeColorEditTool[]; }; Init[]; }.