website | twitter

Sunday, April 05, 2009

Learning Erlang and Adobe Flash format same time part 2

I have already written most important part about SWF format and Erlang's bit syntax at previous post. But just for the record, I take a note of miscellaneous knowledge to construct an interesting Flash shape. This entire note doesn't have any practical value because there are already many other useful libraries to build a swf file. But some of historical esoteric magical peculiar internal might interests you.

In the previous example, I only made three control tags:

  • End (Tag type = 0)
  • ShowFrame (Tag type = 1)
  • SetBackgroundColor (Tag type = 9)

Here, I introduce you two additional tags,

  • DefineShape (Tag type = 2) defines a vector graphics, and
  • PlaceObject2 (Tag type = 26) shows the graphics to an actual screen.

I designed new data structure to represent a SWF shape. While spec/0 is almost same as previous program, I add sample_shape/0 to define DefineShape.

% an example shape.
spec() ->
    {{frame_size, 6000, 3000},
     {frame_rate, 16},
     {frame_count, 160},
     [{set_background_color, {rgb, 240, 250, 250}},
      sample_shape(),
      {place_object2, 1},
      {show_frame},
      {end_tag}]}.

sample_shape() ->
    {define_shape,
     {shape_id, 1},
     {bounds, {rectangle, 2000, 3000, 1000, 2000}},
     {shapes, {{fills, [{solid_fill, {rgb, 50, 200, 50}}]},
        {lines, [{line_style, 100, {rgb, 100, 255, 100}},
   {line_style, 100, {rgb, 50, 150, 50}}]},
        {shape_records,
  [{style_change, [{move_to, 2000, 2000}]},
   {style_change, [{line_style, 1},
     {fill_style0, 1}]},
   {straight_edge, 0, -1000},
   {straight_edge, 1000, 0},
   {style_change, [{line_style, 2},
     {fill_style0, 1}]},
   {straight_edge, 0, 1000},
   {straight_edge, -1000, 0},
   {end_shape}]
        }}}}.

It looks complicated though, it is actually very straightforward. A DefineShape has a shape ID (or character ID), its bounding box, and actual shapes. "shape_id", "bounds", and "shapes" tuples at sample_shape/0 denote these information. Shape ID is used later for a reference by PlaceObject2.

A Shapes part consists of further sub elements, fills, lines and shape records. Every style information are defined in advance, and referenced with corresponding ID. In this case, one solid fill and two line styles are defined. After styles are defined, shape records are followed. A shape record is the most interesting and complicated part. I'll draw a moss green rectangle in 1000 x 1000 twips (20 twips = 1 pixel) with light and dark green borders in a pseudo 3-D style.

This is an actual implementation of DefineShape and PlaceObject2. I'm afraid it seems to be complicated, but it is just a direct translation from the specification. Thanks to Erlang's pattern matching syntax, It is easy to add a new tag by putting a new pattern. But sometimes distinction between ; (semi colon is used between patterns in a function) and . (dot is used after a function definition) annoys me.

tag({define_shape,
     {shape_id, ShapeId},
     {bounds, Bounds},
     {shapes, ShapesWithStyle}}) ->
    BoundsBits = rectangle(Bounds),
    Shapes = shape_with_style(ShapesWithStyle),
    record_header_body(2, list_to_binary([<<ShapeId:16/unsigned-little>>, BoundsBits, Shapes]));

tag({place_object2, CharacterId}) -> 
    PlaceFlagHasClipActions = 0,
    PlaceFlagHasClipDepth = 0,
    PlaceFlagHasName = 0,
    PlaceFlagHasRatio = 0,
    PlaceFlagHasColorTransform = 0,
    PlaceFlagHasMatrix = 0,
    PlaceFlagHasCharacter = 1,
    PlaceFlagMove = 0,
    Depth = 1,
    record_header_body(26, <<PlaceFlagHasClipActions:1,
       PlaceFlagHasClipDepth:1,
       PlaceFlagHasName:1,
       PlaceFlagHasRatio:1,
       PlaceFlagHasColorTransform:1,
       PlaceFlagHasMatrix:1,
       PlaceFlagHasCharacter:1,
       PlaceFlagMove:1,
       Depth:16/unsigned-little,
       CharacterId:16/unsigned-little>>).

shape_with_style/1 is used to construct SHAPEWITHSTYLE structure in the specification.

shape_with_style({{fills, Fills}, {lines, Lines}, {shape_records, Shapes}}) ->
    FillBits = nbits_unsigned([length(Fills)]),
    LineBits = nbits_unsigned([length(Lines)]),
    ShapeRecords = shape_records({shape_records, Shapes}, FillBits, LineBits),
    [fill_style_array({fills, Fills}),
     line_style_array({lines, Lines}),
     <<FillBits:4,
      LineBits:4>>,
     ShapeRecords].

The tricky part is FillBits and LineBits. As I have already mentioned when explaining a rectangle format in previous post, the SWF format tries to shorten a file size by all means, bit by bit. FillBits defines maximum bit size needed by fill style id. For example, if you use only one fill style, one is enough for FillBits. But if you use seven, FillBits is three (because seven is 111 as a binary). After FillBits and LineBits are calculated, they are needed in next helper function shape_records/3. But before I show you shape_records/3, some simple functions to construct styles are followed.

