Nov
24
2012

Fin

As is probably obvious, this site rode off into the sunset well over a year ago. I just came back today to realize that I never put the full stop at the end, so here I am. This site began back in the days before blogs, when portfolio sites were all the rage and Flash sites even more so. I used it as a repository for, first, all the experiments I put together as I was learning Flash, then all the professional work, including my books, that I found myself doing. It was fun, and helpful for me to see a record of what I had produced, and it did help get me a number of jobs.

Now, alas, that era is over. I have been happily working at Brightcove for almost eight years now. I no longer do any consulting or writing on the side. Flash is, sadly, on its way out, and my work now isn’t necessarily stuff that is easy to publish here out of context. The site really does not serve the purpose it once did.

I shall keep it around, as it still is a great way for me to revisit my past, as you might go through that old cardboard box in your storage, and, who knows, perhaps one day I will find some impetus to actively write something on a regular basis and so once again open these doors. But for now, it’s closing time. Full stop.

  • Share/Bookmark
0
May
23
2011

Quick Update to Unison

Unison for BEML template creation

In preparation for my presentation for Brightcove PLAY I wanted to do a quick update to my old (nearly three years old now) AIR application named Unison which was created to aid in the creation of BEML templates used in the Brightcove service. The new feature, found under the HELP menu, is an HTML Compatibility Mode which will filter out all components from design view that are not currently supported in the Brightcove Player. It will also remove any components from your current BEML that are not currently supported (creating a BEML snapshot for you before the deletion) and will not surface those components in the code hinting in Design mode.

Hope this proves a useful addition when creating templates that will be both supported in Flash and HTML.

  • Share/Bookmark
0
Feb
22
2011

The Brightcove Accessible Video Player

Late last year I helped to allow the Brightcove player to be 508c compliant. This basically required enhancements in the following areas:

  • Keyboard navigation
  • Closed captions
  • Screenreader integration

For keyboard navigation, many of our UI components were modified to accept keyboard input and we added management for tabbing through multiple screens of data, viewstacks and dynamic controls that could be added/removed through the API and binding.

Closed captions support was something already available in the player through the API, but we expanded the capabilities and built a special plugin that could be loaded into the player to manage requests for DXFP files and population of a drop down menu of captions options based on the file.

Screenreader support enabled the player controls and closed captions to be surfaced to the latest JAWS screenreader. Lots of quirks with integrating with a screenreader for different browsers and OS versions, but that is a topic for another day…

One thing we are always very concerned about at Brightcove when we release anything new in our player is the matter of backwards compatibility, and so for this accessible implementation it was decided to leave our current templates as they were and release a new template that would specifically enable the accessible functionality, and we would also provide controls through BEML that would allow customers to easily create their own accessible implementations.

Now, to be clear, the accessible capabilities are available in ALL Brightcove players, but to enable this functionality you need access to the underlying BEML. In this way, we could provide support in all types of template without forcing the functionality into older templates (with older BEML). For those without access to BEML, we built the Accessible Video Player as a global template that could be accessed by anyone.

I hope to write a future post about how to build your own accessible template through BEML, but for this post I wanted to discuss the issue of Brightcove Express customers, who do not have access to BEML, wanting to deploy fully accessible players. You see, although an Express customer can create instances of the Accessible Video Player and immediately have keyboard navigation and screenreader integration in these players, closed captions is something that requires a bit more work to enable.

BUT IT CAN BE DONE.

Sorry for the extra stress there, but I think this point has been confused since the player’s release, when I have seen people cite the accessible player as only being available to non-Express customers because of the closed captions limitation. Yes, there is a limitation, but it can be overcome, and this template can be used by any customer to get fully accessible functionality.

At this time Brightcove does not ingest or host closed captions files. There is no built in mechanism for associating a captions file with a video. The way the closed captions support for the Accessible Video Player was implemented requires the use of a custom field in the Brightcove Studio to create this association. The closed captions plugin looks to this custom field for the path to the DFXP file for the current video.

However, custom fields is a feature available to non-Express customers only, so an Express customer can not use the closed captions plugin in this way. It is because of this that there has been confusion about accessibility support for Express customers. All it really means, though, is that the default plugin cannot be used. There is no stopping anyone from modifying it, though.

