Reputation: 31
I'm trying to export an image that has been modified with SVG filters to include a drop shadow. I'm using SVG filters for this instead of CSS due to the shadow spread ability that CSS currently does not support. Unfortunately canvas also does not support a photoshop like shadow spread feature. Canvas will write the image, but not include the SVG filters, only canvas specific shadow filters.
The end goal of this is to export a jpeg image with the filters included. So it'll be SVG > Canvas > Image.
So I guess my question is, how do I trick canvas to include the SVG filters when drawing the image?
Here's what I have so far.
Image HTML:
<img ng-src="logo.png" style="-webkit-filter: url("#logo-filter"); filter: url("#logo-filter");" />
SVG Filter Settings:
<svg>
<defs>
<filter id="logo-filter" x="0" y="0" width="200%" height="200%">
<feFlood result="flood" flood-color="#00FFFF" flood-opacity="1"></feFlood>
<feComposite in="flood" result="mask" in2="SourceGraphic" operator="in" />
<feOffset in="mask" result="offset" dx="10" dy="10" />
<feGaussianBlur in="offset" result="blurred" stdDeviation="3" />
<feComponentTransfer>
<feFuncA type="gamma" exponent="0.5" amplitude="3" in="blurred" result="blurred2" />
</feComponentTransfer>
<feMerge>
<feMergeNode in="blurred2"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
</svg>
Canvas Implementation:
function plotLogo(img, logo, container) {
var canvas = document.getElementById("logo-canvas");
var position = getOffset($('#'+container.id+'-logo'));
var logo_image = new Image();
logo_image.src = img;
logo_image.onload = function(){
canvas.width = container.width;
canvas.height = container.height;
var context = canvas.getContext('2d');
/* StackOverflow Comment: Works but with no shadow spread */
context.shadowOffsetX = logo.shadow.offsetX;
context.shadowOffsetY = logo.shadow.offsetY;
context.shadowColor = logo.shadow.color;
context.shadowBlur = logo.shadow.blur*2; //Multiplied by 2 to get closest look to webkit shadow
context.drawImage(logo_image, position.posX, position.posY, logo.width, logo.height);
};
}
Upvotes: 2
Views: 1217
Reputation: 31
I figured out the answer to my own question, here's the solution I came up with.
I had to have the SVG basically mirror what the user would be manipulating using the svg image tag. Both linked to filter: url(#logo-filter).
<svg id="svg-logo" height="175" width="600">
<defs>
<filter id="logo-filter" x="-50%" y="-50%" width="200%" height="200%">
<feFlood result="flood" flood-color="[[params.logo.shadow.color]]" flood-opacity="1"></feFlood>
<feComposite in="flood" result="mask" in2="SourceGraphic" operator="in" />
<feOffset in="mask" result="offset" dx="[[params.logo.shadow.offsetX]]" dy="[[params.logo.shadow.offsetY]]" />
<feGaussianBlur in="offset" result="blurred" ng-stddeviation="[[params.logo.shadow.blur]]" />
<feComponentTransfer>
<feFuncA type="gamma" exponent="0.5" amplitude="[[params.logo.shadow.spread]]" in="blurred" result="blurred2" />
</feComponentTransfer>
<feMerge>
<feMergeNode in="blurred2"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<image id="svg-logo-image" xlink:href="logo.png" filter="url(#[[svg.logo.filter]])" x="0" y="0" />
</svg>
SVG information is then gathered and transformed into a base64 encode using window.btoa. Note that it will not work unless the xlink:href is also in base64 image code!
var svg = document.getElementById('svg-'+type);
var svgData = new XMLSerializer().serializeToString(svg);
var encodedData = window.btoa(unescape(encodeURIComponent(svgData)));
var newSrc = 'data:image/svg+xml;base64,'+encodedData;
Here's the full function I currently have.
function plotImage(element, container, index, img) {
var type = img ? 'logo' : 'address';
var canvas = document.getElementById(type+'-canvas');
var svg = document.getElementById('svg-'+type);
var svgElem = document.getElementById('svg-'+type+'-image');
var position = getOffset($('#'+container.name+'-'+type), $('#'+container.name));
svg.setAttribute('width', container.width);
svg.setAttribute('height', container.height);
if(typeof img !== 'undefined') {
svgElem.setAttribute('x', position.x);
svgElem.setAttribute('y', position.y);
svgElem.setAttribute('width', element.width);
svgElem.setAttribute('height', element.height);
svgElem.setAttribute('xlink:href', img);
} else {
svgElem.setAttribute('x', position.x);
svgElem.setAttribute('y', position.y+(element.size/3)); // Adjust Y via 1/3 of font-size
}
var svgData = new XMLSerializer().serializeToString(svg);
var encodedData = window.btoa(unescape(encodeURIComponent(svgData)));
var newSrc = 'data:image/svg+xml;base64,'+encodedData;
// Alternate Method, (slower)
// var svgBlob = new Blob([svgData], {type: "image/svg+xml;charset=utf-8"});
// var newSrc = window.URL.createObjectURL(svgBlob);
var image = new Image();
image.src = newSrc;
image.onload = drawCanvas(image, canvas, container);
}
Upvotes: 1