DIRECTORY Basics USING [ BITSHIFT ], Atom USING [ DottedPair, PropList, GetPropFromList ], CedarProcess USING [ CheckAbort, DoWithPriority ], Terminal USING [ ColorMode, GetColor, GetColorMode, GetBlueMap, GetGreenMap, GetRedMap, SetColor, SetBlueMap, SetGreenMap, SetRedMap, Virtual, WaitForBWVerticalRetrace ], IO USING [ PutRope, STREAM ] , Rope USING [ Cat, ROPE ] , Convert USING [ RopeFromCard ] , Real USING [ Fix, Round ], RealFns USING [ Power ], Imager USING [ Context, SetColor ], ImagerBackdoor USING [ AccessBufferRectangle ], ImagerPixel USING [ GetPixels, NewPixels, ObtainScratchPixels, PixelBuffer, PixelMap, PutPixels, ReleaseScratchPixels ], ImagerSample USING [ Fill ], ImagerColor USING [ ColorFromRGB ], ImagerColorFns USING [ RGBFromHSL ], NamedColors USING [ RopeToHSL ], ThreeDBasics USING [ ClipState, Context, ContextClass, Error, ImagerProc, ImagerProcRec, IntegerPair, NatSequence, Patch, Quad, RGB, ShadingValue, ShapeSequence, Triple, Vertex ], ThreeDViewer USING [ DrawInViewer ], SceneUtilities USING [ FlushLog ], RenderWithPixels USING [ BufferRendering ], ColorDisplayRender USING [ DitherImage, StuffBuf ], ImageTwiddle USING [ ]; ImageTwiddleImpl: CEDAR MONITOR IMPORTS Atom, Basics, CedarProcess, ColorDisplayRender, Convert, Imager, ImagerBackdoor, ImagerColor, ImagerColorFns, ImagerPixel, ImagerSample, IO, NamedColors, Real, RealFns, RenderWithPixels, Rope, SceneUtilities, Terminal, ThreeDBasics, ThreeDViewer EXPORTS ImageTwiddle ~ BEGIN Context: TYPE ~ ThreeDBasics.Context; ContextClass: TYPE ~ ThreeDBasics.ContextClass; Triple: TYPE ~ ThreeDBasics.Triple; RGB: TYPE ~ ThreeDBasics.RGB; IntegerPair: TYPE ~ ThreeDBasics.IntegerPair; IntRGB: TYPE ~ RECORD[ r, g, b: CARDINAL]; IntRGBSequence: TYPE ~RECORD [ SEQUENCE length: NAT OF IntRGB ]; NatSequence: TYPE ~ ThreeDBasics.NatSequence; Patch: TYPE ~ ThreeDBasics.Patch; ClipState: TYPE ~ ThreeDBasics.ClipState; ImagerProc: TYPE ~ ThreeDBasics.ImagerProc; ImagerProcRec: TYPE ~ ThreeDBasics.ImagerProcRec; PixelMap: TYPE ~ ImagerPixel.PixelMap; SetNamedColor: PUBLIC PROC [imagerCtx: Imager.Context, color: Rope.ROPE] ~ { clr: RGB _ ImagerColorFns.RGBFromHSL[ NamedColors.RopeToHSL[color] ]; Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ]; }; SetRGBColor: PUBLIC PROC [imagerCtx: Imager.Context, clr: RGB] ~ { Imager.SetColor[ imagerCtx, ImagerColor.ColorFromRGB[clr] ]; }; Rotate8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual, firstValue, lastValue, duration: NAT _ 0] ~ { r, g, b: ARRAY [0..256) OF [0..256); times: NAT; IF lastValue = 0 THEN lastValue _ 255; FOR i: NAT IN [firstValue .. lastValue] DO [r[i], g[i], b[i]] _ Terminal.GetColor[ vt, i]; ENDLOOP; IF duration = 0 THEN times _ 0 -- will be one complete cycle ELSE times _ duration * 75; -- seconds times fields per second times _ times + -- Fill out full cycle (lastValue - firstValue + 1) - times MOD (lastValue - firstValue + 1); FOR i: NAT IN [0..times) DO tr, tg, tb: [0..256); CedarProcess.CheckAbort[]; -- check for external abort request before proceeding vt.WaitForBWVerticalRetrace[]; -- await top of scan (to control update rate) tr _ r[firstValue]; tg _ g[firstValue]; tb _ b[firstValue]; FOR j: NAT IN (firstValue .. lastValue] DO r[j-1] _ r[j]; g[j-1] _ g[j]; b[j-1] _ b[j]; ENDLOOP; r[lastValue] _ tr; g[lastValue] _ tg; b[lastValue] _ tb; FOR i: NAT IN [firstValue .. lastValue] DO Terminal.SetColor[ vt, i, 0, r[i], g[i], b[i]]; ENDLOOP; ENDLOOP; }; Show8BitClrMap: PUBLIC PROC [context: REF Context] ~ { ThreeDViewer.DrawInViewer[ context, NEW[ImagerProcRec _ [ViewerShowClrMap, NIL] ] ]; }; ViewerShowClrMap: ImagerProc ~ { DoShowClrMap: PROC[pixelMap: PixelMap] ~ { firstBottom: INTEGER _ Real.Fix[.75 * context.viewPort.h]; size: INTEGER _ Real.Fix[ .025 * MIN[context.viewPort.h, context.viewPort.w] ]; firstTop: INTEGER _ firstBottom + size; firstLeft: INTEGER _ Real.Fix[context.viewPort.w/2.0] - 16*size; firstRight: INTEGER _ firstLeft + size; FOR i: NAT IN [0..256) DO top: NAT _ firstTop + size * (i / 32); bottom: NAT _ firstBottom + size * (i / 32); left: NAT _ firstLeft + size * (i MOD 32); right: NAT _ firstRight + size * (i MOD 32); ImagerSample.Fill[pixelMap[0], [min: [f: left, s: bottom], max: [f: right, s: top] ], i]; ENDLOOP; }; ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoShowClrMap, context.viewPort^]; }; ShowMapOnLog: PUBLIC PROC [context: REF Context, vt: Terminal.Virtual] ~ { RopeFromQuad: PROC[ i, r, g, b: CARD] RETURNS[ Rope.ROPE ] ~ { output: Rope.ROPE _ Rope.Cat[ " \t ", Convert.RopeFromCard[i], " ", Convert.RopeFromCard[r], " " ]; output _ Rope.Cat[ output, Convert.RopeFromCard[g], " ", Convert.RopeFromCard[b] ]; RETURN[ output ]; }; log: IO.STREAM _ NARROW[ Atom.GetPropFromList[context.props, $Log] ]; state: Terminal.ColorMode _ vt.GetColorMode[]; maxVal: REAL _ IF state.full THEN 255.0 ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1]; FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO -- linear ramp exponentiated jr, jg, jb: CARD; IF Terminal.GetColorMode[vt].full THEN { jr _ vt.GetRedMap[i]; jg _ vt.GetGreenMap[i]; jb _ vt.GetBlueMap[i]; } ELSE [jr, jg, jb] _ vt.GetColor[i]; IF log # NIL THEN { log.PutRope[ RopeFromQuad[ i, jr, jg, jb ] ]; IF (i/4) * 4 = i THEN log.PutRope["\n"]; SceneUtilities.FlushLog[context]; }; ENDLOOP; }; LoadMultiRamps: PUBLIC PROC [vt: Terminal.Virtual, colors: LIST OF RGB] ~ { clr: REF IntRGBSequence _ NEW[ IntRGBSequence[32] ]; numClrs, thisClr, nextClr, rampLength: NAT _ 0; state: Terminal.ColorMode _ vt.GetColorMode[]; maxVal: REAL _ IF state.full THEN 255.0 ELSE REAL[Basics.BITSHIFT[1, state.bitsPerPixelChannelA] - 1]; WHILE colors # NIL DO clr[numClrs].r _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.R]] ]; clr[numClrs].g _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.G]] ]; clr[numClrs].b _ Real.Fix[maxVal * MAX[0.0, MIN[1.0, colors.first.B]] ]; numClrs _ numClrs + 1; colors _ colors.rest; ENDLOOP; rampLength _ Real.Fix[maxVal] / (numClrs-1); FOR i: NAT IN [ 0 .. INTEGER[Real.Fix[maxVal]] ] DO jr, jg, jb: [0..256); position: NAT _ i MOD rampLength; IF position = 0 THEN IF nextClr < numClrs-1 THEN { thisClr _ nextClr; nextClr _ nextClr + 1; } ELSE thisClr _ nextClr; jr _ clr[thisClr].r * (rampLength - position) / rampLength + clr[nextClr].r * position / rampLength; jg _ clr[thisClr].g * (rampLength - position) / rampLength + clr[nextClr].g * position / rampLength; jb _ clr[thisClr].b * (rampLength - position) / rampLength + clr[nextClr].b * position / rampLength; IF Terminal.GetColorMode[vt].full THEN { vt.SetRedMap[i, jr]; vt.SetGreenMap[i, jg]; vt.SetBlueMap[i, jb]; } ELSE vt.SetColor[i, 0, jr, jg, jb]; ENDLOOP; }; ThresholdClrMap: PUBLIC PROC [vt: Terminal.Virtual, levels: NAT] ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; FOR i: NAT IN [0..256) DO -- grayscale j: NAT _ (i * levels / 256) * (256 / levels); gray: NAT _ Linearize[j]; vt.SetColor[i, 0, gray, gray, gray]; ENDLOOP; }; threshTable: REF NatSequence _ NEW[ NatSequence[256] ]; threshLevels: NAT _ 0; ThresholdImage: PUBLIC PROC [context: REF Context, levels: NAT] ~ { threshLevels _ levels; IF context.viewer # NIL -- do through viewer THEN context.class.drawInViewer[context, NEW[ImagerProcRec _ [ThresholdImage2, NIL]]] ELSE DoThresholdImage[context.pixels]; -- do directly }; ThresholdImage2: ImagerProc ~ { ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoThresholdImage, context.viewPort^]; }; DoThresholdImage: PROC [pixelMap: PixelMap] ~ { Action: PROC ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[pixelMap.box.min.f]; yStart: NAT _ Real.Round[pixelMap.box.min.s]; width: NAT _ Real.Round[pixelMap.box.max.f - pixelMap.box.min.f]; height: NAT _ Real.Round[pixelMap.box.max.s - pixelMap.box.min.s]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ pixelMap.samplesPerPixel, width ]; FOR i: NAT IN [0..256) DO j: NAT _ (i * threshLevels / 256) * (256 / threshLevels); threshTable[i] _ Linearize[j]; ENDLOOP; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: pixelMap, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO IF scanSeg.samplesPerPixel > 2 THEN { scanSeg[0][x] _ threshTable[scanSeg[0][x]]; scanSeg[1][x] _ threshTable[scanSeg[1][x]]; scanSeg[2][x] _ threshTable[scanSeg[2][x]]; } ELSE scanSeg[0][x] _ threshTable[scanSeg[0][x]]; ENDLOOP; ImagerPixel.PutPixels[ self: pixelMap, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; AdjustValueRamp: PUBLIC PROC[context: REF Context, exponent: RGB] ~ { Action: PROC[] ~ { maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[context.viewPort.x]; yStart: NAT _ Real.Round[context.viewPort.x]; width: NAT _ Real.Round[context.viewPort.w]; height: NAT _ Real.Round[context.viewPort.h]; expTableR: REF NatSequence _ NEW[ NatSequence[256] ]; expTableG: REF NatSequence _ NEW[ NatSequence[256] ]; expTableB: REF NatSequence _ NEW[ NatSequence[256] ]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ context.pixels.samplesPerPixel, width ]; SELECT context.class.displayType FROM $FullColor => FOR i: NAT IN [0..256) DO expTableR[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.R] * 255.0 ]; expTableG[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.G] * 255.0 ]; expTableB[i] _ Real.Round[ RealFns.Power[ i/255.0, exponent.B] * 255.0 ]; ENDLOOP; $Gray => { exp: REAL _ (exponent.R + exponent.G + exponent.B) / 3.0; FOR i: NAT IN [0..256) DO expTableG[i] _ Real.Round[ RealFns.Power[ i/255.0, exp] * 255.0 ]; ENDLOOP; }; ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Improper renderMode"]]; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO SELECT context.class.displayType FROM $FullColor => { scanSeg[0][x] _ expTableR[scanSeg[0][x]]; scanSeg[1][x] _ expTableG[scanSeg[1][x]]; scanSeg[2][x] _ expTableB[scanSeg[2][x]]; }; $Gray => scanSeg[0][x] _ expTableG[scanSeg[0][x]]; ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Improper renderMode"]]; ENDLOOP; ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; AdjustSaturation: PUBLIC PROC[context: REF Context, percent: REAL] ~ { Action: PROC[] ~ { maxValue: REAL _ 255.0; xStart: NAT _ Real.Round[context.viewPort.x]; yStart: NAT _ Real.Round[context.viewPort.x]; width: NAT _ Real.Round[context.viewPort.w]; height: NAT _ Real.Round[context.viewPort.h]; scanSeg: ImagerPixel.PixelBuffer _ ImagerPixel.NewPixels[ context.pixels.samplesPerPixel, width ]; FOR y: NAT IN [ yStart .. height + yStart ) DO ImagerPixel.GetPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; FOR x: NAT IN [ 0 .. width ) DO SELECT context.class.displayType FROM $FullColor => { scale: REAL; redVal: CARDINAL _ scanSeg[0][x]; grnVal: CARDINAL _ scanSeg[1][x]; bluVal: CARDINAL _ scanSeg[2][x]; min: CARDINAL _ MIN[redVal, grnVal, bluVal]; max: CARDINAL _ MAX[redVal, grnVal, bluVal]; newMin: CARDINAL _ MIN[max, INTEGER[Real.Fix[percent * min]]]; IF (min = 0) OR (max - min = 0) THEN LOOP; scale _ 1.0 * (max - newMin) / (max - min); redVal _ Real.Fix[ (redVal - min) * scale + newMin ]; grnVal _ Real.Fix[ (grnVal - min) * scale + newMin ]; bluVal _ Real.Fix[ (bluVal - min) * scale + newMin ]; scanSeg[0][x] _ redVal; scanSeg[1][x] _ grnVal; scanSeg[2][x] _ bluVal; }; ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Only for full color"]]; ENDLOOP; ImagerPixel.PutPixels[ self: context.pixels, pixels: scanSeg, initIndex: [f: xStart, s: y], count: width ]; ENDLOOP; }; CedarProcess.DoWithPriority[background, Action]; }; LoadOldStd8BitClrMap: PUBLIC PROC [vt: Terminal.Virtual] ~ { Linearize: PROC [value: REAL] RETURNS[NAT] ~ { RETURN[ Real.Round[RealFns.Power[value / 255.0, .43] * 255.0] ]; }; FOR i: NAT IN [0..40) DO -- grayscale gray: NAT _ Linearize[i*6.5]; vt.SetColor[i + 216, 0, gray, gray, gray]; ENDLOOP; FOR i: NAT IN [0..216) DO -- 6 x 6 x 6 color cube vt.SetColor[i, 0, Linearize[42.5 * (i/36 + 1)], Linearize[42.5 * ((i/6) MOD 6 + 1)], Linearize[42.5 * (i MOD 6 + 1)] ]; ENDLOOP; vt.SetColor[0, 0, 0, 0, 0]; }; SetUpTerrainColors: PUBLIC PROC[vt: Terminal.Virtual] ~ { terrainHues: ARRAY [0..4) OF RGB _ [[.05, .4, .0], [.5, .6, .1], [.4, .4, .4], [1., 1., 1.]]; skyHue: RGB _ [.5, .5, 1.0]; waterHue: RGB _ [.2, .2, .8]; start: NAT _ 8; -- start^ _ 8; length: NAT _ 240; valueStepSize, minValue, rampValue, index, hueRampSize: NAT _ 0; hueRampSize _ 238 / (LENGTH[terrainHues] + 1); valueStepSize _ 256 / hueRampSize; minValue _ 256 - valueStepSize*hueRampSize; FOR j: NAT IN [0..4) DO -- do hue steps from each hue to next currentHue: RGB _ [terrainHues[j].R, terrainHues[j].G, terrainHues[j].B]; rampValue _ minValue; FOR i: NAT IN [0..hueRampSize) DO -- build hue ramps r, g, b: NAT; r _ Real.Round[ rampValue * currentHue.R ]; g _ Real.Round[ rampValue * currentHue.G ]; b _ Real.Round[ rampValue * currentHue.B ]; vt.SetColor[index+start, 0, r, g, b ]; rampValue _ rampValue + valueStepSize; index _ index + 1; ENDLOOP; ENDLOOP; vt.SetColor[239+start, 0, Real.Fix[skyHue.R*255], Real.Fix[skyHue.G*255], Real.Fix[skyHue.B*255] ]; vt.SetColor[start, 0, Real.Fix[waterHue.R*255], Real.Fix[waterHue.G*255], Real.Fix[waterHue.B*255] ]; }; DitherImage: PUBLIC PROC[dstContext, rgbContext: REF Context] ~ { ColorDisplayRender.DitherImage[dstContext, rgbContext]; -- convenience call }; ScaleDownImage: PUBLIC PROC[dstContext, srcContext: REF Context, strtchFctr: REAL _ 1.0] ~{ Action: PROC ~ { srcHght: NAT _ Real.Round[srcContext.viewPort.h]; dstHght: NAT _ Real.Round[dstContext.viewPort.h]; srcWdth: NAT _ Real.Round[srcContext.viewPort.w]; dstWdth: NAT _ Real.Round[dstContext.viewPort.w]; numHits: REF NatSequence _ NEW[ NatSequence[dstWdth] ]; -- holds divisor for average xPos, yPos: INTEGER; xCtr, yCtr: NAT _ 0; scanSegIn, scanSegOut: ImagerPixel.PixelBuffer; scanSegIn _ ImagerPixel.ObtainScratchPixels[ srcContext.pixels.samplesPerPixel, srcWdth ]; scanSegOut _ ImagerPixel.ObtainScratchPixels[ dstContext.pixels.samplesPerPixel, dstWdth ]; FOR x: NAT IN [0..dstWdth) DO -- clear the pixels FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ 0; ENDLOOP; ENDLOOP; yPos _ 2 * dstHght - srcHght; -- offset for vertical incrementer FOR x: NAT IN [0..dstWdth) DO numHits[x] _ 0; ENDLOOP; -- clear divisor array FOR y: NAT IN [ 0 .. srcHght ) DO -- work up through source scan lines ImagerPixel.GetPixels[ self: srcContext.pixels, pixels: scanSegIn, initIndex: [f: 0, s: y], count: srcWdth ]; xPos _ 2 * dstWdth - srcWdth; xCtr _ 0; FOR x: NAT IN [ 0 .. srcWdth ) DO -- work across source scan line FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][xCtr] _ scanSegOut[i][xCtr] + scanSegIn[i][x]; -- sum intensities ENDLOOP; numHits[xCtr] _ numHits[xCtr] + 1; -- sum hits per pixel IF xPos > 0 THEN { xCtr _ xCtr + 1; xPos _ xPos - 2 * srcWdth; IF xCtr >= dstWdth THEN EXIT; }; xPos _ xPos + 2 * dstWdth; ENDLOOP; IF yPos > 0 THEN { -- big increment exceeded FOR x: NAT IN [0..dstWdth) DO FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ Real.Fix[ strtchFctr * scanSegOut[i][x] / numHits[x] ]; ENDLOOP; numHits[x] _ 0; ENDLOOP; ImagerPixel.PutPixels[ self: dstContext.pixels, pixels: scanSegOut, initIndex: [f: 0, s: yCtr], count: dstWdth ]; FOR x: NAT IN [0..dstWdth) DO -- clear the pixels FOR i: NAT IN [0..scanSegOut.samplesPerPixel) DO scanSegOut[i][x] _ 0; ENDLOOP; ENDLOOP; yPos _ yPos - 2 * srcHght; -- subtract big increment from position yCtr _ yCtr + 1; IF yCtr >= dstHght THEN EXIT; }; yPos _ yPos + 2 * dstHght; -- add in little incrment until big increment exceeded ENDLOOP; ImagerPixel.ReleaseScratchPixels[scanSegIn]; ImagerPixel.ReleaseScratchPixels[scanSegOut]; IF dstContext.viewer # NIL THEN -- make sure image gets copied to screen ThreeDViewer.DrawInViewer[ dstContext, NEW[ImagerProcRec _ [ColorDisplayRender.StuffBuf, NIL]] ]; }; IF srcContext.viewPort = NIL THEN srcContext.class.validateDisplay[srcContext]; IF dstContext.viewPort = NIL THEN dstContext.class.validateDisplay[dstContext]; IF dstContext.pixels = NIL THEN RenderWithPixels.BufferRendering[dstContext, TRUE]; CedarProcess.DoWithPriority[background, Action]; -- be nice to other processess }; END. ΌImageTwiddleImpl.mesa Last Edited by: Crow, April 4, 1989 11:21:55 am PDT Bloomenthal, September 26, 1988 12:04:42 pm PDT Types Colors Rotates color map entries in given range Print color map on log for context set color map to constant steps simulating restricted number of grey levels adjust pixel values over whole image to conform restricted number of grey levels Adjust the values in the display memory for TRC or ? (only for full-color or gray) Adjust the values in the display memory for Saturation (only for full-color) setting up colors for old terrain pictures { forest, savannah, rock, snow } AIS Files and Texture Images Bresenham-like walk across image, areas averaged down Κ˜head™J™3Icode™/defaultšΟk ˜ Jšœ œ œ˜Jšœ œ+˜9Jšœœ ˜4Jšœ œ³˜ΔJšœœ œ˜$Jšœ œœœ˜Jšœ œœ˜#Jšœ œ˜Jšœ œ ˜Jšœ œ˜&Jšœœ˜0Jšœœq˜„Jšœœ ˜Jšœœ˜%Jšœœ˜%Jšœœ˜"Jšœœƒœ1˜ΛJšœœ˜%Jšœœ˜#Jšœœ˜+Jšœœ˜3Jšœœ˜——head2šœœ˜Jšœχ˜ώJšœ ˜J˜Jšœ˜J˜—head3šΠbi™Iunitšœ œ˜%Mšœœ˜/Mšœœ˜#Jšœœœ˜Jšœ œ˜-Jšœœœ œ˜*Jš œœœœ œœ ˜@Jšœ œ˜-Jšœœ˜!Jšœ œ˜)Jšœ œ˜+Mšœœ˜1Jšœ œ˜&—šΟb™ašΟn œœœ)œ˜LLšœœ=˜EQ˜ šœœœœ˜+Q˜/Qšœ˜—Qšœ˜—Q˜Q˜— š œ œ œ ˜6Jšœ$œ$œ˜TQ˜Q˜—š œ˜ š  œœ˜*Qšœ œ&˜:Qšœœœ+˜OQšœ œ˜'Qšœ œ.˜@Qšœ œ˜( šœœœ ˜Qšœœ˜'Qšœœ"˜-Qšœœœ˜+Qšœœœ˜-Q˜YQšœ˜—Q˜—J˜QJ˜— š  œ œ œ#˜JQ™"š   œœœœœ˜>Jšœ œa˜rJ˜UJšœ ˜J˜—Jšœœœœ.˜EQ˜. šœœœ ˜Qšœ˜ Qšœœœ%˜>—š œœœœœ‘˜VMšœ œ˜šœ ˜"šœ˜M˜M˜M˜M˜—Mšœ ˜$—šœœœ˜J˜-Jšœœ˜(J˜!J˜—Jšœ˜—J˜J˜—š  œœœ œœœ˜KQšœœœ˜4Qšœ'œ˜/Q˜. šœœœ ˜Qšœ˜ Qšœœœ%˜>— šœ œ˜Qšœ#œœ˜HQšœ#œœ˜HQšœ#œœ˜HQ˜Q˜Qšœ˜—J˜,š œœœœ˜3M˜Mšœ œœ ˜!šœœœ˜,Mšœ5˜9Mšœ˜—M˜kJ˜hJ˜hšœ ˜"MšœN˜RMšœ ˜$—Qšœ˜—J˜—š œœœ œ˜DQ™Kš   œœ œœœ˜.Jšœ:˜@J˜— š œœœ œ‘˜4Jšœœ'˜-Jšœœ˜J˜%Jšœ˜—Q˜—Qšœ œœ˜7Jšœœ˜š  œœœ œœ˜CQšœ œ˜ šœœ ‘˜7Qšœ%œ#œ˜UMšœ%‘˜7—J˜—š œ˜JšœU˜UJ˜—š œœ˜/Q™Pš œœ˜š   œœ œœœ˜.Jšœ:˜@J˜—Jšœ œ ˜Qšœœ"˜-Qšœœ"˜-Qšœœ7˜AQšœœ7˜B˜9Jšœ˜J˜—šœœœ ˜Jšœœ3˜9Jšœ˜Jšœ˜— šœœœ˜.Jšœl˜l šœœœ˜šœ˜šœ˜Qšœ+˜+Qšœ+˜+Qšœ+˜+Q˜—Jšœ,˜0—Qšœ˜—Qšœm˜mQšœ˜—Q˜—J˜0Q˜Q™—š  œœœ œœ˜EJ™Rš œœ˜Jšœ œ ˜Qšœœ"˜-Qšœœ"˜-Qšœœ"˜,Qšœœ"˜-Qšœ œœ˜5Qšœ œœ˜5Qšœ œœ˜5˜9J˜%J˜— šœ˜%šœœœœ ˜(J˜IJ˜IJ˜IJšœ˜—šœ ˜ Jšœœ0˜9šœœœ ˜J˜BJšœ˜—J˜—Jšœœ8˜I— šœœœ˜.J˜r šœœœ˜šœœ˜&˜Q˜)Q˜)Q˜)Q˜—J˜4Jšœœ8˜I—Qšœ˜—Q˜sQšœ˜—Q˜—J˜0J˜J˜—š  œœœ œœ˜FJ™Lš œœ˜Jšœ œ ˜Qšœœ"˜-Qšœœ"˜-Qšœœ"˜,Qšœœ"˜-˜9J˜%J˜— šœœœ˜.J˜r šœœœ˜šœœ˜&˜Qšœœ˜ Qšœœ˜!Qšœœ˜!Qšœœ˜!Qšœœœ˜,Qšœœœ˜,Qšœœœœ˜>Qš œœœœœ˜*Q˜+Q˜7Q˜6Q˜6Q˜Q˜Q˜Q˜—Jšœœ8˜I—Qšœ˜—Q˜sQšœ˜—Q˜—J˜0J˜J˜— š œ œ˜<š   œœ œœœ˜.Jšœ:˜@J˜— š œœœ œ‘˜3Jšœœ˜J˜+Jšœ˜—š œœœ œ ‘˜;˜J˜Jšœœ ˜%Jšœœ˜J˜—Jšœ˜—Q˜Q˜Q™—š œ œ˜9Jš‘*™*šœ œœœ=˜]J™ —Jšœœ˜Jšœ œ˜Jšœœ ‘˜#Qšœœ˜Qšœ8œ˜@Qšœœ˜.Q˜"Q˜+ š œœœœ‘%˜AJšœ œ;˜JJ˜š œœœœ‘˜7Jšœ œ˜ J˜/J˜+J˜+J˜(J˜&J˜Jšœ˜—Jšœ˜—J˜iJ˜j ˜J™———šŸ™š  œ œœ ˜AJšœ:‘˜MJšœ ˜—š  œœœœœ ˜[J™5š œœ˜Jšœ œ%˜1Jšœ œ%˜1Jšœ œ%˜1Jšœ œ%˜1Jšœ œœ‘˜TJšœ œ˜Jšœ œ˜Q˜0Q˜ZQ˜\ š œœœœ ‘˜:Qš œœœ!œ œ˜QQšœ˜—Jšœ$‘"˜FJš œœœœœ‘˜Qš œœœœ‘$˜JJ˜tJ˜J˜ š œœœœ‘˜E šœœœ!˜0Qšœ=‘˜OQšœ˜—Jšœ*‘˜?šœ œ˜Jšœ1œœœ˜NJ˜—J˜Jšœ˜— šœ œ‘˜7 šœœœœ˜ šœœœ!˜0Q˜JQšœ˜—Q˜Qšœ˜—J˜x š œœœœ ‘˜:Qš œœœ!œ œ˜QQšœ˜—Qšœ‘'˜FQšœœœœ˜1Q˜—Jšœ‘6˜RJšœ˜—J˜,J˜-šœœœ‘(˜K˜J˜ Jšœ/œ˜7J˜——J˜—Jšœœœ.˜OJšœœœ.˜OJšœœœ.œ˜SJšœ4‘˜RJ˜——Jš˜—…—BήV