DIRECTORY Args, CedarProcess, Commander, CommanderOps, Controls, CtBasic, CtDispatch, CtMap, CtViewer, Imager, ImagerColor, ImagerFont, IO, Menus, Process, Random, RawViewers, Real, RealFns, Rope, RuntimeError, TIPUser, ViewerClasses, ViewerOps, ViewersWorldInstance, ViewersWorldTypes; CtMapCommandsImpl: CEDAR PROGRAM IMPORTS Args, CedarProcess, CommanderOps, Controls, CtBasic, CtDispatch, CtMap, CtViewer, Imager, ImagerFont, IO, Menus, Process, Random, RawViewers, Real, RealFns, Rope, RuntimeError, TIPUser, ViewerOps, ViewersWorldInstance ~ BEGIN Arg: TYPE ~ Args.Arg; Control: TYPE ~ Controls.Control; OuterData: TYPE ~ Controls.OuterData; CtProc: TYPE ~ CtDispatch.CtProc; Cmap: TYPE ~ CtMap.Cmap; RGB: TYPE ~ ImagerColor.RGB; ROPE: TYPE ~ Rope.ROPE; CmEditData: TYPE ~ REF CmEditDataRec; CmEditDataRec: TYPE ~ RECORD [ cm, cmSave: Cmap, mono, watch: BOOL ¬ FALSE, w, n, base, i0, i1, y0, y1: NAT ¬ 0, yMul, xScale, yScale: REAL ¬ 0.0, x: ARRAY[0..255] OF NAT ¬ ALL[0], y: ARRAY[0..2] OF ARRAY[0..255] OF NAT ¬ ALL[ALL[0]], bases: ARRAY[0..2] OF NAT ¬ ALL[0], viewer: ViewerClasses.Viewer ¬ NIL ]; CmEditPaint: ViewerClasses.PaintProc ~ { Action: PROC ~ { CmEditInit[d]; CmEditDrawCmap[context, d, self.ww, self.wh]; Imager.SetFont[context, ImagerFont.Find["Xerox/TiogaFonts/Helvetica14"]]; IF d.mono THEN { Imager.SetXYI[context, self.ww-50, 10+d.bases[2]]; Imager.ShowRope[context, "mono"]; } ELSE FOR n: NAT IN [0..2] DO Imager.SetXYI[context, self.ww-50, 10+d.bases[n]]; Imager.ShowRope[context, SELECT n FROM 0=>"red", 1=>"green", ENDCASE=>"blue"]; Imager.MaskRectangleI[context, 0, d.bases[n], self.ww, 1]; ENDLOOP; }; d: CmEditData ¬ NARROW[self.data]; IF whatChanged = NIL THEN Imager.DoWithBuffer[context, Action, 0, 0, self.ww, self.wh] ELSE CmEditUpdate[context, d]; }; CmEditInit: PROC [d: CmEditData] ~ { x: REAL ¬ 0; xInc: REAL ¬ d.viewer.cw/255.0; compH: NAT ¬ d.viewer.ch/3; d.w ¬ RoundI[xInc]; d.yMul ¬ (IF d.mono THEN d.viewer.ch ELSE compH)/255.0; d.xScale ¬ IF d.viewer.cw > 1.0 THEN 255.0/(d.viewer.cw-1) ELSE 0.0; d.yScale ¬ IF d.yMul # 0.0 THEN 1.0/d.yMul ELSE 0.0; FOR i: NAT IN [0..255] DO d.x[i] ¬ RoundI[x]; x ¬ x+xInc; ENDLOOP; FOR n: NAT IN [IF d.mono THEN 2 ELSE 0..2] DO y: INTEGER ¬ d.bases[n] ¬ (2-n)*compH; FOR i: NAT IN[0..255] DO d.y[n][i] ¬ RoundI[y+d.yMul*d.cm[n][i]]; ENDLOOP; ENDLOOP; TRUSTED {IF NOT d.watch THEN Process.Detach[FORK CmEditWatch[d]]}; }; CmEditCompIndex: PROC [d: CmEditData, y: INTEGER] RETURNS [NAT] ~ INLINE { IF d.mono THEN RETURN[2]; RETURN[SELECT y FROM > d.bases[0] => 0, > d.bases[1] => 1, ENDCASE => 2]; }; CmEditNotify: ViewerClasses.NotifyProc ~ { d: CmEditData ¬ NARROW[self.data]; mouse: TIPUser.TIPScreenCoords ¬ NARROW[input.first]; SELECT input.rest.first FROM $leftDown, $rightDown => { d.n ¬ CmEditCompIndex[d, mouse.mouseY]; d.base ¬ d.bases[d.n]; d.i0 ¬ d.i1 ¬ RoundI[mouse.mouseX*d.xScale]; d.y0 ¬ d.y1 ¬ mouse.mouseY-d.bases[d.n]; }; $leftHeld => { IF d.n # CmEditCompIndex[d, mouse.mouseY] THEN RETURN; -- disallow comp change d.i0 ¬ d.i1; d.y0 ¬ d.y1; d.i1 ¬ RoundI[mouse.mouseX*d.xScale]; d.y1 ¬ mouse.mouseY-d.base; }; $rightHeld => { IF d.n # CmEditCompIndex[d, mouse.mouseY] THEN RETURN; -- disallow comp change d.i0 ¬ d.i1; d.y0 ¬ d.y1; d.i1 ¬ RoundI[mouse.mouseX*d.xScale]; d.y1 ¬ mouse.mouseY-d.base; }; ENDCASE => RETURN; ViewerOps.PaintViewer[viewer: self, hint: client, whatChanged: d, clearClient: FALSE]; }; CmEditUpdate: PROC [cxt: Imager.Context, d: CmEditData] ~ { i0, i1: NAT; y, dy, imy, imdy: REAL; IF d.i1 > d.i0 THEN {i0 ¬ d.i0; i1 ¬ d.i1; y ¬ d.yScale*d.y0; imy ¬ d.base+d.y0} ELSE {i0 ¬ d.i1; i1 ¬ d.i0; y ¬ d.yScale*d.y1; imy ¬ d.base+d.y1}; Imager.SetColor[cxt, Imager.white]; FOR i: NAT IN [i0..i1] DO Imager.MaskRectangleI[cxt, d.x[i], d.y[d.n][i], d.w, 1]; ENDLOOP; Imager.SetColor[cxt, Imager.black]; dy ¬ IF i0 = i1 THEN 0.0 ELSE d.yScale*(1.0*(d.y1-d.y0))/(d.i1-d.i0); imdy ¬ d.yMul*dy; FOR i: NAT IN [i0..i1] DO IF d.mono THEN d.cm[0][i] ¬ d.cm[1][i] ¬ d.cm[2][i] ¬ RoundI[y] ELSE d.cm[d.n][i] ¬ RoundI[y]; CtMap.WriteEntry[i, [d.cm[0][i], d.cm[1][i], d.cm[2][i]]]; Imager.MaskRectangleI[cxt, d.x[i], d.y[d.n][i] ¬ RoundI[imy], d.w, 1]; imy ¬ imy+imdy; y ¬ y+dy; ENDLOOP; }; cmEditUsage: ROPE ~ "Cm Edit [-mono]: interactively edit the colormap."; CmEdit: PUBLIC CtProc ~ { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd]; cm: Cmap ¬ CtMap.Read[]; data: CmEditData ¬ NEW[CmEditDataRec ¬ [cm: cm, cmSave: CtMap.Copy[cm]]]; v: ViewerClasses.Viewer ¬ ViewerOps.CreateViewer[ flavor: $CmEdit, paint: FALSE, info: [ data: data, name: "Color Map Editor", openHeight: 300+24, iconic: TRUE, column: right, menu: Menus.CreateMenu[], scrollable: FALSE]]; data.mono ¬ argv.argc = 2 AND Rope.Equal[argv[1], "-mono", FALSE]; Menus.AppendMenuEntry[v.menu, Menus.CreateEntry["Undo", CmEditUndo, data,, FALSE]]; Menus.AppendMenuEntry[v.menu, Menus.CreateEntry["Read", CmEditRead, data,, FALSE]]; data.viewer ¬ v; ViewerOps.AddProp[v, $CmEdit, data]; ViewerOps.OpenIcon[v]; }; CmEditDrawCmap: PROC [context: Imager.Context, d: CmEditData, w, h: INTEGER] ~ { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, w, h]; Imager.SetColor[context, Imager.black]; FOR n: NAT IN [IF d.mono THEN 2 ELSE 0..2] DO b: NAT ¬ d.bases[n]; FOR i: NAT IN [0..255] DO Imager.MaskRectangleI[context, d.x[i], RoundI[b+d.yMul*d.cm[n][i]], d.w, 1]; ENDLOOP; ENDLOOP; }; CmEditNewCmap: PROC [data: CmEditData] ~ { data.cmSave ¬ CtMap.Copy[data.cm ¬ CtMap.Read[]]; ViewerOps.PaintViewer[data.viewer, client, FALSE, NIL]; }; CmEditRead: Menus.ClickProc ~ {CmEditNewCmap[NARROW[clientData]]}; CmEditUndo: Menus.ClickProc ~ {CtMap.Write[NARROW[clientData, CmEditData].cmSave]}; CmEditWatch: PROC [data: CmEditData] ~ { data.watch ¬ TRUE; WHILE data.watch DO CtMap.WaitTilNew[]; CmEditNewCmap[data]; ENDLOOP; }; CmEditDestroy: ViewerClasses.DestroyProc ~ {NARROW[self.data, CmEditData].watch ¬ FALSE}; cmSaveUsage: ROPE ~ "Cm Save ."; CmSave: CtProc ~ { IF Args.GetRope[cmd] = NIL THEN RETURN[error: "incorrect arguments"]; CtMap.Save[CtMap.Read[], Args.GetRope[cmd]]; }; cmLoadUsage: ROPE ~ "Cm Load ."; CmLoad: CtProc ~ { IF Args.GetRope[cmd] = NIL THEN RETURN[error: "incorrect arguments"]; IF NOT CtMap.Load[Args.GetRope[cmd]] THEN RETURN[error: "Can't read file."]; }; cmLinearUsage: ROPE ~ "Cm Linear: set the color map to a linear ramp."; CmLinear: CtProc ~ {CtMap.Mono[]}; cmDitherUsage: ROPE ~ "Cm Dither [-dense] [gamma (default: 1.0)]: dither to span RGB space."; CmDither: CtProc ~ { a, b: Arg; [a, b] ¬ Args.ArgsGet[cmd, "[r-dense%b"]; IF b.ok OR RawViewers.GetColorWorld[ViewersWorldInstance.GetWorld[]] = $dense THEN DoGamma[IF a.ok THEN a.real ELSE 1.0, $Dither] ELSE { -- from RawViewersImpl.mesa GCorrect: PROC [c: REAL] RETURNS [b: NAT] ~ {b ¬ Clip[RealFns.Power[c,invGamma]]}; Clip: PROC [r: REAL] RETURNS [s: NAT] ~ { s ¬ Real.Round[r*255.0]; IF s < 0 THEN s ¬ 0 ELSE IF s > 255 THEN s ¬ 255; }; SetColor: PROC [i: NAT, r, g, b: REAL] ~ { cm[0][i] ¬ GCorrect[r]; cm[1][i] ¬ GCorrect[g]; cm[2][i] ¬ GCorrect[b]; }; SetColor2: PROC [i: NAT, r, g, b: REAL] ~ { SetColor[i, r, g, b]; SetColor[BYTE.LAST-i, 1.0-r, 1.0-g, 1.0-b]; }; invGamma: REAL; cm: Cmap ¬ CtMap.ObtainCmap[]; invGamma ¬ 1.0/(IF a.ok AND a.real # 0.0 THEN a.real ELSE 2.2); FOR i: NAT IN [0..3) DO FOR j: NAT IN [0..256) DO cm[i][j] ¬ 0; ENDLOOP; ENDLOOP; FOR b: NAT IN [0..4) DO FOR g: NAT IN [0..6) DO FOR r: NAT IN [0..5) DO i: NAT ~ (r*6+g)*4+b; IF i < 60 THEN SetColor2[i, r/4.0, g/5.0, b/3.0]; ENDLOOP; ENDLOOP; ENDLOOP; SetColor2[60, 0.25, 0.25, 0.25]; -- quarter-grey SetColor2[61, 0.5, 0.5, 0.5]; -- half-grey SetColor2[62, 0.1, 0.3, 0.85]; -- distinct/blue CtMap.WriteAndRelease[cm]; }; }; cmGrayUsage: ROPE ~ "Cm Gray [gamma (default = 2.2)]: grayscale image."; CmGray: CtProc ~ { a: Arg; [a] ¬ Args.ArgsGet[cmd, "[r"]; DoGamma[IF a.ok THEN a.real ELSE 2.2, $BW]; }; cmGammaUsage: ROPE ~ "Cm Gamma [value] [-bw | -dither]: if no value, interactive."; Gammaler: Controls.ControlProc ~ {DoGamma[control.value, control.clientUse]}; CmGamma: CtProc ~ { gamma, bw, dither: Arg; [gamma, bw, dither] ¬ Args.ArgsGet[cmd, "[r-bw%b-dither%b"]; IF bw.ok AND dither.ok THEN RETURN[error: "can't be both bw and dither"] ELSE { mode: ATOM ¬ SELECT TRUE FROM bw.ok => $BW, dither.ok => $Dither, ENDCASE => IF CtBasic.GetBpp[] = 24 THEN $BW ELSE $Dither; init: REAL ¬ IF mode = $BW THEN 2.2 ELSE 1.0; max: REAL ¬ IF mode = $BW THEN 5.0 ELSE 2.0; name: ROPE ¬ IF mode = $BW THEN "Gamma (BW)" ELSE "Gamma (Dither)"; IF gamma.ok THEN DoGamma[gamma.real, mode] ELSE [] ¬ Tool[name, LIST[Controls.NewControl[type: hSlider, max: max, init: init, x: 60, y: 10, w: 200, h: 20, detents: LIST[[init, init]], proc: Gammaler, clientUse: mode]]]; }; }; DoGamma: PROC [gamma: REAL, mode: ATOM] ~ { SELECT mode FROM $Dither => { ditherMap, gammaMap: Cmap; CtMap.Dither[ditherMap ¬ CtMap.ObtainCmap[]]; CtMap.Gamma[gamma, gammaMap ¬ CtMap.ObtainCmap[]]; FOR i: NAT IN [0..255] DO FOR j: NAT IN [0..2] DO gammaMap[j][i] ¬ MIN[255, MAX[0, Real.Round[ REAL[ditherMap[j][i]]* (IF i = 0 THEN 1.0 ELSE REAL[gammaMap[j][i]]/REAL[i])]]]; ENDLOOP; ENDLOOP; CtMap.ReleaseCmap[ditherMap]; CtMap.WriteAndRelease[gammaMap]; }; $BW => CtMap.Gamma[gamma]; ENDCASE; }; cmDeGammaUsage: ROPE ~ "Cm DeGamma: apply inverse gamma to color map"; CmDeGamma: CtProc ~ {CtMap.DeGamma[]}; cmNegateUsage: ROPE ~ "Cm Negate: flip color map entries about the center."; CmNegate: CtProc ~ { cmap: Cmap ¬ CtMap.Read[]; FOR n: NAT IN [0..128) DO save: CtMap.RGB ¬ CtMap.GetEntry[cmap, n]; CtMap.SetEntry[cmap, n, CtMap.GetEntry[cmap, 255-n]]; CtMap.SetEntry[cmap, 255-n, save]; ENDLOOP; CtMap.Write[cmap]; }; cmColorUsage: ROPE ~ "Cm Color : Set color map entry i."; CmColor: CtProc ~ { ENABLE Real.RealException => GOTO BadVal; R: PROC [a: Arg] RETURNS [i: INT] ~ {i ¬ MAX[0, MIN[255, RoundI[255.0*a.real]]]}; i, r, g, b: Arg; [i, r, g, b] ¬ Args.ArgsGet[cmd, "%irrr" ! Args.Error => {error ¬ reason; CONTINUE}]; IF error # NIL THEN RETURN; CtMap.WriteEntry[i.int, [R[r], R[g], R[b]]]; EXITS BadVal => RETURN[error: "real must be in [0.0..1.0]"]; }; cmRampUsage: ROPE ~ "Cm Ramp ."; CmRamp: CtProc ~ { ENABLE Real.RealException => GOTO BadVal; R: PROC [a: Arg] RETURNS [i: INT] ~ {i ¬ MAX[0, MIN[255, RoundI[255.0*a.real]]]}; i0, r0, g0, b0, i1, r1, g1, b1: Arg; [i0, r0, g0, b0, i1, r1, g1, b1] ¬ Args.ArgsGet[cmd, "%irrrirrr" ! Args.Error => {error ¬ reason; CONTINUE}]; IF error # NIL THEN RETURN; CtMap.Ramp[i0.int, i1.int, [R[r0], R[g0], R[b0]], [R[r1], R[g1], R[b1]]]; EXITS BadVal => RETURN[error: "real must be in [0.0..1.0]"]; }; cmSplineUsage: ROPE ~ "Cm Spline i0 r0 g0 b0 i1 r1 g1 b1"; CmSpline: CtProc ~ { IF Args.NArgs[cmd] # 8 THEN RETURN[error: "bad args"] ELSE { SlowInOut: PROC [value0, value1: NAT, t: REAL] RETURNS [REAL] ~ { t2: REAL ¬ t*t; t3: REAL ¬ t*t2; RETURN[value0+(3.0*t2-t3-t3)*(value1-value0)]; }; cmap: CtMap.Cmap ¬ CtMap.Read[]; i0, r0, g0, b0, i1, r1, g1, b1: Arg; [i0, r0, g0, b0, i1, r1, g1, b1] ¬ Args.ArgsGet[cmd, "%iiiiiiii" ! Args.Error => {error ¬ reason; GOTO Bad}]; IF i1.int <= i0.int THEN RETURN; FOR i: INT IN [i0.int..i1.int] DO t: REAL ¬ REAL[i-i0.int]/REAL[i1.int-i0.int]; r: NAT ¬ RoundI[SlowInOut[r0.int, r1.int, t]]; g: NAT ¬ RoundI[SlowInOut[g0.int, g1.int, t]]; b: NAT ¬ RoundI[SlowInOut[b0.int, b1.int, t]]; CtMap.SetEntry[cmap, i, [r, g, b]]; ENDLOOP; CtMap.Write[cmap]; }; EXITS Bad => NULL; }; cmTriColorsUsage: ROPE ~ "Cm TriColors [-gamma ]."; CmTriColors: CtProc ~ { r1, g1, b1, r2, g2, b2, r3, g3, b3, gammaA: Arg; [r1, g1, b1, r2, g2, b2, r3, g3, b3, gammaA] ¬ Args.ArgsGet[cmd, "%rrrrrrrrr-gammaA%r" ! Args.Error => {error ¬ reason; CONTINUE}]; IF error # NIL THEN RETURN ELSE { ENABLE RuntimeError.BoundsFault => GOTO Bad; R: PROC [a: Arg] RETURNS [r: REAL] ~ {r ¬ MAX[0.0, MIN[1.0, a.real]]}; Scale: PROC [v: RGB, s: REAL] RETURNS [r: RGB] ~ {r ¬ [s*v.R, s*v.G, s*v.G]}; Interp: PROC [v1, v2: RGB, a: REAL] RETURNS [v: RGB] ~ { -- a=1 => v2 v ¬ [v1.R+a*(v2.R-v1.R), v1.G+a*(v2.G-v1.G), v1.B+a*(v2.B-v1.B)]}; G: PROC [a: REAL] RETURNS [r: REAL] ~ {r ¬ CtMap.ApplyGamma[a, gamma]}; Gam: PROC [v: RGB] RETURNS [r: RGB] ~ {r ¬ [G[v.R], G[v.G], G[v.B]]}; gamma: REAL ¬ IF gammaA.ok THEN gammaA.real ELSE 2.2; rgb1: RGB ¬ [R[r1], R[g1], R[b1]]; rgb2: RGB ¬ [R[r2], R[g2], R[b2]]; rgb3: RGB ¬ [R[r3], R[g3], R[b3]]; cm: Cmap ¬ CtMap.ObtainCmap[]; FOR i: INTEGER IN [0..255] DO a: REAL ¬ REAL[IF i < 128 THEN i ELSE i-128]/127.0; v: RGB ¬ IF i < 128 THEN Gam[Interp[rgb1, rgb2, a]] ELSE Gam[Interp[rgb2, rgb3, a]]; CtMap.SetEntry[cm, i, [RoundI[v.R], RoundI[v.G], RoundI[v.B]]]; ENDLOOP; CtMap.WriteAndRelease[cm]; EXITS Bad => RETURN[error: "bad argument"]; }; }; cmScaleUsage: ROPE ~ "Cm Scale ."; CmScale: CtProc ~ { IF NOT Args.ArgReal[cmd].ok THEN RETURN[error: "bad argument"]; CtMap.Scale[CtMap.Read[], Args.ArgReal[cmd].real]; }; cmTriScaleUsage: ROPE ~ "Cm Scale ."; CmTriScale: CtProc ~ { r, g, b: Arg; [r, g, b] ¬ Args.ArgsGet[cmd, "%rrr" ! Args.Error => {error ¬ reason; CONTINUE}]; CtMap.TriScale[CtMap.Read[], r.real, g.real, b.real]; }; cmAddUsage: ROPE ~ "Cm Add ."; CmAdd: CtProc ~ { IF NOT Args.ArgInt[cmd].ok THEN RETURN[error: "bad argument"]; CtMap.Add[CtMap.Read[], Args.ArgInt[cmd].int]; }; cmInterpUsage: ROPE ~ "Cm Interp ."; CmInterp: CtProc ~ { cmLoad: Cmap ¬ CtMap.NewCmap[]; IF NOT Args.ArgReal[cmd].ok OR NOT CtMap.Load[Args.GetRope[cmd, 1], cmLoad] THEN RETURN[error: "bad arguments"]; CtMap.Interp[Args.ArgReal[cmd].real, CtMap.Read[], cmLoad]; }; cmComposeUsage: ROPE ~ "Cm Compose : result _ c2[c1]."; CmCompose: CtProc ~ { cm1, cm2: Cmap; ok1: BOOL ¬ CtMap.Load[Args.GetRope[cmd, 0], cm1 ¬ CtMap.NewCmap[]]; ok2: BOOL ¬ CtMap.Load[Args.GetRope[cmd, 1], cm2 ¬ CtMap.NewCmap[]]; IF NOT ok1 OR NOT ok2 THEN RETURN[error: "bad color map name(s)."]; CtMap.Compose[cm1, cm2]; }; cmTentsUsage: ROPE ~ "Cm Tents [nTents]."; TentCycler: ToolProc ~ { c: Control ¬ o.controls.first; abs: REAL ¬ ABS[c.value]; FOR ntent: REAL ¬ .5, ntent+c.value WHILE ABS[ntent] < 128 AND NOT o.destroyed DO IF c.value = 0 THEN Process.Pause[Process.MsecToTicks[100]]; -- no hog CtMap.Tents[ntent]; ENDLOOP; IF abs > 0.01 THEN Process.Pause[Process.MsecToTicks[RoundI[1000.0/abs]]]; }; CmTents: CtProc ~ { IF Args.ArgReal[cmd].ok THEN CtMap.Tents[Args.ArgReal[cmd].real] ELSE [] ¬ Tool["Cytent", LIST[Controls.NewControl[name: "Speed", type: hSlider, x: 60, y: 10, w: 200, h: 20, min: -2, max: 2, detents: LIST[[, 0]]]], TentCycler]; }; cmLineUsage: ROPE ~ "Cm Line ."; CmLine: CtProc ~ { RealMod: PROC [a, b: REAL] RETURNS [REAL] ~ {RETURN[a-Real.Floor[a/b]*b]}; ncyclesA: Arg ¬ Args.ArgReal[cmd, 0]; widthA: Arg ¬ Args.ArgInt[cmd, 1]; ncycles: REAL ¬ IF ncyclesA.ok THEN ncyclesA.real ELSE 1; width: INTEGER ¬ IF widthA.ok THEN widthA.int ELSE 1; period: REAL ¬ 256.0/ncycles; cm: Cmap ¬ CtMap.ObtainCmap[]; FOR i: NAT IN [0..256) DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 255; ENDLOOP; FOR i: NAT IN [0..256) DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ IF RealMod[i, period] < width THEN 0 ELSE 255; ENDLOOP; CtMap.WriteAndRelease[cm]; }; cmSquareUsage: ROPE ~ "Cm Square ."; CmSquare: CtProc ~ { ncycles: REAL ¬ IF Args.ArgReal[cmd].ok THEN Args.ArgReal[cmd].real ELSE 1; CtMap.Square[ncycles]; }; cmSinUsage: ROPE ~ "Cm Sin ."; CmSin: CtProc ~ { v: ARRAY[0..2] OF REAL ¬ ALL[0.0]; IF Args.NArgs[cmd] < 1 THEN RETURN[error: "bad arguments"]; FOR i: NAT IN [0..2] DO a: Arg ¬ Args.ArgReal[cmd, i]; IF NOT a.ok AND i = 0 THEN RETURN[error: "bad arguments"]; v[i] ¬ IF a.ok THEN a.real ELSE v[i-1]; ENDLOOP; CtMap.Sin[v[0], v[1], v[2]]; }; cmGaussUsage: ROPE ~ "Cm Gauss: put a gaussian in color map."; CmGauss: CtProc ~ {CtMap.Gauss[]}; cmContoursUsage: ROPE ~ "Cm Contours [-power ] [-ncycles ] show contours."; CmContours: CtProc ~ { nCyclesArg, powerArg: Arg; [nCyclesArg, powerArg] ¬ Args.ArgsGet[cmd, "-power%r-ncycles%r" ! Args.Error => {error ¬ reason; CONTINUE}]; IF error # NIL THEN RETURN ELSE { nCycles: REAL ~ IF nCyclesArg.ok THEN nCyclesArg.real ELSE 4.0; power: REAL ~ 1.0/(IF powerArg.ok THEN MAX[0.01, powerArg.real] ELSE 6.0); FOR n: NAT IN [0..255] DO t: REAL ~ REAL[n]/255.0; intensity: REAL ¬ .5+.5*RealFns.Sin[nCycles*2.5*3.141592*RealFns.Power[t, power]]; cmapValue: INT ¬ RoundI[(t+(1.0-t)*intensity)*255.0]; CtMap.WriteEntry[n, [cmapValue, cmapValue, cmapValue]]; ENDLOOP; CtMap.JustWritten[]; }; }; cmOnlyUsage: ROPE ~ "Cm Only ."; CmOnly: CtProc ~ { a: ROPE ¬ Args.GetRope[cmd]; IF a = NIL THEN RETURN[error: "bad arguments"]; SELECT TRUE FROM a.Equal["red"] => CtMap.PrimaryOnly[r]; a.Equal["green"] => CtMap.PrimaryOnly[g]; a.Equal["blue"] => CtMap.PrimaryOnly[b]; ENDCASE => RETURN[error: "argument one of: red, green, blue."]; }; cmToOthersUsage: ROPE ~ "Cm ToOthers one component to other two."; CmToOthers: CtProc ~ { source: ROPE ~ Args.GetRope[cmd]; i0dst, i1dst, isrc: INT ¬ -1; SELECT TRUE FROM Rope.Equal[source, "red", FALSE] => {isrc ¬ 0; i0dst ¬ 1; i1dst ¬ 2}; Rope.Equal[source, "grn", FALSE] => {isrc ¬ 1; i0dst ¬ 0; i1dst ¬ 2}; Rope.Equal[source, "blu", FALSE] => {isrc ¬ 2; i0dst ¬ 0; i1dst ¬ 1}; ENDCASE; IF isrc # -1 THEN { cmap: CtMap.Cmap ¬ CtMap.Read[]; FOR n: NAT IN [0..255] DO cmap[i0dst][n] ¬ cmap[i1dst][n] ¬ cmap[isrc][n]; ENDLOOP; CtMap.Write[cmap]; CtMap.JustWritten[]; }; }; cmNBitsUsage: ROPE ~ "Cm NBits [nBits]: if no argument, interactive."; NBitser: Controls.ControlProc ~ { cm: Cmap ¬ CtMap.ObtainCmap[]; CtMap.Mono[cm]; CtMap.NBits[cm, RoundI[control.value]]; CtMap.ReleaseCmap[cm]; }; CmNBits: CtProc ~ { IF Args.ArgInt[cmd].ok THEN CtMap.NBits[CtMap.Read[], Args.ArgInt[cmd].int] ELSE [] ¬ Tool["CmNBits", LIST[Controls.NewControl[type: hSlider, x: 60, y: 10, w: 80, h: 20, min: 1, max: 8, init: 8, precision: 0, proc: NBitser]]]; }; cmPrintUsage: ROPE ~ "Cm Print: print the color map contents."; CmPrint: CtProc ~ { cm: Cmap ¬ CtMap.Read[]; FOR i: INT IN [0..256) DO IO.PutFL[cmd.out, "%3g:\t%3g\t%3g\t%3g\n", LIST[IO.int[i], IO.int[cm[0][i]], IO.int[cm[1][i]], IO.int[cm[2][i]]]]; ENDLOOP; }; cmScrambleUsage: ROPE ~ "Cm Scramble: randomly interchange entries."; CmScramble: CtProc ~ {CtMap.Scramble[CtMap.Read[]]}; cmRandomUsage: ROPE ~ "Cm Random: randomize the color map."; CmRandom: CtProc ~ {CtMap.Rand[]}; cmFlashUsage: ROPE ~ "Cm Flash: interactively randomize color map."; Flasher: ToolProc ~ { CtMap.Rand[]; Process.Pause[Process.MsecToTicks[RoundI[1000*o.controls.first.value]]]; }; CmFlash: CtProc ~ { [] ¬ Tool["Flash", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: 1.0, max: 0.0, init: 0.5]], Flasher]; }; cmSpeckleUsage: ROPE ~ "Cm Speckle: randomly set single entries."; Speckler: ToolProc ~ { RandInt: PROC RETURNS [INTEGER] ~ {RETURN[Random.ChooseInt[max: 255]]}; Process.Pause[Process.MsecToTicks[RoundI[100*o.controls.first.value]]]; CtMap.WriteEntry[RandInt[], [RandInt[], RandInt[], RandInt[]]]; }; CmSpeckle: CtProc ~ { [] ¬ Tool["Speckle", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: 1, max: 0]], Speckler]; }; cmCycleUsage: ROPE ~ "Cm Cycle: interactively cycle color map."; Cycler: ToolProc ~ { abs: REAL ¬ ABS[o.controls.first.value]; n: INT ¬ MAX[1, RoundI[abs]]; IF abs = 0.0 THEN {Process.Pause[Process.MsecToTicks[1000]]; RETURN}; CtMap.Write[CtMap.Cycle[cm, IF o.controls.first.value > 0 THEN n ELSE -n]]; IF abs > 0.01 THEN Process.Pause[Process.MsecToTicks[RoundI[1000.0/abs]]]; }; CmCycle: CtProc ~ { [] ¬ Tool["Cycle", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: -25, max: 25, init: 0, detents: LIST[[, 0.0]]]], Cycler]; }; cmShiftUsage: ROPE ~ "Cm Shift [n]: if no argument, interactive."; Shifter: Controls.ControlProc ~ { cm: Cmap ¬ CtMap.ObtainCmap[]; CtMap.Write[CtMap.Cycle[CtMap.Read[cm], RoundI[control.valuePrev-control.value]]]; CtMap.ReleaseCmap[cm]; }; CmShift: CtProc ~ { IF Args.ArgInt[cmd].ok THEN CtMap.Write[CtMap.Cycle[CtMap.Read[], Args.ArgInt[cmd].int]] ELSE [] ¬ Tool["Shift", LIST[Controls.NewControl[type: dial, report: FALSE, x: 60, y: 10, w: 80, h: 80, max: 255, init: 0, detents: LIST[[, 0]], proc: Shifter]]]; }; cmThresholdUsage: ROPE ~ "Cm Threshold [n]: in no argument, interactive."; Thresholder: Controls.ControlProc ~ {CtMap.Write[MakeThreshMap[RoundI[control.value]]]}; MakeThreshMap: PROC [thresh: INT] RETURNS [cm: Cmap] ~ { cm ¬ CtMap.ObtainCmap[]; thresh ¬ thresh MOD 255; FOR i: INT IN [0 .. thresh) DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 0; ENDLOOP; FOR i: INT IN [thresh .. 255] DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 255; ENDLOOP; }; CmThreshold: CtProc ~ { IF Args.ArgInt[cmd].ok THEN CtMap.Write[MakeThreshMap[Args.ArgInt[cmd].int]] ELSE [] ¬ Tool["Threshold", LIST[Controls.NewControl[type: dial, report: TRUE, x: 60, y: 10, w: 80, h: 80, max: 255, init: 0, detents: LIST[[, 0]], proc: Thresholder]]]; }; cmRippleUsage: ROPE ~ "Cm Ripple: interactively ripple sine waves."; Rip: TYPE ~ RECORD [nSins, incr: REAL ¬ 1.0]; Rippler: ToolProc ~ { nSins: ARRAY [0..2] OF REAL; c: ARRAY [0..2] OF Control ¬ [o.controls.first, o.controls.rest.first, o.controls.rest.rest.first]; FOR i: NAT IN [0..2] DO data: REF Rip ¬ NARROW[c[i].clientData]; data.incr ¬ SELECT data.nSins FROM < 0.25 => 1.0, > 25.0 => -1.0, ENDCASE => data.incr; nSins[i] ¬ data.nSins ¬ data.nSins+data.incr*c[i].value; ENDLOOP; CtMap.Sin[nSins[0], nSins[1], nSins[2]]; }; CmRipple: CtProc ~ { NewC: PROC [r: ROPE, y: NAT] RETURNS [c: Control] ~ { c ¬ Controls.NewControl[r, hSlider, NEW[Rip],, 0.1, 0.05,, FALSE,,, 60, y, 200, 20, [left, center]]; }; [] ¬ Tool["Ripple", LIST[NewC["red", 10], NewC["grn", 30], NewC["blu", 50]], Rippler]; }; cmSnakeUsage: ROPE ~ "Cm Snake: snake through color space."; Snaker: ToolProc ~ { r, g, b: REAL; [r, g, b] ¬ GetSnake[]; [] ¬ CtMap.Cycle[cm, 1]; CtMap.SetEntry[cm, 1, [RoundI[255*r], RoundI[255*g], RoundI[255*b]]]; CtMap.Write[cm]; Process.Pause[Process.MsecToTicks[RoundI[100*(o.controls.first.max-o.controls.first.value)]]]; }; r, g, b: Section; Section: TYPE ~ RECORD [count: INTEGER ¬ 0, p, d: REAL ¬ 0.0]; GetSnake: PROC RETURNS [REAL, REAL, REAL] ~ { NewSection: PROC [p: REAL] RETURNS [s: Section] ~ { s.p ¬ p; s.count ¬ Random.ChooseInt[min: 5, max: 30]; s.d ¬ ((1./1024.)*REAL[Random.ChooseInt[min: 1, max: 1024]]-p)/REAL[s.count]; }; IF (r.count ¬ r.count-1) <= 0 THEN r ¬ NewSection[r.p]; IF (g.count ¬ g.count-1) <= 0 THEN g ¬ NewSection[g.p]; IF (b.count ¬ b.count-1) <= 0 THEN b ¬ NewSection[b.p]; r.p ¬ MAX[0.0, MIN[1.0, r.p+r.d]]; g.p ¬ MAX[0.0, MIN[1.0, g.p+g.d]]; b.p ¬ MAX[0.0, MIN[1.0, b.p+b.d]]; RETURN[r.p, g.p, b.p]; }; CmSnake: CtProc ~ {[] ¬ Tool["Snake", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 350, h: 20, taper: exp]], Snaker]}; cmBlendUsage: ROPE ~ "Cm Blend : continuously blend."; rec: TYPE ~ RECORD [data: SEQUENCE num: NAT OF Cmap]; Blender: PROC [o: OuterData, cm: REF rec, ncm: NAT] ~ { inc: INTEGER ¬ 1; n0, n1: INTEGER ¬ 0; WHILE NOT o.destroyed DO IF (n1 ¬ n0+inc) = -1 OR n1 = ncm THEN {inc ¬ -inc; n1 ¬ n0+inc}; FOR t: REAL ¬ 0, t+o.controls.first.value WHILE t <= 0.5*3.1415926535 DO IF o.destroyed THEN EXIT; CtMap.Interp[RealFns.Sin[t], cm.data[n0], cm.data[n1]]; ENDLOOP; n0 ¬ n1; ENDLOOP; Cleanup[NARROW[o.clientData, REF ToolData]]; }; CmBlend: CtProc ~ { ncm: NAT ¬ 0; cm: REF rec ¬ NEW[rec[Args.NArgs[cmd]]]; FOR a: NAT IN[0..Args.NArgs[cmd]) DO Process.CheckForAbort[]; IF CtMap.Load[Args.GetRope[cmd, a], cm.data[ncm] ¬ CtMap.NewCmap[]] THEN ncm ¬ ncm+1 ELSE IO.PutF1[cmd.out, "Can't read %g\n", IO.rope[Args.GetRope[cmd, a]]]; ENDLOOP; IF ncm # 0 THEN TRUSTED { Process.Detach[FORK Blender[Tool["Color Map Blender", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: .01, max: 1., init: .1]]], cm, ncm]]; }; }; ToolProc: TYPE ~ PROC [o: OuterData, cm: Cmap]; ToolData: TYPE ~ RECORD [cm: Cmap, reset, looping: BOOL]; Tool: PROC [name: ROPE, controls: Controls.ControlList, proc: ToolProc ¬ NIL] RETURNS [o: OuterData] ~{ t: REF ToolData ¬ NEW[ToolData ¬ [CtMap.Read[], FALSE, proc # NIL]]; o ¬ Controls.OuterViewer[ name: name, column: right, buttons: LIST[ Controls.ClickButton["Reset", Reset, t], Controls.ClickButton["Reset&Quit", ResetAndQuit, t]], destroyProc: Destroy, controls: controls, clientData: t]; IF proc # NIL THEN TRUSTED {Process.Detach[FORK Looper[o, proc, t]]}; }; Looper: PROC [o: OuterData, proc: ToolProc, t: REF ToolData] ~ { cm: Cmap ¬ CtMap.Read[]; CedarProcess.SetPriority[background]; -- effective? WHILE NOT o.destroyed DO proc[o, cm]; ENDLOOP; Cleanup[t]; }; Destroy: Controls.DestroyProc ~ { t: REF ToolData ¬ NARROW[clientData]; IF NOT t.looping THEN Cleanup[t]; -- otherwise, do so after loop exits }; Cleanup: PROC [t: REF ToolData] ~ { IF t.reset THEN CtMap.Write[t.cm]; CtMap.ReleaseCmap[t.cm]; CtViewer.PutColormap[CtViewer.currentViewer, CtMap.Read[]]; }; Reset: Controls.ClickProc ~ {CtMap.Write[NARROW[clientData, REF ToolData].cm]}; ResetAndQuit: Controls.ClickProc ~ { NARROW[clientData, REF ToolData].reset ¬ TRUE; ViewerOps.DestroyViewer[parent.parent]; }; RoundI: PROC [r: REAL] RETURNS [INT] ~ INLINE {RETURN[Real.Round[r]]}; ViewerOps.RegisterViewerClass[$CmEdit, NEW[ViewerClasses.ViewerClassRec ¬ [ destroy: CmEditDestroy, paint: CmEditPaint, notify: CmEditNotify, tipTable: TIPUser.InstantiateNewTIPTable["ColorTrix.tip"]]]]; CtDispatch.RegisterCmOp["Basic Commands:", NIL, NIL]; CtDispatch.RegisterCmOp["Save", CmSave, cmSaveUsage]; CtDispatch.RegisterCmOp["Load", CmLoad, cmLoadUsage]; CtDispatch.RegisterCmOp["Linear", CmLinear, cmLinearUsage]; CtDispatch.RegisterCmOp["Gray", CmGray, cmGrayUsage]; CtDispatch.RegisterCmOp["Dither", CmDither, cmDitherUsage]; CtDispatch.RegisterCmOp["Gamma", CmGamma, cmGammaUsage]; CtDispatch.RegisterCmOp["DeGamma", CmDeGamma, cmDeGammaUsage]; CtDispatch.RegisterCmOp["Negate", CmNegate, cmNegateUsage]; CtDispatch.RegisterCmOp["Edit", CmEdit, cmEditUsage]; CtDispatch.RegisterCmOp["Explicit Setting:", NIL, NIL]; CtDispatch.RegisterCmOp["Color", CmColor, cmColorUsage]; CtDispatch.RegisterCmOp["Ramp", CmRamp, cmRampUsage]; CtDispatch.RegisterCmOp["Spline", CmSpline, cmSplineUsage]; CtDispatch.RegisterCmOp["TriColors", CmTriColors, cmTriColorsUsage]; CtDispatch.RegisterCmOp["Scale/Add/Compose/Interpolate:", NIL, NIL]; CtDispatch.RegisterCmOp["Scale", CmScale, cmScaleUsage]; CtDispatch.RegisterCmOp["TriScale", CmTriScale, cmTriScaleUsage]; CtDispatch.RegisterCmOp["Add", CmAdd, cmAddUsage]; CtDispatch.RegisterCmOp["Interp", CmInterp, cmInterpUsage]; CtDispatch.RegisterCmOp["Compose", CmCompose, cmComposeUsage]; CtDispatch.RegisterCmOp["Function Creation:", NIL, NIL]; CtDispatch.RegisterCmOp["Tents", CmTents, cmTentsUsage]; CtDispatch.RegisterCmOp["Square", CmSquare, cmSquareUsage]; CtDispatch.RegisterCmOp["Line", CmLine, cmLineUsage]; CtDispatch.RegisterCmOp["Sin", CmSin, cmSinUsage]; CtDispatch.RegisterCmOp["Gauss", CmGauss, cmGaussUsage]; CtDispatch.RegisterCmOp["Contours", CmContours, cmContoursUsage]; CtDispatch.RegisterCmOp["Miscellany:", NIL, NIL]; CtDispatch.RegisterCmOp["Only", CmOnly, cmOnlyUsage]; CtDispatch.RegisterCmOp["ToOthers", CmToOthers, cmToOthersUsage]; CtDispatch.RegisterCmOp["NBits", CmNBits, cmNBitsUsage]; CtDispatch.RegisterCmOp["Print", CmPrint, cmPrintUsage]; CtDispatch.RegisterCmOp["Blend", CmBlend, cmBlendUsage]; CtDispatch.RegisterCmOp["Randomizing:", NIL, NIL]; CtDispatch.RegisterCmOp["Scramble", CmScramble, cmScrambleUsage]; CtDispatch.RegisterCmOp["Random", CmRandom, cmRandomUsage]; CtDispatch.RegisterCmOp["Flash", CmFlash, cmFlashUsage]; CtDispatch.RegisterCmOp["Speckle", CmSpeckle, cmSpeckleUsage]; CtDispatch.RegisterCmOp["Cycling:", NIL, NIL]; CtDispatch.RegisterCmOp["Cycle", CmCycle, cmCycleUsage]; CtDispatch.RegisterCmOp["Shift", CmShift, cmShiftUsage]; CtDispatch.RegisterCmOp["Threshold", CmThreshold, cmThresholdUsage]; CtDispatch.RegisterCmOp["Ripple", CmRipple, cmRippleUsage]; CtDispatch.RegisterCmOp["Snake", CmSnake, cmSnakeUsage]; END.  CtMapCommandsImpl.mesa Copyright Σ 1985, 1992 by Xerox Corporation. All rights reserved. Bloomenthal, April 15, 1993 8:21 pm PDT Heckbert, June 13, 1988 11:43:17 pm PDT Glassner, November 12, 1990 3:30 pm PST Using ViewerOps from within a CtProc is ok for all CtProcs registered as type cm. Editor Terminal.WaitForBWVerticalRetrace[InterminalBackdoor.terminal]; Basic Commands Explicit Setting Scale/Add/Compose/Interpolate Function Creation Miscellany Randomizing Cycling Rippling/Snaking Blending Support Start Code Κ–"cedarcode" style•NewlineDelimiter ™™Jšœ Οeœ6™BJ™'J™'J™'—J˜JšΟk œžœ”˜žJ˜šΡblnœžœž˜ Jšžœgžœq˜α—J˜šœž˜J˜Jšœžœ ˜Jšœ žœ˜$Jšœ žœ˜'Jšœ žœ˜$Jšœžœ˜Jšžœžœžœ˜ Jšžœžœžœ˜J˜JšΟbQ™Q—headšΟl™Jšœžœžœ˜(šœžœžœ˜ J˜Jšœžœžœ˜Jšœžœ˜%Jšœžœ˜#Jš œ žœ žœžœžœ˜)Jšœ žœžœžœ žœžœžœžœ˜=Jš œžœžœžœžœ˜*Jšœ%ž˜(J˜—J˜šΟn œ˜(š’œžœ˜Jšœ˜Jšœ-˜-J˜Išžœ˜ šžœ˜J˜2Jšœ!˜!J˜—š žœžœžœžœž˜J˜2Jšœžœžœžœ ˜NJ˜:Jšžœ˜——J˜—Jšœžœ ˜"šžœž˜Jšžœ=˜AJšžœ˜—J˜J˜—š’ œžœ˜$Jšœžœ˜ Jšœžœ˜Jšœžœ˜J˜Jšœ žœžœ žœ˜7Jšœ žœžœžœ˜DJšœ žœžœ žœ˜4Jš žœžœžœ žœ!žœ˜Bš žœžœžœžœžœžœž˜-Jšœžœ˜&Jš žœžœžœ žœ*žœ˜JJšžœ˜—Jš žœžœžœ žœžœ˜BJ˜J˜—š ’œžœžœžœžœžœ˜JJšžœžœžœ˜Jšžœžœžœ'žœ˜IJ˜J˜—šΠbn œ˜*Jšœžœ ˜"Jšœ!žœ˜5šžœž˜šœ˜J˜'J˜J˜,J˜(J˜—šœ˜Jšžœ(žœžœΟc˜NJ˜ J˜ J˜%J˜J˜—šœ˜Jšžœ(žœžœ€˜NJ˜ J˜ J˜%J˜J˜—Jšžœžœ˜—JšœOžœ˜VJ˜J˜—š’ œžœ)˜;Jšœžœ˜ Jšœžœ˜šžœ ˜Jšžœ=˜AJšžœ>˜B—J˜#Jš žœžœžœ žœ:žœ˜[J˜#Jšœžœ žœžœ(˜EJ˜šžœžœžœ ž˜šžœ˜ Jšžœ1˜5Jšžœ˜—J˜:J˜FJ˜J˜ Jšžœ˜—J˜J˜—Jšœ žœ7˜Hš£œžœ ˜J˜Jšœ.˜.˜J˜——Jšœžœ₯œ₯œ˜9š£œ ˜J˜šžœžœžœžœ)˜KJšžœžœ˜$—Jšœ;˜;J˜J˜—Jšœžœ0˜Dš£ œ ˜J˜Jšœžœ;˜DJšœžœ;˜DJš žœžœžœžœžœžœ"˜CJ˜J˜——š‘™Jšœžœ˜+š’ œ˜J˜Jšœžœžœ ˜š žœžœžœžœžœ ž˜QJšžœ žœ)₯€ ˜FJ˜Jšžœ˜—Jšžœ žœ8˜JJ˜—š£œ ˜šžœ˜Jšžœ$˜(Jšžœžœjžœ˜’—J˜J˜—Jšœ žœ₯œ ₯œ˜<š£œ ˜Jš ’œžœžœžœžœžœ˜JJ˜%J˜"Jš œ žœžœ žœžœ˜9Jš œžœžœ žœ žœ˜5Jšœžœ˜J˜Jš žœžœžœ žœ'žœ˜Hšžœžœžœ ž˜Jšœ!žœžœžœ˜OJšžœ˜—Jšœ˜J˜J˜—Jšœžœ˜-š£œ ˜Jš œ žœžœžœžœ˜KJ˜J˜J˜—Jšœ žœ˜'š£œ ˜Jš œžœžœžœžœ˜"Jšžœžœžœ˜;šžœžœžœž˜J˜Jš žœžœžœžœžœ˜:Jšœžœžœžœ˜'Jšžœ˜—J˜˜J˜——Jšœžœ,˜>š£œ˜"J˜—JšœžœB˜Wš£ œ ˜J˜˜AJšœžœ˜*—šžœ ž˜Jšžœž˜ šžœ˜Jš œ žœžœžœžœ˜?Jš œžœžœ žœžœžœ˜Jšžœžœžœ ž˜Jšœžœžœ ˜Jšœ žœC˜RJšœ žœ'˜5J˜7Jšžœ˜—J˜J˜——J˜——š‘ ™ Jšœ žœ#˜4š£œ ˜Jšœžœ˜Jšžœžœžœžœ˜/šžœžœž˜J˜'J˜)J˜(Jšžœžœ.˜?—J˜J˜—Jšœžœ?˜Tš£ œ ˜Jšœžœ˜!Jšœžœ˜šžœžœž˜Jšœžœ&˜EJšœžœ&˜EJšœžœ&˜EJšžœ˜—šžœ žœ˜J˜ Jš žœžœžœ žœ1žœ˜SJ˜J˜J˜—J˜J˜—Jšœžœ4˜Fš’œ˜!J˜J˜J˜'J˜J˜—š£œ ˜šžœ˜Jšžœ0˜4Jšžœžœx˜–—J˜J˜—Jšœžœ-˜?š£œ ˜J˜šžœžœžœ žœ˜šžœ(˜*Jš žœžœ žœžœžœ˜G—Jšžœ˜—J˜——š‘ ™ Jšœžœ0˜Eš£ œ*˜4J˜—Jšœžœ*˜=š£œ˜"J˜—Jšœžœ2˜Dš£œ˜J˜ J˜HJ˜—š£œ ˜Jšœžœ,žœI˜‘J˜J˜—Jšœžœ.˜Bš£œ˜Jš ’œžœžœžœžœ˜GJ˜GJ˜?J˜—š£ œ ˜Jšœžœ,žœ;˜…J˜——š‘™Jšœžœ.˜@š’œ˜Jšœžœžœ˜(Jšœžœžœ˜Jšžœ žœ,žœ˜EJšœžœžœžœ˜KJšžœ žœ8˜JJ˜—š£œ ˜Jšœžœ,žœDžœ˜₯J˜J˜—Jšœžœ0˜Bš’œ˜!J˜JšœR˜RJ˜J˜—š£œ ˜šžœ˜Jšžœ=˜AJšžœžœ)žœ:žœ˜’—J˜J˜—Jšœžœ4˜JJš’ œM˜Xš’ œžœ žœžœ˜8J˜Jšœžœ˜Jš žœžœžœžœ%žœ˜KJš žœžœžœžœ'žœ˜OJ˜—š£ œ ˜šžœ˜Jšžœ1˜5Jšžœžœ)žœ:žœ˜©—J˜——š‘™Jšœžœ1˜DJšœžœžœžœ˜-š£œ˜Jšœžœžœžœ˜JšœžœžœQ˜cšžœžœžœž˜Jšœžœžœ˜(Jšœ žœ žœ žœ˜WJ˜8Jšžœ˜—Jšœ(˜(J˜—š£œ ˜š ’œžœžœžœžœ˜5Jšœ$žœžœ$˜dJ˜—Jšœžœ>˜VJ˜J˜—Jšœžœ*˜<š’œ˜Jšœ žœ˜J˜J˜J˜EJ˜Jšœ^˜^J˜—J˜Jš œ žœžœ žœ žœ˜>š ’œžœžœžœžœžœ˜-š’ œžœžœžœ˜3J˜J˜,Jšœžœ)žœ ˜MJ˜—Jšžœžœ˜7Jšžœžœ˜7Jšžœžœ˜7Jšœžœžœ˜"Jšœžœžœ˜"Jšœžœžœ˜"Jšžœ˜J˜—Jš£œžœ,žœ6˜‘—š‘™Jšœžœ₯œ ˜DJš œžœžœžœžœžœ˜5š£œžœžœ žœ˜7Jšœžœ˜Jšœžœ˜šžœžœ ž˜Jšžœžœ žœ˜Ašžœžœžœž˜HJšžœ žœžœ˜J˜7Jšžœ˜—J˜Jšžœ˜—Jšœžœžœ ˜,J˜—š£œ ˜Jšœžœ˜ Jšœžœžœ˜(šžœžœžœž˜$J˜šžœA˜CJšžœ ˜Jšžœžœ#žœ˜I—Jšžœ˜—šžœ žœžœ˜Jšœžœ#žœ,žœI˜΄J˜—J˜——š‘™Jšœ žœžœ˜/šœ žœžœžœ˜9J˜—š’œ₯ž₯œ₯žœ₯œ ₯œ₯œ₯œ žœ˜MJšž₯œ˜Jšœ˜Jš œžœ žœžœ žœ˜D˜Jšœ ˜ J˜šœ žœ˜J˜(J˜5—J˜Jšœ˜Jšœ˜—Jš žœžœžœžœžœ˜EJ˜J˜—š’œžœ#žœ˜@J˜Jšœ&€ ˜3Jšžœžœ žœ žœ˜.J˜ J˜J˜—š’œ˜!Jšœžœ žœ ˜%Jšžœžœ žœ €$˜FJ˜J˜—š’œžœžœ˜#Jšžœ žœ˜"Jšœ˜Jšœ;˜;J˜J˜—š£œ$žœ žœ˜OJ˜—š£ œ˜$Jšžœ žœžœ˜.Jšœ'˜'J˜J˜—Jš’œžœžœžœžœžœžœ˜F—š‘ ™ šœ'žœ!˜KJ˜J˜J˜J˜=J˜—šœ+žœžœ˜8J˜:J˜:J˜>J˜:J˜>J˜;J˜?J˜@J˜;—šœ-žœžœ˜:J˜=J˜:J˜>J˜G—šœ:žœžœ˜GJ˜=J˜DJ˜7J˜>J˜A—šœ.žœžœ˜;J˜=J˜>J˜:J˜7J˜=J˜D—šœ(žœžœ˜5J˜:J˜DJ˜=J˜=J˜=—šœ)žœžœ˜6J˜DJ˜>J˜=J˜A—šœ&žœžœ˜3J˜=J˜=J˜EJ˜>J˜=——J˜Jšžœ˜J˜J˜—…—o^ƒ