As I mentioned, the plugin looks to the custom field on the video to determine from where to load the DFXP file. By modifying the plugin to use different logic to determine the path you can have a fully accessible player as an Express customer as well.

The source for the plugin is here. The lines that determine the path to the DFXP file are the following:

1
2
3
var video:VideoDTO = mVideoPlayer.getCurrentVideo();
if (video && video.customFields.hasOwnProperty("dfxppath")) {
    mCaptionsModule.loadDFXP(video.customFields.dfxppath, video.id);

Now, what could you change this to? Here are some ideas:

  • Have a server side script that accepts a video ID and redirects to the DFXP file based on this ID. The server side script would change based on desired language and how the file was looked up, but the ActionScript would be as simple as:
1
2
3
var video:VideoDTO = mVideoPlayer.getCurrentVideo();
if (video) {
    mCaptionsModule.loadDFXP("http://domain/script?id=" + video.id, video.id);
  • Store all DFXP files in a known location using a standard naming convention based on the video ID, then you could request the file directly in the following manner:
1
2
3
var video:VideoDTO = mVideoPlayer.getCurrentVideo();
if (video) {
    mCaptionsModule.loadDFXP("http://domain/path/captions" + video.id + ".dfxp", video.id);
  • Put the path to the DFXP file in the player snippet in the following manner:

<param name=”dfxppath” value=”http://domain/path/file.dfxp” />

Then you can access the file path through the player API with:

1
2
3
4
var video:VideoDTO = mVideoPlayer.getCurrentVideo();
var path:String = mExperienceModule.getPlayerParameter("dfxppath");
if (video && path) {
    mCaptionsModule.loadDFXP(path, video.id);

These are just three ideas, but hopefully they demonstrate that with only a slight change to the plugin you can reuse it for however you store your DFXP files, whether you are an Express customer or not, making the Accessible Video Player one that is available to all Brightcove customers.

  • Share/Bookmark
2
Dec
03
2010

Inserting a Featured Video into a Playlist

OK, I’m back after a long hiatus and am going to be (hopefully) posting a number of entries discussing some tricks and features in the Brightcove players that developers might not be aware of. This particular post comes from a customer request for a piece of functionality not built into the players by default.

The issue to address is that a “playlist player” delivered by Brightcove expects all of its content to be within a playlist (makes sense, right?), but sometimes an old link to a playlist player is requesting a video that is no longer a part of the playlist. We’ve had a lot of internal discussions around this, and the current behavior is that a featured video will only appear in the player if it exists in the playlist, as opposed to a) loading the featured video within the video window without giving a way to navigate back to it if a new video is selected, since the video is not the playlist UI, or b) inserting the video into the playlist so that it can be played and navigated to like any other video in the playlist. Although b) might seem an obvious solution (I think a) is just potentially a bad user experience), it was felt that making an assumption that a video should be inserted into a playlist was not a good thing — the playlist might be something completely different and the video being included within it could be anything from a humorous grouping to an offensive inclusion.

However, it has still been a common request from publishers. So here I’ll offer a simple plugin solution that will perform this behavior, checking to see if a featured video was requested via the link and, if it was and the video was not loaded, requesting the video from the backend and inserting it into the loaded playlist. Here is a player that just includes a playlist of my Bobblehead help videos. But the link references the video ID for a logo animation video. The default player behavior would be to ignore this video, but with the plugin the logo animation is prepended to the playlist.

Featured Video Plugin Test

First off, you will want to download the Player API SWC if you don’t have a copy. Then fire up Flash Builder or whatever IDE you use and start a new ActionScript project, making sure to include the API SWC. Alter the main application to extend CustomModule as opposed to Sprite, and you’ll start with something like this:

1
2
3
4
5
6
7
8
9
package {

    import com.brightcove.api.CustomModule;

    public class FeaturedVideoPlugin extends CustomModule {

    }

}

