Flash alpha GradientMatrix to PDF using purePDF

One of the most hard thing to traslate when creating pdf documents from existing flash movies is indeed the gradient matrix.

Not only because pdf and flash have 2 different coordinates system and because the gradient matrix is applied in 2 different ways, but also if you want to convert gradient with transparent colors inside.
Let me say that I’ve encountered the same issue Mario posted here, even if I made a little modification to his solution: GradientMatrix.as
This is the swf example. Click on the sprite to start the animation. It will rotate and translate both the sprite and its gradient matrix, then click again to stop the animation and create the pdf file at that frame.
SWF HERE
In order to create the correct gradient matrix with the right position and rotation in purePDF I’ve used the PdfShading.complexAxial static method in this way:
var cb_shading: PdfShading = PdfShading.complexAxial( writer, top_left.x, top_left.y, top_right.x, top_right.y, cb_colors, cb_ratios, true, true );

then for the alpha masking I’ve created a PdfTransparencyGroup applied to the alpha mask.

You can see my solution (which probably is not the best one, but it’s the one I discovered for now) in the code below.
[kml_flashembed movie=”/wp-content/uploads/2010/02/ExampleColorGradient2.swf” height=”300″ width=”400″ /]


And this is the code I used to do that (download ExampleColorGradient2.as):

