Skip to content
This repository has been archived by the owner on Feb 6, 2022. It is now read-only.

Commit

Permalink
Fixed SVG outlines to not render behind the original text.
Browse files Browse the repository at this point in the history
Reported in #52
  • Loading branch information
Arnavion committed Aug 3, 2015
1 parent 2ee0448 commit 6939e10
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 33 deletions.
118 changes: 85 additions & 33 deletions src/renderers/web/span-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export class SpanStyles {
})((x: number, y: number): void => {
const outlineFilter = <SVGFEMorphologyElement>document.createElementNS("http://www.w3.org/2000/svg", "feMorphology");
filterElement.appendChild(outlineFilter);
outlineFilter.in1.baseVal = "SourceAlpha";
outlineFilter.in1.baseVal = "source";
outlineFilter.operator.baseVal = SVGFEMorphologyElement.SVG_MORPHOLOGY_OPERATOR_DILATE;
outlineFilter.radiusX.baseVal = x;
outlineFilter.radiusY.baseVal = y;
Expand All @@ -296,42 +296,77 @@ export class SpanStyles {
outlineNumber++;
});

if (outlineNumber > 0) {
const mergedOutlines = <SVGFEMergeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlines);
mergedOutlines.result.baseVal = "outline";

for (let i = 0; i < outlineNumber; i++) {
const outlineReferenceNode = <SVGFEMergeNodeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlines.appendChild(outlineReferenceNode);
outlineReferenceNode.in1.baseVal = `outline${ i }`;
}

const outlineColorFilter = <SVGFEFloodElement>document.createElementNS("http://www.w3.org/2000/svg", "feFlood");
filterElement.appendChild(outlineColorFilter);
outlineColorFilter.setAttribute("flood-color", outlineColor.toString());
// Start with SourceAlpha. Leave the alpha as 0 if it's 0, and set it to 1 if it's greater than 0
const source = <SVGFEComponentTransferElement>document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
filterElement.insertBefore(source, filterElement.firstElementChild);
source.in1.baseVal = "SourceAlpha";
source.result.baseVal = "source";

const sourceAlphaTransferNode = <SVGFEFuncAElement>document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
source.appendChild(sourceAlphaTransferNode);
sourceAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
sourceAlphaTransferNode.slope.baseVal = 1e6; // 0 remains 0, greater-than-0 becomes 1
sourceAlphaTransferNode.intercept.baseVal = 0;

// Merge the individual outlines
const mergedOutlines = <SVGFEMergeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlines);

for (let i = 0; i < outlineNumber; i++) {
const outlineReferenceNode = <SVGFEMergeNodeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlines.appendChild(outlineReferenceNode);
outlineReferenceNode.in1.baseVal = `outline${ i }`;
}

const coloredOutline = <SVGFECompositeElement>document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
filterElement.appendChild(coloredOutline);
coloredOutline.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_IN;
coloredOutline.in2.baseVal = "outline";
// Color it with the outline color
const coloredSource = <SVGFEComponentTransferElement>document.createElementNS("http://www.w3.org/2000/svg", "feComponentTransfer");
filterElement.appendChild(coloredSource);

const outlineRedTransferNode = <SVGFEFuncRElement>document.createElementNS("http://www.w3.org/2000/svg", "feFuncR");
coloredSource.appendChild(outlineRedTransferNode);
outlineRedTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineRedTransferNode.slope.baseVal = 0;
outlineRedTransferNode.intercept.baseVal = outlineColor.red / 255 * outlineColor.alpha;

const outlineGreenTransferNode = <SVGFEFuncGElement>document.createElementNS("http://www.w3.org/2000/svg", "feFuncG");
coloredSource.appendChild(outlineGreenTransferNode);
outlineGreenTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineGreenTransferNode.slope.baseVal = 0;
outlineGreenTransferNode.intercept.baseVal = outlineColor.green / 255 * outlineColor.alpha;

const outlineBlueTransferNode = <SVGFEFuncBElement>document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
coloredSource.appendChild(outlineBlueTransferNode);
outlineBlueTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineBlueTransferNode.slope.baseVal = 0;
outlineBlueTransferNode.intercept.baseVal = outlineColor.blue / 255 * outlineColor.alpha;

const outlineAlphaTransferNode = <SVGFEFuncAElement>document.createElementNS("http://www.w3.org/2000/svg", "feFuncA");
coloredSource.appendChild(outlineAlphaTransferNode);
outlineAlphaTransferNode.type.baseVal = SVGComponentTransferFunctionElement.SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
outlineAlphaTransferNode.slope.baseVal = outlineColor.alpha;
outlineAlphaTransferNode.intercept.baseVal = 0;

// Blur the merged outline
if (this._gaussianBlur > 0) {
const gaussianBlurFilter = <SVGFEGaussianBlurElement>document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
}
for (let i = 0; i < this._blur; i++) {
const blurFilter = <SVGFEConvolveMatrixElement>document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
filterElement.appendChild(blurFilter);
blurFilter.setAttribute("kernelMatrix", "1 2 1 2 4 2 1 2 1");
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
}
}