The first thing to do in the class, just as whenever using CustomModule, is to override the protected inititalize() method. Within this we will first check to see if we have either a List or a TileList to deal with, and exit if not. Then we can use getPlayerParameter() to see if a video id was included with the link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override protected function initialize():void {
    _experienceModule = player.getModule(APIModules.EXPERIENCE) as ExperienceModule;

    // first, see if we have a List component
    _list = _experienceModule.getElementByID("videoList") as List;
    // if not, try a TileList component
    if (!_list) {
        _list = _experienceModule.getElementByID("videoList") as TileList;
    }
    // no list, so nothing to do
    if (!_list) return;

    // check to see if a video was requested in the link
    var featuredVideo:String = _experienceModule.getPlayerParameter("@videoPlayer");
    // no video, so nothing to do
    if (!featuredVideo) return;
    // more to come...
}

If a featured video is found, we can go through the data in the list, using the ListWidget’s getData() and see if the video can be found, by either the id or referenceID, depending on what was included in the link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// see if we have a referenceID or a a regular id
var referenceID:String;
var id:Number;
if (featuredVideo.substr(0, 4) == "ref:") {
    referenceID = featuredVideo.substr(4);
} else {
    id = Number(featuredVideo);
    if (isNaN(id)) {
        referenceID = featuredVideo;
    }
}
var useID:Boolean = !isNaN(id);

// now go through the videos to see if the featured video was included
_videos = _list.getData();
for each (var video:VideoDTO in _videos) {
    if (useID) {
        if (video.id == id) {
            return;
        }
    } else {
        if (video.referenceId == referenceID) {
            return;
        }
    }
}

// more to come...

If the video is not found in the list, we must request it through the ContentModule. Also, we’ll set up a listener to stop the video that was loaded on player load.

1
2
3
4
5
6
7
8
9
10
11
12
13
// the video player will start playing the first in the list,
// so make sure to stop the video if that happens
_videoPlayer = player.getModule(APIModules.VIDEO_PLAYER) as VideoPlayerModule;
_videoPlayer.addEventListener(MediaEvent.BEGIN, onMediaBegin);

// now request the featured video and wait for the response
_contentModule = player.getModule(APIModules.CONTENT) as ContentModule;
_contentModule.addEventListener(ContentEvent.MEDIA_LOAD, onMediaLoad);
if (useID) {
    _contentModule.getMediaAsynch(id, "id");
} else {
    _contentModule.getMediaAsynch(referenceID, "referenceId");
}

The two handlers are pretty simple. onMediaBegin() stops playback of the video that was loaded with the player. onMediaLoad() inserts the featured video, once loaded, at the start of the list’s array of videos, then passes that data back to the list for rendering.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Pauses the video if it starts before featured video is returned.
*/

private function onMediaBegin(event:MediaEvent):void {
    _videoPlayer.removeEventListener(MediaEvent.BEGIN, onMediaBegin);
    _videoPlayer.pause(true);
}

/**
* Adds featured video to playlist.
*/

private function onMediaLoad(event:ContentEvent):void {
    _videoPlayer.removeEventListener(MediaEvent.BEGIN, onMediaBegin);
    _contentModule.removeEventListener(ContentEvent.MEDIA_LOAD, onMediaLoad);

    // if we have a video, add it to the beginning of the playlist
    var video:VideoDTO = event.media as VideoDTO;
    if (video) {
        _videos.unshift(video);
        _list.setData(_videos);
        _list.setSelectedIndex(0);
    }
}

