DropShadowFilter not working on html5/ios/android

I have the following code (OpenFL 8.9.6-LpgCLS):

import openfl.filters.DropShadowFilter;
import openfl.display.Sprite;

class Main extends Sprite {

  public function new () {	
    super ();
    var r = new ShadowedRect();
    r.x = 200;
    r.y = 200;
    addChild(r);
  }

}

class ShadowedRect extends Sprite {
  public function new() {
    super();
    graphics.beginFill(0xff0000);
    graphics.drawRect(0, 0, 100, 100);
    graphics.endFill();
    filters = [ new DropShadowFilter() ];
  }
}

I would expect this to draw a red square with a dropshadow, but the shadow is not rendered in any of my targets (html5, ios, android). Am I missing something obvious?

You code is fine. Some of the filters have indeed been buggy in the past few OpenFL releases when compiling for non-Flash targets.

My current project targets HTML5 and I have implemented workarounds for Glow and DropShadow filters using BitmapData draw operations until the built in filters are fixed. My workarounds are not ideal, though, since they require creating, updating, and manually disposing another display object…

Hopefully filters will get fixed soon.

I don’t suppose you’re willing to share your workaround…? =)

Sure thing! I dunno how efficient it is, but I don’t mind sharing.

I do think my “distance” values differ slightly than how Flash/OpenFL implemented them, so you might have to play around with the implementation or change my code. Currently I only have basic strokes, glows, and drop shadows implemented.

Here’s a sample file to test the work around:

package;

import haxe.Timer;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.text.TextFieldAutoSize;
import openfl.text.TextFormat;

/**
 * ...
 * @author Tamar Curry
 */
class Main extends Sprite 
{
	
	private var tf:TextField;
	private var filterSprite:FilterSprite;
	
	public function new() 
	{
		super();
		
		tf = new TextField();
		tf.width = 1;
		tf.height = 1;
		tf.autoSize = TextFieldAutoSize.LEFT;
		
		var format:TextFormat = tf.defaultTextFormat;
		format.size = 40;
		tf.defaultTextFormat = format;
		
		tf.x = 20;
		tf.y = 20;
		
		filterSprite = new FilterSprite();
		filterSprite.source = tf;
		filterSprite.x = tf.x;
		filterSprite.y = tf.y;
		filterSprite.addStroke( 2, 0xffffff );
		filterSprite.addGlow( 4, 0x990000, 1, 8, 8 );
		filterSprite.addDropShadow();
		
		addChild( filterSprite );
		addChild( tf );
		
		newText();
		
		var timer:Timer = new Timer(1000);
		timer.run = newText;
	}
	
	private function newText():Void
	{
		tf.text = "Hello, World " + Math.round( Math.random() * 999999 );
		filterSprite.render();
	}
}

And then here’s the source for the filter workarounds. The regular Blur filter still works in HTML5 builds, so I use it as an optional softening effect if desired:

package ;

import openfl.display.Bitmap;
import openfl.display.BitmapData;
import openfl.display.DisplayObject;
import openfl.display.PixelSnapping;
import openfl.display.Sprite;
import openfl.filters.BitmapFilter;
import openfl.filters.BlurFilter;
import openfl.geom.ColorTransform;
import openfl.geom.Matrix;
import openfl.geom.Point;
import openfl.geom.Rectangle;

/**
 * ...
 * @author Tamar Curry
 */
class FilterSprite extends Sprite
{
	public var source:DisplayObject;
	
	private var _holder:Sprite;
	private var _matrix:Matrix;
	private var _bitmaps:Array<Bitmap>;
	private var _filterEffects:Array<IFilterEffect>;
	public var _sourceRender:BitmapData;
	
	public var hasRender(default, null):Bool;
	
	// -----------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------
	public function new() 
	{
		super();
		_filterEffects = [];
		_bitmaps = [];
		_matrix = new Matrix();
		hasRender = false;
		_holder = new Sprite();
		addChild(_holder);
		this.mouseEnabled = false;
		this.mouseChildren = false;
	}
	
	// -----------------------------------------------------------------------------------------------
	public function addStroke(distance:Int, rgbColor:Int, alpha:Float = 1):Void
	{
		_filterEffects.push( new GlowEffect(distance, rgbColor, alpha, 0, 0) );
		addBitmap();
	}
	
	// -----------------------------------------------------------------------------------------------
	public function addGlow(distance:Int, rgbColor:Int, alpha:Float = 1, blurX:Float = 0, blurY:Float = 0):Void
	{
		_filterEffects.push( new GlowEffect(distance, rgbColor, alpha, blurX, blurY) );
		addBitmap();
	}
	
	// -----------------------------------------------------------------------------------------------
	public function addDropShadow(distance:Float = 4, angle:Float = 45, rgbColor:Int = 0, alpha:Float = 1, blurX:Float = 4, blurY:Float = 4):Void
	{
		_filterEffects.push( new DropShadowEffect(distance, angle, rgbColor, alpha, blurX, blurY) );
		addBitmap();
	}
	
