DIRECTORY ImagerBasic USING [Pair, IntPair, Rectangle, IntRectangle, Transformation, TransformType], ImagerTransform, Real USING [FixI, RoundLI, Float], RealFns USING [SinDeg, CosDeg]; ImagerTransformImpl: CEDAR PROGRAM IMPORTS Real, RealFns EXPORTS ImagerTransform = BEGIN OPEN ImagerBasic; epsilonHardness: REAL _ .00001; -- How far from non-hard you can get and still be easy TransformTypeNone: PUBLIC SIGNAL = CODE; Translate: PUBLIC PROC [dx, dy: REAL] RETURNS [Transformation] = { RETURN [[1, 0, dx, 0, 1, dy, identity]] }; PreTranslate: PUBLIC PROC [dx, dy: REAL, m: Transformation] RETURNS [Transformation] = { m.c _ dx*m.a + dy*m.b + m.c; m.f _ dx*m.d + dy*m.e + m.f; RETURN [m] }; PreIntTranslate: PUBLIC PROC [dx, dy: INTEGER, m: Transformation] RETURNS [Transformation] = { m.c _ dx*m.a + dy*m.b + m.c; m.f _ dx*m.d + dy*m.e + m.f; RETURN [m] }; Rotate: PUBLIC PROC [degrees: REAL] RETURNS [n: Transformation] = { WHILE degrees >= 360. DO degrees _ degrees - 360.; ENDLOOP; WHILE degrees < 0. DO degrees _ degrees + 360.; ENDLOOP; SELECT degrees FROM 0. => n _ [ 1., 0., 0., 0., 1., 0., identity ]; 90. => n _ [ 0.,-1., 0., 1., 0., 0., rot90 ]; 180. => n _ [-1., 0., 0., 0.,-1., 0., rot180 ]; 270. => n _ [ 0., 1., 0.,-1., 0., 0., rot270 ]; ENDCASE => { n.a _ RealFns.CosDeg[degrees]; n.d _ RealFns.SinDeg[degrees]; n.b _ -n.d; n.e _ n.a; n.c _ 0.; n.f _ 0.; n.type _ hard; }; }; PreRotate: PUBLIC PROC [degrees: REAL, m: Transformation] RETURNS [Transformation] = { RETURN [Concat[Rotate[degrees], m]]; }; Scale: PUBLIC PROC [sx, sy: REAL] RETURNS [Transformation] = { transformType: TransformType _ SELECT TRUE FROM ABS[sx] # 1 OR ABS[sy] # 1 => hard, (sx = 1) AND (sy = 1) => identity, (sx =-1) AND (sy = 1) => mirrorX, (sx = 1) AND (sy =-1) => mirrorY, (sx =-1) AND (sy =-1) => rot180, ENDCASE => hard; RETURN [[ sx, 0., 0., 0., sy, 0., transformType ]]; }; PreScale: PUBLIC PROC [sx, sy: REAL, m: Transformation] RETURNS [Transformation] = { RETURN [Concat[Scale[sx, sy], m]]; }; Transform: PUBLIC PROC [p: Pair, transform: Transformation] RETURNS [Pair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x + c, y: p.y + f] ]; rot90 => RETURN[ [ x: -p.y + c, y: p.x + f] ]; rot180 => RETURN[ [ x: -p.x + c, y: -p.y + f] ]; rot270 => RETURN[ [ x: p.y + c, y: -p.x + f] ]; mirrorX => RETURN[ [ x: -p.x + c, y: p.y + f] ]; mirrorY => RETURN[ [ x: p.x + c, y: -p.y + f] ]; mirror45Deg => RETURN[ [ x: p.y + c, y: p.x + f] ]; mirror135Deg => RETURN[ [ x: -p.y + c, y: -p.x + f] ]; hard => RETURN[ [ x: p.x * a + p.y * b + c, y: p.x * d + p.y * e + f ] ]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; InverseTransform: PUBLIC PROC [p: Pair, transform: Transformation] RETURNS [Pair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x - c, y: p.y - f] ]; rot90 => RETURN[ [ x: p.y - f, y: -p.x + c] ]; rot180 => RETURN[ [ x: -p.x + c, y: -p.y + f] ]; rot270 => RETURN[ [ x: -p.y + f, y: p.x - c] ]; mirrorX => RETURN[ [ x: -p.x + c, y: p.y - f] ]; mirrorY => RETURN[ [ x: p.x - c, y: -p.y + f] ]; mirror45Deg => RETURN[ [ x: p.y - f, y: p.x - c] ]; mirror135Deg => RETURN[ [ x: -p.y + f, y: -p.x + c] ]; hard => RETURN[Transform[p, Invert[transform]]]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; TransformVec: PUBLIC PROC [p: Pair, transform: Transformation] RETURNS [Pair] = { OPEN transform; SELECT type FROM identity => RETURN[p]; rot90 => RETURN[ [ x: -p.y, y: p.x] ]; rot180 => RETURN[ [ x: -p.x, y: -p.y] ]; rot270 => RETURN[ [ x: p.y, y: -p.x] ]; mirrorX => RETURN[ [ x: -p.x, y: p.y] ]; mirrorY => RETURN[ [ x: p.x, y: -p.y] ]; mirror45Deg => RETURN[ [ x: p.y, y: p.x] ]; mirror135Deg => RETURN[ [ x: -p.y, y: -p.x] ]; hard => RETURN[ [ x: p.x * a + p.y * b, y: p.x * d + p.y * e ] ]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; InverseTransformVec: PUBLIC PROC [p: Pair, transform: Transformation] RETURNS [Pair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x, y: p.y] ]; rot90 => RETURN[ [ x: p.y, y: -p.x] ]; rot180 => RETURN[ [ x: -p.x, y: -p.y] ]; rot270 => RETURN[ [ x: -p.y, y: p.x] ]; mirrorX => RETURN[ [ x: -p.x, y: p.y] ]; mirrorY => RETURN[ [ x: p.x, y: -p.y] ]; mirror45Deg => RETURN[ [ x: p.y, y: p.x] ]; mirror135Deg => RETURN[ [ x: -p.y, y: -p.x] ]; hard => RETURN[TransformVec[p, Invert[transform]]]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; IntTransform: PUBLIC PROC [p: IntPair, transform: Transformation] RETURNS [IntPair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x + Real.FixI[c], y: p.y + Real.FixI[f] ] ]; rot90 => RETURN[ [ x: -p.y + Real.FixI[c], y: p.x + Real.FixI[f] ] ]; rot180 => RETURN[ [ x: -p.x + Real.FixI[c], y: -p.y + Real.FixI[f] ] ]; rot270 => RETURN[ [ x: p.y + Real.FixI[c], y: -p.x + Real.FixI[f] ] ]; mirrorX => RETURN[ [ x: -p.x + Real.FixI[c], y: p.y + Real.FixI[f] ] ]; mirrorY => RETURN[ [ x: p.x + Real.FixI[c], y: -p.y + Real.FixI[f] ] ]; mirror45Deg => RETURN[ [ x: p.y + Real.FixI[c], y: p.x + Real.FixI[f] ] ]; mirror135Deg => RETURN[ [ x: -p.y + Real.FixI[c], y: -p.x + Real.FixI[f] ] ]; hard => RETURN[ [ x: Real.FixI[p.x * a + p.y * b + c], y: Real.FixI[p.x * d + p.y * e + f] ] ]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; InverseIntTransform: PUBLIC PROC [p: IntPair, transform: Transformation] RETURNS [IntPair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x - Real.FixI[c], y: p.y - Real.FixI[f] ] ]; rot90 => RETURN[ [ x: p.y - Real.FixI[f], y: -p.x + Real.FixI[c] ] ]; rot180 => RETURN[ [ x: -p.x + Real.FixI[c], y: -p.y + Real.FixI[f] ] ]; rot270 => RETURN[ [ x: -p.y + Real.FixI[f], y: p.x - Real.FixI[c] ] ]; mirrorX => RETURN[ [ x: -p.x + Real.FixI[c], y: p.y - Real.FixI[f] ] ]; mirrorY => RETURN[ [ x: p.x - Real.FixI[c], y: -p.y + Real.FixI[f] ] ]; mirror45Deg => RETURN[ [ x: p.y - Real.FixI[f], y: p.x - Real.FixI[c] ] ]; mirror135Deg => RETURN[ [ x: -p.y + Real.FixI[f], y: -p.x + Real.FixI[c] ] ]; hard => RETURN[IntTransform[p, Invert[transform]]]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; TransformIntVec: PUBLIC PROC [p: IntPair, transform: Transformation] RETURNS [IntPair] = { OPEN transform; SELECT type FROM identity => RETURN[p]; rot90 => RETURN[ [ x: -p.y, y: p.x] ]; rot180 => RETURN[ [ x: -p.x, y: -p.y] ]; rot270 => RETURN[ [ x: p.y, y: -p.x] ]; mirrorX => RETURN[ [ x: -p.x, y: p.y] ]; mirrorY => RETURN[ [ x: p.x, y: -p.y] ]; mirror45Deg => RETURN[ [ x: p.y, y: p.x] ]; mirror135Deg => RETURN[ [ x: -p.y, y: -p.x] ]; hard => { RETURN[ [ x: Real.FixI[p.x * a + p.y * b], y: Real.FixI[p.x * d + p.y * e] ] ]; }; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; InverseTransformIntVec: PUBLIC PROC [p: IntPair, transform: Transformation] RETURNS [IntPair] = { OPEN transform; SELECT type FROM identity => RETURN[ [ x: p.x, y: p.y] ]; rot90 => RETURN[ [ x: p.y, y: -p.x] ]; rot180 => RETURN[ [ x: -p.x, y: -p.y] ]; rot270 => RETURN[ [ x: -p.y, y: p.x] ]; mirrorX => RETURN[ [ x: -p.x, y: p.y] ]; mirrorY => RETURN[ [ x: p.x, y: -p.y] ]; mirror45Deg => RETURN[ [ x: p.y, y: p.x] ]; mirror135Deg => RETURN[ [ x: -p.y, y: -p.x] ]; hard => RETURN[TransformIntVec[p, Invert[transform]]]; ENDCASE => { SIGNAL TransformTypeNone[]; RETURN[ p ]; }; }; TransformRectangle: PUBLIC PROC [rect: Rectangle, transform: Transformation] RETURNS [Rectangle] = { IF transform.b = 0. AND transform.d = 0. THEN { -- No rotation or skew [[rect.x, rect.y]] _ Transform[[rect.x, rect.y], transform ]; [[rect.w, rect.h]] _ TransformVec[[rect.w, rect.h], transform ]; IF rect.w < 0 THEN { rect.x _ rect.x + rect.w; rect.w _ ABS[rect.w]; }; IF rect.h < 0 THEN { rect.y _ rect.y + rect.h; rect.h _ ABS[rect.h]; }; } ELSE { p1, p2, p3, p4: Pair; -- Hard case, find resulting bounding box p1 _ Transform[[rect.x, rect.y], transform ]; p2 _ Transform[[rect.x + rect.w, rect.y], transform ]; p3 _ Transform[[rect.x + rect.w, rect.y + rect.h], transform ]; p4 _ Transform[[rect.x, rect.y + rect.h], transform ]; rect.x _ MIN[p1.x, p2.x, p3.x, p4.x]; rect.y _ MIN[p1.y, p2.y, p3.y, p4.y]; rect.w _ MAX[p1.x, p2.x, p3.x, p4.x] - rect.x; rect.h _ MAX[p1.y, p2.y, p3.y, p4.y] - rect.y; }; RETURN [ rect ]; }; TransformIntRectangle: PUBLIC PROC[rect: IntRectangle, transform: Transformation] RETURNS [IntRectangle] = { IF transform.b = 0. AND transform.d = 0. THEN { -- No rotation or skew [[rect.x, rect.y]] _ IntTransform[[rect.x, rect.y], transform ]; [[rect.w, rect.h]] _ TransformIntVec[[rect.w, rect.h], transform ]; IF rect.w < 0 THEN { rect.x _ rect.x + rect.w; rect.w _ ABS[rect.w]; }; IF rect.h < 0 THEN { rect.y _ rect.y + rect.h; rect.h _ ABS[rect.h]; }; } ELSE { p1, p2, p3, p4: Pair; t: REAL; p1 _ Transform[[Real.Float[rect.x], Real.Float[rect.y]], transform ]; p2 _ Transform[[Real.Float[rect.x + rect.w], Real.Float[rect.y]], transform ]; p3 _ Transform[[Real.Float[rect.x + rect.w], Real.Float[rect.y + rect.h]], transform ]; p4 _ Transform[[Real.Float[rect.x], Real.Float[rect.y + rect.h]], transform ]; t _ MAX[MIN[p1.x, p2.x, p3.x, p4.x, LAST[INTEGER]-1], FIRST[INTEGER]+1]; rect.x _ Real.RoundLI[t]; IF rect.x > t THEN rect.x _ rect.x - 1; -- Floor[t] t _ MAX[MIN[p1.y, p2.y, p3.y, p4.y, LAST[INTEGER]-1], FIRST[INTEGER]+1]; rect.y _ Real.RoundLI[t]; IF rect.y > t THEN rect.y _ rect.y - 1; t _ MAX[MIN[MAX[p1.x, p2.x, p3.x, p4.x] - rect.x, LAST[INTEGER]-1], 0]; rect.w _ Real.RoundLI[t]; IF rect.w < t THEN rect.w _ rect.w + 1; -- Ceiling[t] t _ MAX[MIN[MAX[p1.y, p2.y, p3.y, p4.y] - rect.y, LAST[INTEGER]-1], 0]; rect.h _ Real.RoundLI[t]; IF rect.h < t THEN rect.h _ rect.h + 1; }; RETURN [ rect ]; }; Concat: PUBLIC PROC [pre, post: Transformation] RETURNS [prod: Transformation] = { IF post.type = identity THEN { prod _ pre; prod.c _ pre.c + post.c; prod.f _ pre.f + post.f; } ELSE IF pre.type = identity THEN { prod _ post; prod.c _ pre.c*post.a + pre.f*post.b + post.c; prod.f _ pre.c*post.d + pre.f*post.e + post.f; } ELSE IF post.type = mirrorY THEN { t: Pair; t.x _ pre.c + post.c; t.y _ -pre.f + post.f; SELECT pre.type FROM rot90 => prod _ [ 0., -1., t.x, -1., 0., t.y, mirror135Deg ]; rot180 => prod _ [-1., 0., t.x, 0., 1., t.y, mirrorX ]; rot270 => prod _ [ 0., 1., t.x, 1., 0., t.y, mirror45Deg ]; mirrorX => prod _ [-1., 0., t.x, 0., -1., t.y, rot180 ]; mirrorY => prod _ [ 1., 0., t.x, 0., 1., t.y, identity ]; mirror45Deg => prod _ [ 0., 1., t.x, -1., 0., t.y, rot270 ]; mirror135Deg => prod _ [ 0., -1., t.x, 1., 0., t.y, rot90 ]; hard => { prod _ [ a: pre.a, d: -pre.d, b: pre.b, e: -pre.e, c: t.x, f: t.y, type: pre.type ] }; ENDCASE => { SIGNAL TransformTypeNone[]; prod _ [ 1., 0., t.x, 0., -1., t.y, mirrorY ]; } } ELSE { -- not a trivial case prod.a _ pre.a*post.a + pre.d*post.b; prod.d _ pre.a*post.d + pre.d*post.e; prod.b _ pre.b*post.a + pre.e*post.b; prod.e _ pre.b*post.d + pre.e*post.e; prod.c _ pre.c*post.a + pre.f*post.b + post.c; prod.f _ pre.c*post.d + pre.f*post.e + post.f; prod.type _ hard; IF (ABS[prod.a] < epsilonHardness -- test for rotation or axis interchange ops AND ABS[prod.e] < epsilonHardness AND (ABS[ABS[prod.d] - 1.0] < epsilonHardness) AND (ABS[ABS[prod.b] - 1.0] < epsilonHardness) ) THEN { IF prod.d > 0 THEN IF prod.b > 0 THEN prod _ [0., 1., prod.c, 1., 0., prod.f, mirror45Deg ] ELSE prod _ [0., -1., prod.c, 1., 0., prod.f, rot90 ] ELSE IF prod.b > 0 THEN prod _ [0., 1., prod.c, -1., 0., prod.f, rot270 ] ELSE prod _ [0., -1., prod.c, -1., 0., prod.f, mirror135Deg ] } ELSE IF (ABS[prod.d] < epsilonHardness -- test for null or mirror ops AND ABS[prod.b] < epsilonHardness AND (ABS[ABS[prod.a] - 1.0] < epsilonHardness) AND (ABS[ABS[prod.e] - 1.0] < epsilonHardness) ) THEN { IF prod.a > 0 THEN IF prod.e > 0 THEN prod _ [ 1., 0., prod.c, 0., 1., prod.f, identity ] ELSE prod _ [ 1., 0., prod.c, 0., -1., prod.f, mirrorY ] ELSE IF prod.e > 0 THEN prod _ [-1., 0., prod.c, 0., 1., prod.f, mirrorX ] ELSE prod _ [-1., 0., prod.c, 0., -1., prod.f, rot180 ] }; -- do nothing if can't make it non-hard }; }; Invert: PUBLIC PROC [m: Transformation] RETURNS [tfm: Transformation] = { det: REAL _ m.a*m.e - m.d*m.b; -- compute determinant tfm _ [ a: m.e / det, d: -m.d / det, b: -m.b / det, e: m.a / det, c: (m.b * m.f - m.e * m.c) / det, f: (m.d * m.c - m.a * m.f) / det, type: hard ]; }; Create: PUBLIC PROC [a, b, c, d, e, f: REAL] RETURNS [tfm: Transformation] = { tfm _ [a, b, c, d, e, f, hard]; }; END. ŽImagerTransformImpl.mesa Created March 11, 1983 Last Edit By: Plass, August 12, 1983 1:46 pm Crow, July 31, 1983 3:31 pm Public Signals Public Procedures This always returns a rectangle, thus "hard" transforms will cause a bounding box to be returned Hard case, find resulting integer bounding box enclosing real bounding box Interpretation of easy transforms identity => [TransformationRep _ [ 1., 0., x, 0., 1., y] ]; rot90 => [TransformationRep _ [ 0., -1., x, 1., 0., y] ]; rot180 => [TransformationRep _ [-1., 0., x, 0., -1., y] ]; rot270 => [TransformationRep _ [ 0., 1., x, -1., 0., y] ]; mirrorX => [TransformationRep _ [-1., 0., x, 0., 1., y] ]; mirrorY => [TransformationRep _ [ 1., 0., x, 0., -1., y] ]; mirror45Deg => [TransformationRep _ [ 0., 1., x, 1., 0., y] ]; mirror135Deg => [TransformationRep _ [ 0., -1., x, -1., 0., y] ]; catch trivial cases Make non-hard if a, b, d, e all within epsilonHardness of values for one of the non-hard cases Michael Plass, August 12, 1983 1:46 pm: Changed Real.RoundI to Real.RoundLI Michael Plass, August 12, 1983 2:42 pm: Added clip to INTEGER bounds in TransformIntRectangle. Κ T˜head™J™™ J™J™——J˜šΟk ˜ Jšœ œI˜ZJšœ˜Jšœœ˜$Jšœ œ˜!—šœ ˜"Jšœ˜Jšœ˜Iunitšœœœ˜Jšœœ Οc6˜V—šΟb™Jšœœœœ˜(—šŸ™š Οn œœœ œœ˜BJšœ!˜'Jšœ˜—š   œœœ œœ˜XJšœ<˜—Jšœ˜—š œ œ$ œ ˜UJšœ ˜šœ˜Jšœ œ ˜3Jšœ œ ˜1Jšœ œ ˜2Jšœ œ ˜2Jšœ œ ˜2Jšœ œ ˜2Jšœœ ˜5Jšœœ ˜6Jšœ œ"˜2Jšœ œœ ˜?—Jšœ˜—š  œ œ$ œ ˜QJšœ ˜šœ˜Jšœ œ˜Jšœ œ˜)Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœœ˜-Jšœœ˜.Jšœ œœ.˜EJšœœœ ˜>—Jšœ˜—š œ œ$ œ ˜XJšœ ˜šœ˜Jšœ œ˜+Jšœ œ˜)Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœœ˜-Jšœœ˜.Jšœ œ%˜5Jšœœœ ˜>—Jšœ˜—š  œ œ& œ ˜WJšœ ˜šœ˜Jšœ œ7˜JJšœ œ7˜HJšœ œ7˜IJšœ œ7˜IJšœ œ7˜IJšœ œ7˜IJšœœ7˜LJšœœ7˜MJšœ œœ$˜;Jšœ3˜3Jšœœœ ˜>—Jšœ˜—š œ œ& œ ˜^Jšœ ˜šœ˜Jšœ œ8˜KJšœ œ8˜IJšœ œ8˜JJšœ œ8˜JJšœ œ8˜JJšœ œ8˜JJšœœ8˜MJšœœ8˜NJšœ œ%˜5Jšœœœ ˜>—Jšœ˜—š œ œ' œ ˜ZJšœ ˜šœ˜Jšœ œ˜Jšœ œ˜)Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœœ˜-Jšœœ˜.šœ ˜ JšœœD˜QJ˜—Jšœœœ ˜>—Jšœ˜—š œ œ' œ ˜aJšœ ˜šœ˜Jšœ œ˜+Jšœ œ˜)Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœ œ˜*Jšœœ˜-Jšœœ˜.Jšœ œ(˜8Jšœœœ ˜>—Jšœ˜LšΟi`™`—Jš œ œ.˜Mšœœ˜*Jšœœ˜)šœž˜-Jšœ>˜>Jšœ@˜@Jšœ œ*œ ˜LJšœ œ*œ ˜LJ˜—šœž)˜KJšœ-˜-Jšœ6˜6Jšœ?˜?Jšœ6˜6Jšœ œ˜%Jšœ œ˜%Jšœ œ"˜.Jšœ œ#˜/J˜—Jšœ ˜Jšœ˜—Jš œ œ0˜Ršœœ˜-Jšœœ˜)šœž˜-JšœA˜AJšœC˜CJšœ œ*œ ˜LJšœ œ*œ ˜LJšœ˜JšžJ™J—šœœ˜(JšœE˜EJšœN˜NJšœW˜WJšœN˜NLš œœœœœœœ˜JJšœœ œž ˜PLš œœœœœœœ˜HJšœœ œ˜BLš œœœœ#œœ ˜GIašœœ œž ˜RLš œœœœ#œœ ˜GMšœœ œ˜CJ˜—Jšœ ˜Jšœ˜—head3šŸ!™!Jšœ?™?Jšœ=™=Jšœ>™>Jšœ>™>Jšœ>™>Jšœ>™>JšœA™AJšœB™B—š œœœœ˜RJšœ™šœœ˜"Jšœ ˜ Jšœ3˜3Jšœ˜—šœœœ˜"Jšœ ˜ Jšœ`˜`J˜—šœœœ˜#Jšœ˜Jšœ/˜/šœ ˜Jšœ@˜@Jšœ@˜@JšœB˜BJšœ=˜=Jšœ>˜>Jšœ@˜@Jšœ?˜?Jšœ)˜)Jšœ˜Jšœ0˜0šœœ˜-Jšœ7˜7—J˜——šœ ž˜&JšœM˜MJšœM˜MJšœ]˜]Jšœ˜Jšž^œ™`šœœœž,˜OJšœœ˜"Jšœœœ"˜/Jšœœœ$˜0—šœ˜Jšœ ˜šœ˜Jšœ ˜Jšœ8˜