Here is the entirety of the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package {

    import com.brightcove.api.APIModules;
    import com.brightcove.api.CustomModule;
    import com.brightcove.api.components.List;
    import com.brightcove.api.components.ListWidget;
    import com.brightcove.api.components.TileList;
    import com.brightcove.api.dtos.VideoDTO;
    import com.brightcove.api.events.ContentEvent;
    import com.brightcove.api.events.MediaEvent;
    import com.brightcove.api.modules.ContentModule;
    import com.brightcove.api.modules.ExperienceModule;
    import com.brightcove.api.modules.VideoPlayerModule;

    public class FeaturedVideoPlugin extends CustomModule {

        private var _experienceModule:ExperienceModule;
        private var _contentModule:ContentModule;
        private var _videoPlayer:VideoPlayerModule;
        private var _list:ListWidget;
        private var _videos:Array;
       
        override protected function initialize():void {
            _experienceModule = player.getModule(APIModules.EXPERIENCE) as ExperienceModule;

            // first, see if we have a List component
            _list = _experienceModule.getElementByID("videoList") as List;
            // if not, try a TileList component
            if (!_list) {
                _list = _experienceModule.getElementByID("videoList") as TileList;
            }
            // no list, so nothing to do
            if (!_list) return;

            // check to see if a video was requested in the link
            var featuredVideo:String = _experienceModule.getPlayerParameter("@videoPlayer");
            // no video, so nothing to do
            if (!featuredVideo) return;

            // see if we have a referenceID or a a regular id
            var referenceID:String;
            var id:Number;
            if (featuredVideo.substr(0, 4) == "ref:") {
                referenceID = featuredVideo.substr(4);
            } else {
                id = Number(featuredVideo);
                if (isNaN(id)) {
                    referenceID = featuredVideo;
                }
            }
            var useID:Boolean = !isNaN(id);

            // now go through the videos to see if the featured video was included
            _videos = _list.getData();
            for each (var video:VideoDTO in _videos) {
                if (useID) {
                    if (video.id == id) {
                        return;
                    }
                } else {
                    if (video.referenceId == referenceID) {
                        return;
                    }
                }
            }

            // the video player will start playing the first in the list,
            // so make sure to stop the video if that happens
            _videoPlayer = player.getModule(APIModules.VIDEO_PLAYER) as VideoPlayerModule;
            _videoPlayer.addEventListener(MediaEvent.BEGIN, onMediaBegin);

            // now request the featured video and wait for the response
            _contentModule = player.getModule(APIModules.CONTENT) as ContentModule;
            _contentModule.addEventListener(ContentEvent.MEDIA_LOAD, onMediaLoad);
            if (useID) {
                _contentModule.getMediaAsynch(id, "id");
            } else {
                _contentModule.getMediaAsynch(referenceID, "referenceId");
            }
        }
       
        /**
        * Pauses the video if it starts before featured video is returned.
        */

        private function onMediaBegin(event:MediaEvent):void {
            _videoPlayer.removeEventListener(MediaEvent.BEGIN, onMediaBegin);
            _videoPlayer.pause(true);
        }

        /**
        * Adds featured video to playlist.
        */

        private function onMediaLoad(event:ContentEvent):void {
            _videoPlayer.removeEventListener(MediaEvent.BEGIN, onMediaBegin);
            _contentModule.removeEventListener(ContentEvent.MEDIA_LOAD, onMediaLoad);

            // if we have a video, add it to the beginning of the playlist
            var video:VideoDTO = event.media as VideoDTO;
            if (video) {
                _videos.unshift(video);
                _list.setData(_videos);
                _list.setSelectedIndex(0);
            }
        }
       
    }

}

Once it is compiled, you can just include it as a plugin through the player settings in the Publishing module of the studio. I’ve also included a zip here of the whole project which includes the compiled SWF. If you don’t need to tweak it, all you have to do is host it and include it as a plugin for your player.

  • Share/Bookmark
0
May
14
2010

Been Busy in the City of Angels

I swore I’d do better at this whole blog thing, and yet here it is after several months non-posting. In fact I just approved a comment that was posted a while ago that I hadn’t got around to (sorry! I will answer!). I must commit to trying to submit a post a week here for a little while just to get into a good habit. I have a nice list of ideas for posts I’ve wanted to do for a while, some concerning Brightcove, some concerning the aether and aeon libraries and my book.

So why have I been lax? Other than the new baby and the fulltime job (no excuse, I know), I went from Blithe Spirit, as mentioned in my last post, to working on City of Angels at Longwood Players in Cambridge. It’s a show I saw back when I was in high school that I’ve always wanted to do, and here it was. So I did it, hour+ commute and all, and damn it was worth it, even if I couldn’t hit all the notes like I could back in high school :).

I did at least do a little work on the computer that I can post here without turning this into a theater blog. We had a publicity photo shoot at one point and soon after I had a night where I just couldn’t get to sleep, so just for kicks I hopped on the computer and turned one of the photos into a strip of film from an old film noir (if you know the show, it makes sense), dropping the characters into a rendered 3D detective’s office, making it b&w and grainy, etc. So the producers liked it and asked to use it in some marketing material and I offered to do anything else that might come up.

