DIRECTORY
ImagerBasic USING [Pair, IntPair, Rectangle, IntRectangle, Transformation, TransformType],
ImagerTransform,
Real   USING [FixI, RoundLI, Float],
RealFns  USING [SinDeg, CosDeg];
 
Public Procedures
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 ];  };
 
};
This always returns a rectangle, thus "hard" transforms will cause a bounding box to be returned
 
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];  };
}
Hard case, find resulting integer bounding box enclosing real bounding box
 
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 ];
};
 
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] ];
 
Concat: 
PUBLIC 
PROC [pre, post: Transformation] 
RETURNS [prod: Transformation] = {
catch trivial cases
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;
Make non-hard if a, b, d, e all within epsilonHardness of values for one of the non-hard cases  
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];
};