MODULE SVGFilters;
IMPORT SVG, SVGColors, SVGUtilities, XML, XMLObjects, Raster, Math;
CONST
InSourceGraphic*=0;
InFilterElement*=1;
BlendModeNormal=0;
BlendModeMultiply=1;
BlendModeScreen=2;
BlendModeDarken=3;
BlendModeLighten=4;
ColorMatrixTypeMatrix=0;
ColorMatrixTypeSaturate=1;
ColorMatrixTypeHueRotate=2;
ColorMatrixTypeLuminanceToAlpha=3;
TYPE
Buffer*=SVG.Document;
FilterWindow*=POINTER TO FilterWindowDesc;
FilterWindowDesc*=RECORD
x*, y*, width*, height*: SVG.Length;
modeBlend, modeCopy: Raster.Mode;
sourceGraphic: Buffer;
END;
In*=POINTER TO InDesc;
InDesc*=RECORD
type*: SHORTINT;
fe*: FilterElement;
END;
FilterElement*=OBJECT
VAR
in*: In;
x*,y*,width*,height*: SVG.Length;
PROCEDURE Apply*(window: FilterWindow): Buffer;
VAR target: Buffer;
BEGIN
target := SVG.NewDocument(ENTIER(width),ENTIER(height));
ApplyFilter(window, target);
RETURN target;
END Apply;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
BEGIN
HALT(99)
END ApplyFilter;
PROCEDURE GetInBuffer(in: In; window: FilterWindow): Buffer;
VAR b:Buffer; fe: FilterElement;
BEGIN
CASE in.type OF
InSourceGraphic: RETURN window.sourceGraphic;
| InFilterElement: fe:=in.fe; b:=fe.Apply(window); RETURN b;
END
END GetInBuffer;
PROCEDURE GetInPixel(x,y, tx0,ty0: LONGINT; window: FilterWindow; in: In; source: Buffer; VAR pix: Raster.Pixel);
BEGIN
IF in.type#InFilterElement THEN
x:=x+tx0-ENTIER(window.x);
y:=y+ty0-ENTIER(window.y);
END;
IF (0 <= x) & (x < source.width) & (0 <= y) & (y < source.height) THEN
Raster.Get(source,x,y, pix, window.modeCopy);
ELSE
Raster.SetRGBA(pix,0,0,0,0)
END
END GetInPixel;
END FilterElement;
feBlend*=OBJECT(FilterElement)
VAR
in2*: In;
mode*: SHORTINT;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty: LONGINT;
source,source2: Buffer;
pix,pix2: Raster.Pixel;
BEGIN
source := GetInBuffer(in,window);
source2 := GetInBuffer(in2,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
GetInPixel(tx,ty, ENTIER(x),ENTIER(y), window, in,source, pix);
GetInPixel(tx,ty, ENTIER(x),ENTIER(y), window, in2,source2, pix2);
Blend(pix,pix2);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
PROCEDURE Blend(VAR a, b: Raster.Pixel);
VAR fa, fb, ta, tb, i: LONGINT;
BEGIN
CASE mode OF
| BlendModeNormal:
fa := 255;
fb := 255-ORD(a[3]);
FOR i := 0 TO 2 DO
a[i] := Raster.Clamp[200H + (fa * ORD(a[i]) + fb * ORD(b[i])) DIV 255]
END
| BlendModeMultiply:
fb := 255-ORD(a[3]);
FOR i := 0 TO 2 DO
fa := 255-ORD(b[3])+ORD(b[i]);
a[i] := Raster.Clamp[200H + (fa * ORD(a[i]) + fb * ORD(b[i])) DIV 255]
END
| BlendModeScreen:
fb := 255;
FOR i := 0 TO 2 DO
fa := 255-ORD(b[i]);
a[i] := Raster.Clamp[200H + (fa * ORD(a[i]) + fb * ORD(b[i])) DIV 255]
END
| BlendModeDarken:
fa := 255-ORD(b[3]);
fb := 255-ORD(a[3]);
FOR i := 0 TO 2 DO
ta := fa * ORD(a[i]) + 255 * LONG(ORD(b[i]));
tb := 255 * LONG(ORD(a[i])) + fb * ORD(b[i]);
IF ta<tb THEN
a[i] := Raster.Clamp[200H + ta DIV 255]
ELSE
a[i] := Raster.Clamp[200H + tb DIV 255]
END
END
| BlendModeLighten:
fa := 255-ORD(b[3]);
fb := 255-ORD(a[3]);
FOR i := 0 TO 2 DO
ta := fa * ORD(a[i]) + 255 * LONG(ORD(b[i]));
tb := 255 * LONG(ORD(a[i])) + fb * ORD(b[i]);
IF ta>tb THEN
a[i] := Raster.Clamp[200H + ta DIV 255]
ELSE
a[i] := Raster.Clamp[200H + tb DIV 255]
END
END
END;
fa := 255-ORD(a[3]);
fb := 255-ORD(b[3]);
a[3] := Raster.Clamp[200H + 255 - (fa*fb) DIV 255]
END Blend;
END feBlend;
feOffset*=OBJECT(FilterElement)
VAR
dx*,dy*: SVG.Length;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty, sx,sy: LONGINT;
source: Buffer;
pix: Raster.Pixel;
BEGIN
source := GetInBuffer(in,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
sx := ENTIER(tx-dx);
sy := ENTIER(ty-dy);
GetInPixel(sx,sy, ENTIER(x),ENTIER(y), window, in,source, pix);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
END feOffset;
ColorMatrix*=RECORD
a: ARRAY 5,4 OF LONGREAL;
END;
feColorMatrix*=OBJECT(FilterElement)
VAR
type*: SHORTINT;
matrix*: ColorMatrix;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty: LONGINT;
source: Buffer;
pix: Raster.Pixel;
BEGIN
source := GetInBuffer(in,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
GetInPixel(tx,ty, ENTIER(x),ENTIER(y), window, in,source, pix);
TransformByColorMatrix(pix,matrix);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
END feColorMatrix;
feGaussianBlur*=OBJECT(FilterElement)
VAR
stdDeviationX*, stdDeviationY*: SVG.Length;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty, k: LONGINT;
source: Buffer;
pix: Raster.Pixel;
temp: Buffer;
kernel: POINTER TO ARRAY OF LONGREAL;
kernelSizeX, kernelSizeY, kernelHalfX, kernelHalfY: LONGINT;
norm, sSquared2: LONGREAL;
sum: SVGColors.ColorSum;
BEGIN
kernelHalfX := 3*ENTIER(stdDeviationX);
kernelHalfY := 3*ENTIER(stdDeviationY);
kernelSizeX := 2*kernelHalfX+1;
kernelSizeY := 2*kernelHalfY+1;
IF stdDeviationX>stdDeviationY THEN NEW(kernel,kernelSizeX)
ELSE NEW(kernel,kernelSizeY);
END;
source := GetInBuffer(in,window);
temp := SVG.NewDocument(source.width, source.height);
sSquared2 := 2*stdDeviationX*stdDeviationX;
norm := Math.sqrt(SHORT(sSquared2)*Math.pi);
FOR k :=-kernelHalfX TO kernelHalfX DO
kernel[kernelHalfX+k] := Math.exp(SHORT(-k*k/sSquared2)) / norm;
END;
FOR ty:=0 TO temp.height-1 DO
FOR tx:=0 TO temp.width-1 DO
sum[0] := 0; sum[1] := 0; sum[2] := 0; sum[3] := 0;
FOR k:=-kernelHalfX TO kernelHalfX DO
GetInPixel(tx+k,ty, ENTIER(x),ENTIER(y), window, in,source, pix);
SVGColors.WeightedAdd(sum, kernel[kernelHalfX+k], pix);
END;
SVGColors.ColorSumToPixel(sum, pix);
Raster.Put(temp, tx,ty, pix, window.modeCopy)
END; END;
sSquared2 := 2*stdDeviationY*stdDeviationY;
norm := Math.sqrt(SHORT(sSquared2)*Math.pi);
FOR k :=-kernelHalfY TO kernelHalfY DO
kernel[kernelHalfY+k] := Math.exp(SHORT(-k*k/sSquared2)) / norm;
END;
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
sum[0] := 0; sum[1] := 0; sum[2] := 0; sum[3] := 0;
FOR k:=-kernelHalfY TO kernelHalfY DO
GetInPixel(tx,ty+k, ENTIER(x),ENTIER(y), window, in,temp, pix);
SVGColors.WeightedAdd(sum, kernel[kernelHalfY+k], pix);
END;
SVGColors.ColorSumToPixel(sum, pix);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END
END ApplyFilter;
END feGaussianBlur;
feMerge*=OBJECT(FilterElement)
VAR in2: XMLObjects.List;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty: LONGINT;
source: Buffer;
pix: Raster.Pixel;
enum: XMLObjects.Enumerator;
next: In;
nextPtr: ANY;
BEGIN
IF in#NIL THEN
source := GetInBuffer(in,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
GetInPixel(tx,ty, ENTIER(x),ENTIER(y), window, in,source, pix);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
IF in2#NIL THEN
enum := in2.GetEnumerator();
WHILE enum.HasMoreElements() DO
nextPtr := enum.GetNext();
next := nextPtr(In);
source := GetInBuffer(next,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
GetInPixel(tx,ty, ENTIER(x),ENTIER(y), window, in,source, pix);
Raster.Put(target, tx,ty, pix, window.modeBlend)
END; END;
END
END
END
END ApplyFilter;
PROCEDURE AddNode*(in: In);
BEGIN
IF in2=NIL THEN NEW(in2) END;
in2.Add(in);
END AddNode;
END feMerge;
feFlood*=OBJECT(FilterElement)
VAR
pix*: Raster.Pixel;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty: LONGINT;
BEGIN
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
END feFlood;
feImage*=OBJECT(FilterElement)
VAR image*: Buffer;
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty, sx,sy: LONGINT;
pix: Raster.Pixel;
BEGIN
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
sx := ENTIER(tx*image.width/width);
sy := ENTIER(ty*image.height/height);
IF (sx <image.width) & (sy < image.height) THEN
Raster.Get(image, sx,sy, pix, window.modeCopy);
ELSE
Raster.SetRGBA(pix,0,0,0,0)
END;
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
END feImage;
feTile*=OBJECT(FilterElement)
PROCEDURE ApplyFilter*(window: FilterWindow; target: Buffer);
VAR tx,ty, sx,sy: LONGINT;
source: Buffer;
pix: Raster.Pixel;
BEGIN
source := GetInBuffer(in,window);
FOR ty:=0 TO target.height-1 DO
FOR tx:=0 TO target.width-1 DO
sx := tx MOD source.width;
sy := ty MOD source.height;
GetInPixel(sx,sy, ENTIER(x),ENTIER(y), window, in,source, pix);
Raster.Put(target, tx,ty, pix, window.modeCopy)
END; END;
END ApplyFilter;
END feTile;
Filter*=OBJECT
VAR fElements: XMLObjects.ArrayDict;
rootElement*: FilterElement;
window*: FilterWindow;
PROCEDURE &New*;
BEGIN
NEW(fElements);
NEW(window);
Raster.InitMode(window.modeBlend, Raster.srcOverDst);
Raster.InitMode(window.modeCopy, Raster.srcCopy);
END New;
PROCEDURE AddFilterElement*(fElement: FilterElement; id: SVG.String);
BEGIN
fElements.Remove(id^);
fElements.Add(id^, fElement);
END AddFilterElement;
PROCEDURE GetFilterElement*(id: SVG.String):FilterElement;
VAR p: ANY;
BEGIN
p := fElements.Get(id^);
IF p = NIL THEN RETURN NIL
ELSE RETURN p(FilterElement) END
END GetFilterElement;
PROCEDURE Apply*(source, target: Buffer);
VAR result: Buffer;
minx, miny, maxx, maxy: LONGINT;
BEGIN
IF rootElement#NIL THEN
window.sourceGraphic := SVG.NewDocument(window.width,window.height);
Raster.Copy(source, window.sourceGraphic,
ENTIER(window.x), ENTIER(window.y), ENTIER(window.x+window.width), ENTIER(window.y+window.height),
0, 0, window.modeCopy);
result := rootElement.Apply(window);
minx := ENTIER(rootElement.x);
miny := ENTIER(rootElement.y);
maxx := ENTIER(rootElement.x+result.width);
maxy := ENTIER(rootElement.y+result.height);
IF minx<0 THEN minx := 0 END;
IF miny<0 THEN miny := 0 END;
IF maxx>target.width THEN maxx := target.width END;
IF maxy>target.height THEN maxy := target.height END;
IF (minx<=maxx) & (miny<=maxy) THEN
Raster.Copy(result, target,
minx-ENTIER(rootElement.x), miny-ENTIER(rootElement.y), maxx-ENTIER(rootElement.x), maxy-ENTIER(rootElement.y),
minx, miny, window.modeBlend)
END
ELSE
SVG.Log("Filter has no elements");
END
END Apply;
END Filter;
FilterDict*=OBJECT
VAR filters: XMLObjects.ArrayDict;
PROCEDURE &New*;
BEGIN
NEW(filters)
END New;
PROCEDURE AddFilter*(filter: Filter; id: SVG.String);
BEGIN
filters.Add(id^, filter)
END AddFilter;
PROCEDURE GetFilter*(id: SVG.String):Filter;
VAR p: ANY;
BEGIN
p := filters.Get(id^);
IF p = NIL THEN RETURN NIL
ELSE RETURN p(Filter) END
END GetFilter;
END FilterDict;
FilterStack*=OBJECT
VAR
topFilter: Filter;
next: FilterStack;
PROCEDURE Push*(filter: Filter);
VAR pushed: FilterStack;
BEGIN
NEW(pushed);
pushed^ := SELF^;
next := pushed;
topFilter:=filter;
END Push;
PROCEDURE Pop*(VAR filter: Filter);
BEGIN
filter := topFilter;
SELF^ := next^;
END Pop;
END FilterStack;
PROCEDURE ParseIn*(value: SVG.String; VAR in: SHORTINT);
BEGIN
IF value^ = "SourceGraphic" THEN in := InSourceGraphic
ELSE
in := InFilterElement
END
END ParseIn;
PROCEDURE ParseBlendMode*(value: SVG.String; VAR mode: SHORTINT);
BEGIN
IF value^ = "normal" THEN mode := BlendModeNormal
ELSIF value^ = "multiply" THEN mode := BlendModeMultiply
ELSIF value^ = "screen" THEN mode := BlendModeScreen
ELSIF value^ = "darken" THEN mode := BlendModeDarken
ELSIF value^ = "lighten" THEN mode := BlendModeLighten
ELSE
SVG.Error("mode attribute feBlend must be 'normal', 'multiply', 'screen', 'darken' or 'lighten'");
mode := BlendModeNormal
END
END ParseBlendMode;
PROCEDURE ParseColorMatrixType*(value: XML.String; VAR type: SHORTINT);
BEGIN
IF value^ = "matrix" THEN type := ColorMatrixTypeMatrix
ELSIF value^ = "saturate" THEN type := ColorMatrixTypeSaturate
ELSIF value^ = "hueRotate" THEN type := ColorMatrixTypeHueRotate
ELSIF value^ = "luminanceToAlpha" THEN type := ColorMatrixTypeLuminanceToAlpha
ELSE
SVG.Error("type attribute feColorMatrix must be 'matrix', 'saturate', 'hueRotate' or 'luminanceToAlpha'");
type := ColorMatrixTypeMatrix
END
END ParseColorMatrixType;
PROCEDURE ParseColorMatrixValues*(values: XML.String; type: SHORTINT; VAR matrix: ColorMatrix):BOOLEAN;
VAR pos,i,j: LONGINT;
angle, s,c: LONGREAL;
BEGIN
FOR i := 0 TO 4 DO
FOR j := 0 TO 3 DO
matrix.a[i,j] := 0;
END; END;
IF type=ColorMatrixTypeLuminanceToAlpha THEN
matrix.a[0,3] := 0.2125;
matrix.a[1,3] := 0.7154;
matrix.a[2,3] := 0.0721;
RETURN TRUE
END;
matrix.a[0,0] := 1;
matrix.a[1,1] := 1;
matrix.a[2,2] := 1;
matrix.a[3,3] := 1;
IF values=NIL THEN RETURN TRUE END;
pos :=0;
CASE type OF
| ColorMatrixTypeMatrix:
FOR j := 0 TO 3 DO
FOR i := 0 TO 4 DO
SVGUtilities.SkipCommaWhiteSpace(pos, values);
SVGUtilities.StrToFloatPos(values^, matrix.a[i,j], pos);
END;END;
RETURN TRUE
| ColorMatrixTypeSaturate:
SVGUtilities.SkipCommaWhiteSpace(pos, values);
SVGUtilities.StrToFloatPos(values^, s, pos);
matrix.a[0,0] := 0.213+0.787*s; matrix.a[1,0] := 0.715-0.715*s; matrix.a[2,0] := 0.072-0.072*s;
matrix.a[0,1] := 0.213-0.213*s; matrix.a[1,1] := 0.715+0.285*s; matrix.a[2,1] := 0.072-0.072*s;
matrix.a[0,2] := 0.213-0.213*s; matrix.a[1,2] := 0.715-0.715*s; matrix.a[2,2] := 0.072+0.928*s;
RETURN TRUE
| ColorMatrixTypeHueRotate:
SVGUtilities.SkipCommaWhiteSpace(pos, values);
SVGUtilities.StrToFloatPos(values^, angle, pos);
s := Math.sin(-SHORT(angle)/180.0*Math.pi);
c := Math.cos(-SHORT(angle)/180.0*Math.pi);
matrix.a[0,0] := 0.213+0.787*c-0.213*s; matrix.a[1,0] := 0.715-0.715*c-0.715*s; matrix.a[2,0] := 0.072-0.072*c+0.928*s;
matrix.a[0,1] := 0.213-0.213*c+0.143*s; matrix.a[1,1] := 0.715+0.285*c+0.140*s; matrix.a[2,1] := 0.072-0.072*c-0.283*s;
matrix.a[0,2] := 0.213-0.213*c-0.787*s; matrix.a[1,2] := 0.715-0.715*c+0.715*s; matrix.a[2,2] := 0.072+0.928*c+0.072*s;
RETURN TRUE
END;
RETURN FALSE
END ParseColorMatrixValues;
PROCEDURE TransformByColorMatrix(VAR pix: Raster.Pixel; VAR matrix: ColorMatrix);
VAR p, result: ARRAY 4 OF LONGREAL;
BEGIN
p[0] := ORD(pix[0])/255.0;
p[1] := ORD(pix[1])/255.0;
p[2] := ORD(pix[2])/255.0;
p[3] := ORD(pix[3])/255.0;
result[0] := matrix.a[0,0]*p[0] + matrix.a[1,0]*p[1] + matrix.a[2,0]*p[2] + matrix.a[3,0]*p[3] + matrix.a[4,0];
result[1] := matrix.a[0,1]*p[0] + matrix.a[1,1]*p[1] + matrix.a[2,1]*p[2] + matrix.a[3,1]*p[3] + matrix.a[4,1];
result[2] := matrix.a[0,2]*p[0] + matrix.a[1,2]*p[1] + matrix.a[2,2]*p[2] + matrix.a[3,2]*p[3] + matrix.a[4,2];
result[3] := matrix.a[0,3]*p[0] + matrix.a[1,3]*p[1] + matrix.a[2,3]*p[2] + matrix.a[3,3]*p[3] + matrix.a[4,3];
pix[0] := Raster.Clamp[200H+ENTIER(result[0]*255)];
pix[1] := Raster.Clamp[200H+ENTIER(result[1]*255)];
pix[2] := Raster.Clamp[200H+ENTIER(result[2]*255)];
pix[3] := Raster.Clamp[200H+ENTIER(result[3]*255)];
END TransformByColorMatrix;
END SVGFilters.