	// -----------------------------------------------------------------------------------------------
	private function addBitmap():Void
	{
		var bitmap:Bitmap = new Bitmap(null, PixelSnapping.AUTO, true);
		_holder.addChildAt(bitmap, 0);
		_bitmaps.push(bitmap);
	}
	
	// -----------------------------------------------------------------------------------------------
	public function clear():Void
	{
		_holder.removeChildren();
		hasRender = false;
		for ( f in _filterEffects ) {
			f.dispose();
		}
		_bitmaps.splice(0, _bitmaps.length);
		_filterEffects.splice(0, _filterEffects.length);
	}
	
	// -----------------------------------------------------------------------------------------------
	public function adjustPosition():Void
	{
		if ( source == null ) {
			return;
		}
		var rect:Rectangle = source.getBounds(null);
		_holder.x = rect.x;
		_holder.y = rect.y;
	}
	
	// -----------------------------------------------------------------------------------------------
	public function render():Void
	{
		_matrix.identity();
		hasRender = true;
		
		var w:Float = 2;
		var h:Float = 2;
		if ( source != null ) {
			var rect:Rectangle = source.getBounds(null);
			_matrix.scale( source.scaleX, source.scaleY );
			_matrix.translate( -rect.x, -rect.y );
			w = source.width;
			h = source.height;
		}
		
		var sW:Int = Math.ceil(w);
		var sH:Int = Math.ceil(h);
		
		_sourceRender = FilterEffectFuncs.checkBitmapData( _sourceRender, sW, sH );
		
		if ( source != null ) {
			_sourceRender.draw( source, _matrix, null, null, null, true );
		}
		
		for ( bitmap in _bitmaps ) {
			bitmap.x = 0;
			bitmap.y = 0;
			bitmap.filters = null;
			bitmap.bitmapData = null;
		}
		
		var len:Int = _filterEffects.length;
		var xOffset:Float = 0;
		var yOffset:Float = 0;
		var pixels:BitmapData = _sourceRender;
		
		for ( i in 0...len ) {
			if ( source == null ) {
				_filterEffects[i].clear();
			}
			else {
				_filterEffects[i].render(pixels, _bitmaps[i]);
				w = _filterEffects[i].bitmapData.width;
				h = _filterEffects[i].bitmapData.height;
				_bitmaps[i].filters = _filterEffects[i].getFilters();
				_matrix.identity();
				_bitmaps[i].x += xOffset;
				_bitmaps[i].y += yOffset;
				xOffset = _bitmaps[i].x;
				yOffset = _bitmaps[i].y;
				pixels = _filterEffects[i].bitmapData;
			}
		}
		
		adjustPosition();
	}
	
}


//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

class FilterEffectFuncs {

	// -----------------------------------------------------------------------------------------------
	public static function getColorTransform(RGBColor:Int):ColorTransform
	{
		var colorTransform:ColorTransform		= new ColorTransform();
		colorTransform.redOffset				= ( RGBColor >> 16 );
		colorTransform.greenOffset				= ( (RGBColor >> 8) & 0xff);
		colorTransform.blueOffset				= (RGBColor & 0xff);
		
		colorTransform.redMultiplier			= 0;
		colorTransform.greenMultiplier			= 0;
		colorTransform.blueMultiplier			= 0;
		return colorTransform;
	}
	
