MODULE SVGGradients;
IMPORT SVG, SVGColors, XMLObjects, Raster, Gfx, Math;
CONST
SpreadMethodPad=0;
SpreadMethodReflect=1;
SpreadMethodRepeat=2;
TYPE
GradientStop=POINTER TO GradientStopDesc;
GradientStopDesc=RECORD
offset: SVG.Length;
color: Raster.Pixel;
next: GradientStop;
END;
Gradient*=OBJECT
VAR first, last: GradientStop;
gradientUnits*: SHORTINT;
spreadMethod*: SHORTINT;
transform*: SVG.Transform;
PROCEDURE &New*;
BEGIN
NEW(transform);
transform.SetIdentity();
END New;
PROCEDURE Copy*(other: Gradient);
VAR current: GradientStop;
BEGIN
first := NIL;
last := NIL;
current := other.first;
WHILE current # NIL DO
AddStop2(current.offset, current.color);
current := current.next;
END;
gradientUnits := other.gradientUnits;
spreadMethod := other.spreadMethod;
transform := other.transform;
END Copy;
PROCEDURE HasStops(): BOOLEAN;
BEGIN
RETURN (first#NIL);
END HasStops;
PROCEDURE ClearStops*;
BEGIN
first := NIL;
last := NIL;
END ClearStops;
PROCEDURE AddStop*(offset: SVG.Length; color: SVG.Color);
VAR r,g,b,a: INTEGER;
p: Raster.Pixel;
BEGIN
SVGColors.Split(color, r, g, b, a);
p[0] := CHR(b);
p[1] := CHR(g);
p[2] := CHR(r);
p[3] := CHR(a);
AddStop2(offset, p);
END AddStop;
PROCEDURE AddStop2(offset: SVG.Length; color: Raster.Pixel);
BEGIN
IF last=NIL THEN
NEW(last);
first := last;
ELSE
NEW(last.next);
last := last.next;
END;
IF offset<0 THEN offset := 0 END;
IF offset>1 THEN offset := 1 END;
last.offset := offset;
last.color := color;
END AddStop2;
PROCEDURE GetFromOffset(offset: SVG.Length): Raster.Pixel;
VAR prev, current: GradientStop;
a: SVG.Length;
BEGIN
ASSERT(HasStops());
CASE spreadMethod OF
SpreadMethodPad:
| SpreadMethodReflect:
offset := 2*(offset/2 - ENTIER(offset/2));
IF offset>1.0 THEN offset := 2.0-offset END;
| SpreadMethodRepeat:
offset := offset - ENTIER(offset);
END;
prev := first;
current := first;
WHILE current # NIL DO
IF current.offset >= offset THEN
IF current.offset = prev.offset THEN RETURN current.color
ELSE
a := (offset-prev.offset)/(current.offset-prev.offset);
RETURN SVGColors.BlendAndPremultiply(prev.color, current.color, a)
END
END;
prev := current;
current := current.next;
END;
RETURN prev.color;
END GetFromOffset;
PROCEDURE GetFromPoint*(p: SVG.Coordinate): Raster.Pixel;
BEGIN HALT(99);
END GetFromPoint;
END Gradient;
LinearGradient*=OBJECT(Gradient)
VAR p1*, p2*: SVG.Coordinate;
PROCEDURE CopyLinear*(other: LinearGradient);
BEGIN
Copy(other);
p1 := other.p1;
p2 := other.p2;
END CopyLinear;
PROCEDURE GetFromPoint*(p: SVG.Coordinate): Raster.Pixel;
VAR offset, len: SVG.Length;
u, v, n: SVG.Coordinate;
BEGIN
u.x :=p2.x-p1.x;
u.y :=p2.y-p1.y;
v.x :=p.x-p1.x;
v.y :=p.y-p1.y;
len := Math.sqrt(SHORT(u.x*u.x+u.y*u.y));
n.x :=u.x / len;
n.y :=u.y / len;
offset := (n.x*v.x+n.y*v.y)/len;
RETURN GetFromOffset(offset);
END GetFromPoint;
END LinearGradient;
RadialGradient*=OBJECT(Gradient)
VAR center*, focal*: SVG.Coordinate;
radius*: SVG.Length;
PROCEDURE CopyRadial*(other: RadialGradient);
BEGIN
Copy(other);
center := other.center;
focal := other.focal;
radius := other.radius;
END CopyRadial;
PROCEDURE GetFromPoint*(p: SVG.Coordinate): Raster.Pixel;
VAR offset, ul2, discr, t1, t2: SVG.Length;
u, v, u2 : SVG.Coordinate;
BEGIN
u.x :=p.x-focal.x;
u.y :=p.y-focal.y;
v.x :=center.x-focal.x;
v.y :=center.y-focal.y;
u2.x:=u.x*u.x;
u2.y:=u.y*u.y;
ul2:= u2.x+u2.y;
discr := 2*v.x*v.y*u.x*u.y+ul2*radius*radius-u2.y*v.x*v.x-u2.x*v.y*v.y;
IF ul2=0 THEN
offset := 0.0;
ELSIF discr <= 0 THEN
offset := 1.0;
ELSE
t1 := (v.x*u.x+v.y*u.y+Math.sqrt(SHORT(discr)))/ul2;
t2 := (v.x*u.x+v.y*u.y-Math.sqrt(SHORT(discr)))/ul2;
IF (t2 < t1) & (t2 >= 0) THEN
t1 := t2;
END;
IF t1=0 THEN offset := 0
ELSE offset := 1/t1
END
END;
RETURN GetFromOffset(offset);
END GetFromPoint;
END RadialGradient;
GradientDict*=OBJECT
VAR gradients: XMLObjects.ArrayDict;
PROCEDURE &New*;
BEGIN
NEW(gradients)
END New;
PROCEDURE AddGradient*(gradient: Gradient; id: SVG.String);
BEGIN
gradients.Add(id^, gradient)
END AddGradient;
PROCEDURE GetGradient*(id: SVG.String):Gradient;
VAR p: ANY;
BEGIN
p := gradients.Get(id^);
IF p = NIL THEN RETURN NIL
ELSE RETURN p(Gradient) END
END GetGradient;
PROCEDURE GetGradientAsPattern*(ctx: Gfx.Context; id: SVG.String;
worldBBox, objectBBox: SVG.Box; userToWorldSpace: SVG.Transform; viewport: SVG.Box): Gfx.Pattern;
VAR
gradient: Gradient;
img: Raster.Image;
mode: Raster.Mode;
x, y, minx, miny, maxx, maxy, imgWidth, imgHeight: LONGINT;
p, patternStart: SVG.Coordinate;
transform: SVG.Transform;
BEGIN
gradient := GetGradient(id);
IF gradient = NIL THEN
SVG.Error("Couldn't find gradient with specified id");
SVG.Error(id^);
RETURN NIL
ELSIF ~gradient.HasStops() THEN
RETURN NIL
ELSE
NEW(transform);
transform.SetIdentity();
transform := transform.Translate(-worldBBox.x, -worldBBox.y);
transform := transform.Multiply(userToWorldSpace);
CASE gradient.gradientUnits OF
SVG.UnitsUserSpaceOnUse:
| SVG.UnitsObjectBoundingBox:
transform:= transform.Translate(objectBBox.x, objectBBox.y);
transform:= transform.Scale(objectBBox.width, objectBBox.height);
END;
transform := transform.Multiply(gradient.transform);
transform := transform.Invert();
patternStart.x := worldBBox.x;
patternStart.y := worldBBox.y;
minx := 0;
miny := 0;
maxx := ENTIER(worldBBox.width)+1;
maxy := ENTIER(worldBBox.height)+1;
IF minx<ENTIER(viewport.x-worldBBox.x) THEN minx := ENTIER(viewport.x-worldBBox.x); patternStart.x := viewport.x END;
IF miny<ENTIER(viewport.y-worldBBox.y) THEN miny := ENTIER(viewport.y-worldBBox.y); patternStart.y := viewport.y END;
IF maxx>ENTIER(viewport.x-worldBBox.x+viewport.width) THEN maxx := ENTIER(viewport.x-worldBBox.x+viewport.width) END;
IF maxy>ENTIER(viewport.y-worldBBox.y+viewport.height) THEN maxy :=ENTIER( viewport.y-worldBBox.y+viewport.height) END;
imgWidth := maxx-minx+1;
imgHeight := maxy-miny+1;
IF (imgWidth<=0) OR (imgHeight<=0) THEN
imgWidth := 1;
imgHeight := 1;
END;
NEW(img);
Raster.Create(img, imgWidth, imgHeight, Raster.BGRA8888);
Raster.InitMode(mode, Raster.srcCopy);
FOR y := 0 TO imgHeight-1 DO
FOR x := 0 TO imgWidth-1 DO
p.x := minx+x;
p.y := miny+y;
p := transform.Transform(p);
Raster.Put(img, x, y, gradient.GetFromPoint(p), mode);
END; END;
RETURN Gfx.NewPattern(ctx, img, SHORT(patternStart.x), SHORT(patternStart.y));
END
END GetGradientAsPattern;
END GradientDict;
PROCEDURE ParseSpreadMethod*(value: SVG.String; VAR spreadMethod: SHORTINT);
BEGIN
IF value^ = "pad" THEN spreadMethod := SpreadMethodPad
ELSIF value^ = "reflect" THEN spreadMethod := SpreadMethodReflect
ELSIF value^ = "repeat" THEN spreadMethod := SpreadMethodRepeat
ELSE
SVG.Error("invalid gradient attribute spreadMethod");
SVG.Error(value^)
END
END ParseSpreadMethod;
END SVGGradients.