What came up was the task of putting one of our actors, Kevin Cirone into some publicity shots with famous Hollywood figures from the 30’s and 40’s. I was directed to some photos online and found some others, and this is what I came up with. Note that these photos were just intended as stage decoration on an office set, so they were more for our enjoyment than for the audience that generally probably couldn’t make them out. I spent maybe a half hour on each, not worrying too much about how integrated the images were beyond what would fool an audience a good distance away (not those looking closely online).

The first here is a shot of Audrey Hepburn and Cary Grant. Kevin’s photo of course is in the middle, with the final composite at the bottom. He just looks so damned pleased, and I love it.

Here was a shot of Cecil B. Demille. It was so dynamic that I had to do it, even if Kevin doesn’t actually appear with anyone famous in the end. Had to add some extra lighting to his face though to try and get the light and shadow angles to match.

Finally, and probably my favorite, is a shot of Louis B. Mayer with Garland and Rooney. The subtext here is that Kevin’s character Buddy (a studio mogul) just told the dirtiest joke to these couple of teenagers. You can just see it. The integration on this one was interesting since I didn’t really have the right angle of a headshot. In the end I had to lower Mayer’s whole body to make it look fairly plausible.

Image manipulation fits under the umbrella of this blog, yes? So we’re still good, even after a several month hiatus. More non-theater stuff is coming, though!

  • Share/Bookmark
1
Jan
21
2010

Blithe Spirit

Here’s just a quick post that’s nothing to do with ActionScript, nothing to do with Brightcove, but to let anyone in the Boston/Worcester/Providence area that I will be performing next month, along with my wife, in Blithe Spirit in Worcester at WCLOC. I was an actor in a past life and have been getting active on the stage again over the past two years, and this is my latest endeavor.

Blithe Spirit is a Noël Coward comedy from the 40’s about an author and his wife who, through an unfortunate séance, summon the ghost of his first wife who then schemes to kill the author in an attempt to reunite with him on the Other Side. It’s fast-paced, witty, and I get to say things like “Surely even an ectoplasmic manifestation has the right to expect a little of the milk of human kindness” with a straight face and heightened British dialect. Fun.

I also created the poster and ads for the production, which was a challenging feat to say the least to get so much info into each of the form factors and still provide a strong central image, but I’m quite proud of the result:

  • Share/Bookmark
0
Jan
08
2010

Pixel Bender Quirks