	// -----------------------------------------------------------------------------------------------
	public static function checkBitmapData(bitmapData:BitmapData, newWidth:Int, newHeight:Int):BitmapData
	{
		if ( bitmapData == null ) {
			bitmapData = new BitmapData(newWidth, newHeight, true, 0);
		}
		else if ( bitmapData.width < newWidth || bitmapData.height < newHeight ) {
			bitmapData.dispose();
			bitmapData = new BitmapData(newWidth, newHeight, true, 0);
		}
		else {
			bitmapData.fillRect( bitmapData.rect, 0 );
		}
		
		return bitmapData;
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

interface IFilterEffect {
	var bitmapData(default, null):BitmapData;
	function clear():Void;
	function render(pixels:BitmapData, bitmap:Bitmap):Void;
	function getFilters():Array<BitmapFilter>;
	function dispose():Void;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

class DropShadowEffect implements IFilterEffect
{
	private var _xDist:Float;
	private var _yDist:Float;
	private var _theta:Float;
	
	private var _pt:Point;
	private var _filter:Array<BitmapFilter>;
	private var _colorTransform:ColorTransform;
	
	public var rgbColor(default, null):Int;
	public var blurX(default, null):Float;
	public var blurY(default, null):Float;
	public var alpha(default, null):Float;
	public var angle(default, null):Float;
	public var distance(default, null):Float;
	public var bitmapData(default, null):BitmapData;
	
	// -----------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------
	public function new(distance:Float, angle:Float, rgbColor:Int, alpha:Float, blurX:Float, blurY:Float) {
		this.distance = distance;
		this.angle = angle;
		this.rgbColor = rgbColor;
		this.alpha = alpha;
		this.blurX = blurX;
		this.blurY = blurY;
		
		_pt = new Point();
		
		_theta = Math.PI * angle / 180;
		_xDist = Math.cos(_theta) * distance;
		_yDist = Math.sin(_theta) * distance;
		
		_colorTransform = FilterEffectFuncs.getColorTransform(rgbColor);
		_filter = null;
		
		if ( blurX != 0 || blurY != 0 ) {
			_filter = [ new BlurFilter(blurX, blurY) ];
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function dispose():Void
	{
		if ( bitmapData != null ) {
			bitmapData.dispose();
			bitmapData = null;
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function clear():Void 
	{
		if ( bitmapData != null ) {
			bitmapData.fillRect( bitmapData.rect, 0 );
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function getFilters():Array<BitmapFilter>
	{
		return _filter;
	}
	
	// -----------------------------------------------------------------------------------------------
	public function render(pixels:BitmapData, bitmap:Bitmap):Void
	{
		bitmapData = FilterEffectFuncs.checkBitmapData( bitmapData, pixels.width, pixels.height );
		bitmapData.copyPixels( pixels, pixels.rect, _pt, null, null, true );
		bitmapData.colorTransform( bitmapData.rect, _colorTransform );
		bitmap.bitmapData = bitmapData;
		bitmap.smoothing = true;
		bitmap.x = _xDist;
		bitmap.y = _yDist;
		bitmap.alpha = alpha;
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

class GlowEffect implements IFilterEffect
{
	private var _pt:Point;
	private var _filter:Array<BitmapFilter>;
	private var _colorTransform:ColorTransform;
	
	public var alpha(default, null):Float;
	public var blurX(default, null):Float;
	public var blurY(default, null):Float;
	public var rgbColor(default, null):Int;
	public var distance(default, null):Int;
	public var bitmapData(default, null):BitmapData;
	
	// -----------------------------------------------------------------------------------------------
	// -----------------------------------------------------------------------------------------------
	public function new(distance:Int, rgbColor:Int, alpha:Float, blurX:Float, blurY:Float)
	{
		this.distance = distance;
		this.rgbColor = rgbColor;
		this.alpha = alpha;
		this.blurX = blurX;
		this.blurY = blurY;
		_pt = new Point();
		_filter = null;
		
		_colorTransform = FilterEffectFuncs.getColorTransform(rgbColor);
		
		if ( blurX != 0 || blurY != 0 ) {
			_filter = [ new BlurFilter(blurX, blurY) ];
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function dispose():Void
	{
		if ( bitmapData != null ) {
			bitmapData.dispose();
			bitmapData = null;
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function clear():Void
	{
		if ( bitmapData != null ) {
			bitmapData.fillRect( bitmapData.rect, 0 );
		}
	}
	
	// -----------------------------------------------------------------------------------------------
	public function getFilters():Array<BitmapFilter>
	{
		return _filter;
	}
	
	// -----------------------------------------------------------------------------------------------
	public function render(pixels:BitmapData, bitmap:Bitmap):Void
	{
		if ( distance <= 0 ) {
			return;
		}
		
		var bitmapW:Int = pixels.width + distance + distance;
		var bitmapH:Int = pixels.height + distance + distance;
		
		bitmapData = FilterEffectFuncs.checkBitmapData( bitmapData, bitmapW, bitmapH );
		
		var skipDrawing:Bool = false;
		var maxDist:Int = (distance * 2);
		
		for ( xOffset in 0...(maxDist+1) ) {
			for ( yOffset in 0...(maxDist+1) ) {
				skipDrawing = false;
				
				// don't draw the corners, because that makes it look rounder
				skipDrawing = skipDrawing || (xOffset == 0 && yOffset == 0);
				skipDrawing = skipDrawing || (xOffset == 0 && yOffset == maxDist);
				skipDrawing = skipDrawing || (xOffset == maxDist && yOffset == 0);
				skipDrawing = skipDrawing || (xOffset == maxDist && yOffset == maxDist);
				
				if ( !skipDrawing ) {
					_pt.x = xOffset;
					_pt.y = yOffset;
					bitmapData.copyPixels( pixels, pixels.rect, _pt, null, null, true );
				}
			}
		}
		
		bitmapData.colorTransform( bitmapData.rect, _colorTransform );
		
		bitmap.bitmapData = bitmapData;
		bitmap.smoothing = true;
		bitmap.alpha = alpha;
		bitmap.x = -distance;
		bitmap.y = -distance;
	}
}
1 Like