if (this._gaussianBlur > 0) {
const gaussianBlurFilter = <SVGFEGaussianBlurElement>document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
}
for (let i = 0; i < this._blur; i++) {
const blurFilter = <SVGFEConvolveMatrixElement>document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
filterElement.appendChild(blurFilter);
blurFilter.setAttribute("kernelMatrix", "1 2 1 2 4 2 1 2 1");
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
}
// Cut out the source, so only the outline remains
const outlineCutoutNode = <SVGFECompositeElement>document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
filterElement.appendChild(outlineCutoutNode);
outlineCutoutNode.in2.baseVal = "source";
outlineCutoutNode.operator.baseVal = SVGFECompositeElement.SVG_FECOMPOSITE_OPERATOR_OUT;

if (filterElement.childElementCount > 0) {
// Merge the outline with the SourceGraphic
const mergedOutlineAndSourceGraphic = <SVGFEMergeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
filterElement.appendChild(mergedOutlineAndSourceGraphic);

Expand All @@ -341,7 +376,24 @@ export class SpanStyles {
const sourceGraphicReferenceNode = <SVGFEMergeNodeElement>document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
mergedOutlineAndSourceGraphic.appendChild(sourceGraphicReferenceNode);
sourceGraphicReferenceNode.in1.baseVal = "SourceGraphic";
}
else {
// Blur the source graphic directly
if (this._gaussianBlur > 0) {
const gaussianBlurFilter = <SVGFEGaussianBlurElement>document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
filterElement.appendChild(gaussianBlurFilter);
gaussianBlurFilter.stdDeviationX.baseVal = this._gaussianBlur;
gaussianBlurFilter.stdDeviationY.baseVal = this._gaussianBlur;
}
for (let i = 0; i < this._blur; i++) {
const blurFilter = <SVGFEConvolveMatrixElement>document.createElementNS("http://www.w3.org/2000/svg", "feConvolveMatrix");
filterElement.appendChild(blurFilter);
blurFilter.setAttribute("kernelMatrix", "1 2 1 2 4 2 1 2 1");
blurFilter.edgeMode.baseVal = SVGFEConvolveMatrixElement.SVG_EDGEMODE_NONE;
}
}

if (filterElement.childElementCount > 0) {
this._svgDefsElement.appendChild(filterElement);

filterWrapperSpan.style.webkitFilter = `url("#${ filterId }")`;
Expand Down
Binary file added tests/functional/outlines/chrome/outlines-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/functional/outlines/chrome/outlines-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/functional/outlines/chrome/outlines-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/functional/outlines/chrome/outlines-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions tests/functional/outlines/outlines.ass
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[Script Info]
Title:
ScriptType: v4.00+
WrapStyle: 0
PlayResX: 256
PlayResY: 144
Scroll Position: 0
Active Line: 0
Video Zoom Percent: 1
ScaledBorderAndShadow: yes

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,36,&H000000FF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
Style: Default2,Arial,36,&H000000FF,&H00FFFFFF,&H7F000000,&H00000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1
Style: Default3,Arial,36,&H9E0000FF,&H9EFFFFFF,&H9E000000,&H9E000000,0,0,0,0,100,100,0,0,1,2,1.2,2,15,15,15,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 1,0:00:00.00,0:00:01.00,Default,,0,0,0,,test
Dialogue: 1,0:00:01.00,0:00:02.00,Default2,,0,0,0,,test
Dialogue: 1,0:00:02.00,0:00:03.00,Default,,0,0,0,,{\alpha&9E&}test
Dialogue: 1,0:00:02.00,0:00:03.00,Default3,,0,0,0,,test
Dialogue: 1,0:00:03.00,0:00:04.00,Default2,,0,0,0,,{\alpha&9E&}test
Dialogue: 1,0:00:03.00,0:00:04.00,Default3,,0,0,0,,test
33 changes: 33 additions & 0 deletions tests/functional/outlines/outlines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* libjass
*
* https://github.com/Arnavion/libjass
*
* Copyright 2013 Arnav Singh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

define(["intern!tdd", "require", "tests/support/test-page"], function (tdd, require, TestPage) {
tdd.suite("Outlines", function () {
tdd.test("Basic", function () {
var testPage = new TestPage(this.remote, require.toUrl("tests/support/browser-test-page.html"), "/tests/functional/outlines/outlines.ass", 256, 144);
return testPage
.prepare()
.then(function (testPage) { return testPage.seekAndCompareScreenshot(0.5, require.toUrl("./outlines-1.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(1.5, require.toUrl("./outlines-2.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(2.5, require.toUrl("./outlines-3.png")); })
.then(function (testPage) { return testPage.seekAndCompareScreenshot(3.5, require.toUrl("./outlines-4.png")); });
});
});
});
1 change: 1 addition & 0 deletions tests/intern.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ define(["intern", "intern/dojo/has!host-node?tests/support/encoded-firefox-profi
"tests/functional/auto-clock",
"tests/functional/fsc/fsc",
"tests/functional/kfx/kfx",
"tests/functional/outlines/outlines",
"tests/functional/r/alpha",
"tests/functional/t/alpha"
],
Expand Down

0 comments on commit 6939e10

Please sign in to comment.