fill_style_array({fills, FillStyleArray}) ->
    FillStyleCount = length(FillStyleArray),
    [<<FillStyleCount:8>>, [fill_style(X) || X <- FillStyleArray]].

fill_style({solid_fill, RGB}) -> [<<16#00:8>>, rgb(RGB)].

line_style_array({lines, LineStyleArray}) ->
    LineStyleCount = length(LineStyleArray),
    [<<LineStyleCount:8>>, [line_style(X) || X <- LineStyleArray]].

line_style({line_style, Width, RGB}) ->
    [<<Width:16/unsigned-little>>, rgb(RGB)].

I think these are straightforward. Perhaps, next is the most tricky part of the SWF shape. Shape records include a number of records, each record represents style change or position change or so. While a rectangle format must to be byte aligned, each record must not be aligned, but whole shape records including them must be aligned! This fact is described in the specification very obscure way, so I didn't find out what to do unless many try-and-errors. Although, I might misunderstood what is a shape record...

Each individual shape record is byte-aligned within an array of shape records; one shape record is padded to a byte boundary before the next shape record begins. --- SWF File Format Specification Version 10

To concatenate many bit strings into one, and align whole bits, I made two functions padding/1 and concat_bits/1 (padding/1 is same one as previous post). It is likely that concatenating bitstrings exists already, but I couldn't find out from the library.

shape_records({shape_records, ShapeRecords}, FillBits, LineBits) ->
    padding(concat_bit([shape_record(X, FillBits, LineBits) || X <- ShapeRecords])).

padding(Bits) ->
    Padding = 8 - bit_size(Bits) rem 8,
    <<Bits/bitstring, 0:Padding>>.

concat_bit(XS) -> lists:foldl(fun(X, Y) -> <<Y/bitstring, X/bitstring>> end, <<>>, XS).
Rest of the source is definitions of each record. I omitted a lot of features to the explanation purpose. EndShapeRecord and StraightEdgeRecord are quite simple.
shape_record({end_shape}, _, _) -> <<0:1, 0:5>>;

shape_record({straight_edge, DeltaX, DeltaY}, _, _) ->
    NumBits = nbits_signed([DeltaX, DeltaY]),
    GeneralLineFlag = 1,
    <<1:1, 1:1, (NumBits - 2):4, GeneralLineFlag:1, DeltaX:NumBits, DeltaY:NumBits>>;

But StyleChangeRecord is the second tricky and daunting part in spite of Erlang's succinct syntax. A part of reasons of the complexity is that StyleChangeRecord allows to define a couple of operations at once optionally; line style, fill style, and pen move. So we need some data structure to represent such option.

In object oriented languages, a dictionary or a map is commonly used while an associated list is popular in functional languages. In Erlang, a list of tuples might be enough. lists:keysearch/3 find a tuple in a list where first argument is matched, so we can use it as a dictionary like structure. In our example, [{line_style, 1}, {fill_style0, 1}] means we want to change line style and fill style same time. and lists:keysearch(line_style, 1, ChangeSpec) detects a line style change option.

shape_record({style_change, ChangeSpec}, FillBits, LineBits) ->
    TypeFlag = 0,
    StateNewStyles = 0,
    StateFillStyle1 = 0,

    {StateLineStyle, LineStyleData} =
 case lists:keysearch(line_style, 1, ChangeSpec) of
     {value, {_, LineStyle}} -> {1, <<LineStyle:LineBits>>};
     false -> {0, <<>>} end,

    {StateFillStyle0, FillStyle0Data} =
 case lists:keysearch(fill_style0, 1, ChangeSpec) of
     {value, {_, FillStyle0}} -> {1, <<FillStyle0:FillBits>>};
     false -> {0, <<>>} end,

    {StateMoveTo, MoveData} =
 case lists:keysearch(move_to, 1, ChangeSpec) of
     {value, {_, MoveDeltaX, MoveDeltaY}} ->
  MoveBits = nbits_signed([MoveDeltaX, MoveDeltaY]),
  {1, <<MoveBits:5,
       MoveDeltaX:MoveBits/signed,
       MoveDeltaY:MoveBits/signed>>};
     false -> {0, <<>>} end,

    <<TypeFlag:1,
     StateNewStyles:1,
     StateLineStyle:1,
     StateFillStyle1:1,
     StateFillStyle0:1,
     StateMoveTo:1,
     MoveData/bitstring,
     FillStyle0Data/bitstring,
     LineStyleData/bitstring>>.
I uploaded final program http://languagegame.org/pub/swf.erl.

3 comments:

  1. Hello.

    Have you seen eswf project? iirc it deals with the same stuff you do. Here's the link http://code.google.com/p/eswf/

    Best,
    Gleb Peregud

    ReplyDelete
  2. Hi Gleber,

    I didn't imagine that anyone tried to handle swf in Erlang. This is very helpful link. Thank you!

    ReplyDelete
  3. Digital Image Converter, fast and reliable app to convert units like: twip, centimeter (cm), millimeter (mm), character (X), character (Y), pixel, inch (in), pica (computer), pica (printer's), PostScript, point (computer) , point (printer's ), in.

    https://play.google.com/store/apps/details?id=com.anazco.juan.digitalimageconverterunits&hl=en

    ReplyDelete

 
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 Unported License.