I just wanted to quickly document several issues I’ve had so far when developing with Pixel Bender in case anyone else runs into similar issues.

  • Reapplying an altered shader as a blend mode does not update a display object. The blend modes I was working with all had a percent parameter. If I changed the percent, then applied the altered shader to the blendShader property of the display object, nothing would happen. The way I could get it to update (I’m sure there are other ways) was to set the blend mode to “normal”, then assign the shader to the blendShader. Only then would the display object update.
  • When developing blend modes, order of the input declarations is important. This might seem obvious, but caught me up initially mostly because I started with blend modes that didn’t care about order, like add or multiply or difference. But when you are coding a blend mode where the order is important, remember that the first input declaration will be filled by the background pixels below your display object while the second declaration will be filled by the pixels of the display object you are applying the blend mode to.
  • For instance, with the following code it should be obvious which input will take the background pixels and which will take the foreground, or source display object:

    1
    2
    input image4 background;
    input image4 source;

    However, the names of course make no difference since you can name them whatever you like. Just don’t make the mistake of reversing the names:

    1
    2
    input image4 source;
    input image4 background;

    Despite the names, the “source” input here will still be filled with the background pixels below your display object while the “background” input will be filled with the pixels from the display object. So be careful!

  • normalize() bug? This is one of the two cases I’ve found where a function that worked fine within the Pixel Bender Toolkit didn’t work in the Flash Player runtime. This is so frustrating since everything appears fine when developing the kernel, and there’s no great debugging way to know why it fails in the Flash Player.
  • As an example, this worked for me in the Toolkit, but failed in the Flash Player:

    1
    2
    float3 p = float3(relativePos.x, relativePos.y, -z);
    p = normalize(p);

    This code, which performs the normalization, worked:

    1
    2
    3
    float3 p = float3(relativePos.x, relativePos.y, -z);
    float pLength = length(p);
    p = float3(p.x/pLength, p.y/pLength, p.z/pLength);

    Huh.

  • Nested min()/max() calls produce erroneous results. This is the other of the two cases when something worked fine in the Toolkit but not the Flash Player. When I had a max() call within another max() call (and the same with min()) I got bad data, but putting them in separate statements resolved the issue.
  • Broken:

    1
    float saturation = max(px.r, max(px.g, px.b)) - min(px.r, min(px.g, px.b));

    Works:

    1
    2
    3
    4
    5
    float maxSat = max(px.r, px.g);
    maxSat = max(maxSat, px.b);
    float minSat = min(px.r, px.g);
    minSat = min(minSat, px.b);
    float saturation = maxSat - minSat;

    Maybe I’m missing something in these broken examples, but the frustration was mostly in the fact that it all worked fine in the Toolkit, but produced a nice black image in the Flash Player. It would have been nice to at least have it broken in both places. :)

    • Share/Bookmark
    3
    Jan
    04
    2010

    Pixel Bender Blend Modes

    In my ActionScript 3.0 Image Effects book I have a chapter in which I discuss Pixel Bender and demonstrate how to create a blending mode for the Flash Player. I also suggest the possibility of rewriting the standard Flash Player blend modes so that a percent parameter might be supported, as you find in Photoshop. Then you could apply a multiply blend mode at 50% as opposed to always 100%. In a previous chapter in the book I present the blending mode formulas to make doing that easier.

    So I finally took my own suggestion :) I have written Pixel Bender shaders not just for the standard Flash Player blend modes (multiply, screen, overlay…), but for the standard Photoshop blend modes as well (color dodge, vivid light, hue…). Here’s an example of them at work:

    Pixel Bender blend modes in Flash Player

    I incorporated the blend modes into the aether effects library. To apply one of these blend modes at runtime, you need to either embed the shader byte code in the SWF or load in the shader at runtime. Embedding would look like this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package {

        import aether.blendModes.HardMixBlendMode;
        import aether.blendModes.ShaderBlendMode;

        import flash.display.Bitmap;
        import flash.display.Sprite;

        [SWF(width=400, height=602)]

        public class EmbeddingBlendMode extends Sprite {

            [Embed(source='/assets/hardMix.pbj', mimeType='application/octet-stream')]
            private static const HardMixKernel:Class;

            [Embed(source='/assets/hydrant.jpg')]
            private static const ForegroundImage:Class;

            [Embed(source='/assets/fungus.jpg')]
            private static const BackgroundImage:Class;

            public function EmbeddingBlendMode() {
                var backgroundImage:Bitmap = new BackgroundImage() as Bitmap;
                addChild(backgroundImage);

                var foregroundImage:Bitmap = new ForegroundImage() as Bitmap;
                addChild(foregroundImage);

                ShaderBlendMode.setShaderClassPath(this);
                foregroundImage.blendShader = new HardMixBlendMode(.5).shader;
            }

        }

    }

    In this case, the shader byte code is embedded into this class, and so I need to tell the ShaderBlendMode class where to find the shader. This is done through the static setShaderClassPath() method. All blend mode shaders will then look to this class for the byte code. By default, each BlendModeShader child class has an expected name of the shader that is assigned to a static shaderClass property. For HardMixBlendMode, it is “HardMixKernel”. For AddBlendMode it is “AddKernel”. Etc. So here in this class I embed the byte code with the class name HardMixKernel. If I wanted to use a different name, I could assign a new value to HardMixBlendMode.shaderClass.

    Finally, I need to assign the actual Shader instance to the blendShader property of the bitmap, and this can be accessed through the shader property of a BlendModeShader instance. Since I am not looking to alter the 50% hard mix blend, I do not even need to save a reference to the blend mode shader, I just assign it directly to the bitmap.

    If you are not embedding the byte code, you will need to load it at runtime. The following code does that (though I still embed the images just to save space here to focus on the blend mode code — if you are not using the mxmlc compiler obviously you will need to load the images or use another type of display object).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package {

        import aether.blendModes.HardMixBlendMode;
        import aether.blendModes.ShaderBlendMode;

        import flash.display.Bitmap;
        import flash.display.Sprite;

        [SWF(width=400, height=602)]

        public class LoadingBlendMode extends Sprite {

            [Embed(source='/assets/hydrant.jpg')]
            private static const ForegroundImage:Class;

            [Embed(source='/assets/fungus.jpg')]
            private static const BackgroundImage:Class;

            private var _blendMode:ShaderBlendMode;

            public function LoadingBlendMode() {
                var backgroundImage:Bitmap = new BackgroundImage() as Bitmap;
                addChild(backgroundImage);

                var foregroundImage:Bitmap = new ForegroundImage() as Bitmap;
                addChild(foregroundImage);

                ShaderBlendMode.shaderFilePath = "/pbjs/";
                _blendMode = new HardMixBlendMode(.5, foregroundImage);
            }

        }

    }

    In this case I must specify through the static shaderFilePath where the shader will be loaded from. Just as with the shaderClass property, there is a shaderFile static property of each ShaderBlendMode that determines the name of the shader file to load. By default, the HardMixBlendMode uses “hardMix.pbj”. The MultiplyBlendMode uses “multiply.pbj”. I hope the pattern is obvious :). If you want to rename the shader file, you simply have to update the shaderFile value for the associated class.

    One of the cool things about these blend modes is that if you assign a display object as the second parameter, as I did in this example, then the shader will reapply itself to the display object if its percentage ever changes (by default in the Flash Player, a shader is copied to the display object, there is no object reference that remains). So if you wanted to change the percentage of the blend mode applied, you would simply need to have:

    1
    _blendMode.percent = 0.8;

    The display object would then be automatically updated with the new shader value.

    To use these shaders, simply grab the aether effects library. There is a precompiled SWC as well as the original source for both the ActionScript and the Pixel Bender shaders. There are no dependencies in the shader blend modes on other pieces of the aether library, so if all you want is to use a blend mode you don’t get any excess baggage along with it.

    • Share/Bookmark
    13
    Dec
    27
    2009

    Unison for BEML editing

    A while back I worked on a prototype for an AIR WYSIWYG BEML editor (how’s that for acronym’s at work?). For those who develop for, or are, Brightcove clients, you will know that BEML stands for Brightcove Experience Markup Language, a simple tag based markup language based on XUL and containing a lot of elements familiar to MXML users.

    Well, the prototype was a bit of a hit and miss. It was a hit because I liked to use it to develop and test with and I found it very useful (hit!), but a miss in that, like many WYSIWYG editors it surfaced what was extremely difficult to do easily or right in such an application, and nothing came of it (miss!). But I still felt that it might be a useful tool, warts and all, for those working with BEML to experiment and play with the language.

    So I tweaked a few things, cleaned up a few others, added some spit and polish and a few extra features that creeped in towards the end, and now am presenting Unison:

    Unison application for BEML editing

    You can use the browser-based version, or download and install the AIR app if you want to export images. Some of the features include:

    • Drag and drop design view
    • Layout tree view of BEML
    • Properties panel for assigning attributes
    • Styles panel for modifying colors through color pickers or CSS
    • Snapshots panel to save versions of BEML
    • Code view with code hints and code completion

    If you have any issues or find bugs, please let me know (consider it beta, optimistically). If you have ideas or requests for additional features, I’d love to hear them (of course, I already have a list that would keep me busy till next Christmas).

    I hope it proves as useful to others and it has for me!

    • Share/Bookmark
    6
    Dec
    23
    2009

    AIRHead == 27Bobs BobbleHead

    I decided to put AIRHead up on the Adobe AIR Marketplace since probably about 5 people read this blog (thanks, you five! :)) and saw under Adobe’s terms and conditions that including AIR in the app name was a big no-no. Unfortunately, my creativity lasted for just the one title, so after trying some lame attempts to mash “27Bobs” with “bobblehead” (27Bobblehead?) I decided to just go for the obvious and call the offering “27Bobs BobbleHead”. Anyway, here ’tis:

    27Bobs BobbleHead

    • Share/Bookmark
    2