package wiki
{
 import flash.display.GradientType;
 import flash.display.Graphics;
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import flash.events.Event;
 import flash.events.MouseEvent;
 import flash.geom.Matrix;
 import flash.geom.Point;
 import flash.geom.Rectangle;
 import flash.net.FileReference;
 import flash.text.TextField;
 import flash.text.TextFormat;
 import flash.text.TextFormatAlign;
 import flash.text.engine.ElementFormat;
 import flash.text.engine.FontDescription;
 import flash.text.engine.TextBlock;
 import flash.text.engine.TextElement;
 import flash.text.engine.TextLine;
 import flash.utils.ByteArray;

 import org.purepdf.colors.GrayColor;
 import org.purepdf.colors.RGBColor;
 import org.purepdf.pdf.PageSize;
 import org.purepdf.pdf.PdfContentByte;
 import org.purepdf.pdf.PdfDictionary;
 import org.purepdf.pdf.PdfDocument;
 import org.purepdf.pdf.PdfGState;
 import org.purepdf.pdf.PdfName;
 import org.purepdf.pdf.PdfShading;
 import org.purepdf.pdf.PdfShadingPattern;
 import org.purepdf.pdf.PdfTemplate;
 import org.purepdf.pdf.PdfTransparencyGroup;
 import org.purepdf.pdf.PdfWriter;

 [SWF(frameRate="60",width="400",height="300")]
 public class ExampleColorGradient2 extends Sprite
 {
 public function ExampleColorGradient2()
 {
 super();
 addEventListener( Event.ADDED_TO_STAGE, onAdded );
 }

 protected const SPRITE_W: Number = 250;
 protected const SPRITE_H: Number = 150;
 protected const STAGE_W: uint = 400;
 protected const STAGE_H: uint = 300;
 protected const GRADIENT_W: Number = SPRITE_W;
 protected const GRADIENT_H: Number = SPRITE_H;

 protected var sprite: Sprite;
 protected var frame: int = -1;
 protected var gradient_matrix: Matrix;
 protected var colors: Array;
 protected var alphas: Array;
 protected var ratios: Array;
 protected var gradient_rotation: Number = 0;
 protected var gradient_rect: Rectangle = new Rectangle( 0, 0, GRADIENT_W, GRADIENT_H );
 protected var text: TextField;

 protected var pair_x: Number;
 protected var pair_y: Number;

 // pdf properties
 private var cb_colors: Vector. = new Vector.();
 private var cb_ratios: Vector. = new Vector.();
 private var cb_alphas: Vector. = new Vector.();

 private var test_sprite: Sprite;

 protected function onAdded( event: Event ): void
 {
 stage.scaleMode = StageScaleMode.NO_SCALE;
 stage.align = StageAlign.TOP_LEFT;

 sprite = new Sprite();
 sprite.graphics.beginFill( 0, 0.5 );
 sprite.graphics.drawRect( 0, 0, SPRITE_W, SPRITE_H );
 sprite.graphics.endFill();

 gradient_matrix = new Matrix();
 colors = [ 0xFF0000, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FF00 ];
 alphas = [ 1, 1, 0, 1, 1 ];
 ratios = [ 0, 63, 126, 186, 255 ];

 sprite.buttonMode = true;
 sprite.addEventListener( MouseEvent.CLICK, onClick );
 addChild( sprite );

 // pdf

 for( var k: int = 0; k < colors.length; ++k )
 {
 cb_colors[k] = RGBColor.fromARGB( 0xFFFFFFFF & colors[k] );
 cb_ratios[k] = ratios[k]/255;
 cb_alphas[k] = alphas[k];
 }

 var msg_format: ElementFormat = new ElementFormat();
 msg_format.fontDescription = new FontDescription("Helvetica");
 msg_format.fontSize = 12;
 msg_format.color = 0x000000;

 var block: TextBlock = new TextBlock();
 block.content = new TextElement("Click the sprite to start/stop", msg_format);
 var message_line: TextLine = block.createTextLine();

 message_line.x = ( STAGE_W - message_line.width ) / 2;
 message_line.y = message_line.height;
 addChild( message_line );

 test_sprite = new Sprite();
 test_sprite.mouseEnabled = false;
 addChild( test_sprite );

 // background
 graphics.beginFill( 0xDDDDDD, 1 );
 for( k = 0; k < STAGE_H; k += 20 )
 {
 graphics.drawRect( 0, k, STAGE_W, 1 );
 }

 onEnterFrame( null );
 }

 protected function onEnterFrame( event: Event ): void
 {
 frame++;
 var matrix: Matrix = new Matrix();
 matrix.identity();
 matrix.translate( -SPRITE_W/2, -SPRITE_H/2 );
 matrix.rotate( radians( -frame/1.5 ) );
 matrix.translate( (STAGE_W/2) + ( 100 * Math.sin( frame/100 )), (STAGE_H/2) + ( 75 * Math.cos( frame/100 ))  );

 sprite.transform.matrix = matrix;

 gradient_rect.x = Math.sin( frame/100 ) * 100;
 gradient_rect.y = Math.cos( frame/100 ) * 100;

 var test: Matrix = new Matrix();
 test = GradientMatrix.getGradientBox( gradient_rect.width, gradient_rect.height, 0, gradient_rect.x, gradient_rect.y );
 pair_x = 1/test.a;
 pair_y = 1/test.d;

 gradient_rotation = radians( frame );
 gradient_matrix = GradientMatrix.getGradientBox( GRADIENT_W, GRADIENT_H, gradient_rotation, gradient_rect.x, gradient_rect.y );
 sprite.graphics.clear();
 sprite.graphics.beginGradientFill( GradientType.LINEAR, colors, alphas, ratios, gradient_matrix );
 sprite.graphics.drawRoundRect( 0, 0, SPRITE_W, SPRITE_H, 10, 10 );
 sprite.graphics.endFill();
 update();
 }

 protected function onClick( event: MouseEvent ): void
 {
 if( hasEventListener( Event.ENTER_FRAME ) )
 {
 removeEventListener( Event.ENTER_FRAME, onEnterFrame );
 save();
 } else {
 addEventListener( Event.ENTER_FRAME, onEnterFrame );
 }
 }

 private function save(): void
 {
 var k: int;
 var buffer: ByteArray = new ByteArray();
 var writer: PdfWriter = PdfWriter.create( buffer, PageSize.create( STAGE_W, STAGE_H ) );
 var document: PdfDocument = writer.pdfDocument;
 document.open();
 var cb: PdfContentByte = document.getDirectContent();
 cb.setTransform( new Matrix( 1, 0, 0, -1, 0, STAGE_H ) );

 // background
 cb.saveState();
 cb.setFillColor( 0xDDDDDD );
 for( k = 0; k < STAGE_H; k += 20 )
 {
 cb.rectangle( 0, k, STAGE_W, 1 );
 cb.fill();
 }
 cb.resetFill();
 cb.restoreState();

 // Exract the gradient box for pdf
 var box: Rectangle = gradient_rect.clone();
 var matrix: Matrix = new Matrix();
 matrix.translate( -GRADIENT_W/2, -GRADIENT_H/2 );
 matrix.translate( -gradient_rect.x, -gradient_rect.y );
 matrix.scale( pair_x, pair_y );
 matrix.concat( gradient_matrix );
 matrix.concat( sprite.transform.matrix );
 matrix.concat( new Matrix( 1, 0, 0, -1, 0, STAGE_H ) );

 var top_left: Point  = box.topLeft.clone();
 var top_right: Point = new Point( box.right, box.top );
 var bottom_right: Point = box.bottomRight.clone();
 var bottom_left: Point = new Point( box.left, box.bottom );

 top_left = matrix.transformPoint( top_left );
 top_right = matrix.transformPoint( top_right );
 bottom_right= matrix.transformPoint( bottom_right );
 bottom_left = matrix.transformPoint( bottom_left );

 cb.saveState();
 cb.setTransform( sprite.transform.matrix );

 // Create the template first
 var template: PdfTemplate = cb.createTemplate( GRADIENT_W, GRADIENT_H );
 var transGroup: PdfTransparencyGroup = new PdfTransparencyGroup();
 transGroup.put( PdfName.CS, PdfName.DEVICERGB );
 transGroup.isolated = true;
 transGroup.knockout = false;
 template.group = transGroup;

 var gState: PdfGState = new PdfGState();
 var maskDict: PdfDictionary = new PdfDictionary();
 maskDict.put( PdfName.TYPE, PdfName.MASK );
 maskDict.put( PdfName.S, new PdfName( "Luminosity" ) );
 maskDict.put( new PdfName( "G" ), template.indirectReference );
 gState.put( PdfName.SMASK, maskDict );
 cb.setGState( gState );

 // ALPHA TEMPLATE
 var matrix2: Matrix = new Matrix();
 matrix2.translate( -GRADIENT_W/2, -GRADIENT_H/2 );
 matrix2.scale( pair_x, pair_y );
 matrix2.concat( gradient_matrix );

 var alphas: Vector. = new Vector.( cb_alphas.length, true );
 for ( k = 0; k < cb_alphas.length; ++k )
 alphas[k] = new GrayColor( cb_alphas[k] );

 var template_shading: PdfShading = PdfShading.complexAxial( cb.writer, 0, 0, GRADIENT_W, 0, Vector.( alphas ), cb_ratios );
 var template_pattern: PdfShadingPattern = new PdfShadingPattern( template_shading );
 template_pattern.matrix = matrix2;

 template.rectangle( 0, 0, SPRITE_W, SPRITE_H );
 template.setShadingFill( template_pattern );
 template.fill();

 // CONTENT
 var cb_shading: PdfShading = PdfShading.complexAxial( writer, top_left.x, top_left.y, top_right.x, top_right.y, cb_colors, cb_ratios, true, true );
 var cb_pattern: PdfShadingPattern = new PdfShadingPattern( cb_shading );

 cb.roundRectangle( 0, 0, SPRITE_W, SPRITE_H, 5 );
 cb.setShadingFill( cb_pattern );
 cb.fill();

 cb.restoreState();
 document.close();

 var f: FileReference = new FileReference();
 f.save( buffer, "ExampleColorGradient2.pdf" );
 }

 private function update(): void
 {
 return;
 test_sprite.graphics.clear();
 test_sprite.graphics.beginFill( 0, 0.1 );
 test_sprite.graphics.drawRect( 0, 0, gradient_rect.width, gradient_rect.height );

 var matrix: Matrix = new Matrix();
 matrix.translate( -GRADIENT_W/2, -GRADIENT_H/2 );
 matrix.scale( pair_x, pair_y );

 var box: Rectangle = gradient_rect.clone();

 matrix.concat( gradient_matrix );
 matrix.concat( sprite.transform.matrix );

 test_sprite.transform.matrix = matrix;
 test_sprite.graphics.endFill();
 }

 public static function radians( degree: Number ): Number
 {
 return degree * ( Math.PI / 180 );
 }
 }
}
Share with...