Monday, July 27, 2009

SVG dimensions & whitespace

Perhaps this is documented somewhere, but I haven't found it.

Let's say you have an SVG image called "whitespacetest.svg" that's 200 by 50 pixels. However, let's say that it only contains a circle that's 50 by 50 on the far right, so there's 150 pixels of whitespace on the left side of the image.

What's the width of the image? According to Flex, it's 50. However, when it goes to draw the image, it will use the full width of the SVG.

Here's the test code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
[Embed(source="whitespaceTest.svg")]
[Bindable]
public var whitespaceTest;
]]>
</mx:Script>

<mx:VBox>
<mx:HBox width="200" height="10" backgroundColor="#ff0000"/>
<mx:Image id="testImage" source="{embed}"/>
<mx:Label text="{testImage.width}"/>
</mx:VBox>
</mx:Application>


This will display 50 for the width, but the circle will be flush with the right edge of the colored box.

My takeaway is that if you need to know the width of an SVG, don't use whitespace in it. Kind of sucky.

Tuesday, July 21, 2009

Minimum size on a scrollbar thumb, using a ProgrammaticSkin

I had to make a programmatic skin for a scroll bar. I don't think this is something most people should do, frankly. Graphic skinning is much easier and more flexible, and allows programmers to program and artists to...well, art. Why is there no verb for making art, I wonder? But I digress.

Anyway, this was a special case. I needed complete control over the colors within the scrollbar, and couldn't use a ColorFilter, as I was under a ColorFilterInjunctivitis jinx brought on by re-reading Harry Potter way too many times or something.

So anyway, here's the problem: there doesn't seem to be any way to set the minimum size of a scrollbar thumb through CSS. Or if there is, I can't find it. It looks to be fairly hard-coded in ScrollBar.as.

Here's my solution. I can't say I'm proud, but the damn thing works. Even if it is a butt-ugly solution, my hope here is that my blog will one day get famous, I'll be acknowledged as an expert in the field, and then this solution will be regarded as the "official workaround" and not an egregious hack.


public class VScrollBarSkin extends ProgrammaticSkin
{
private static const THUMB_MIN_HEIGHT:Number = 20;
override public function get measuredHeight():Number
{
// way to style this, it's not at all obvious.
if (parent && (parent is ScrollThumb))
ScrollThumb(parent).minHeight = THUMB_MIN_HEIGHT;

return SKIN_HEIGHT;
}

Revision to calculateDerivedColor()

First, an explanation. What I'm trying to do is answer the question: what color is to blue as green is to beige? It's kind of a silly question, but it's very useful for recoloring an entire theme.

Without further ado, here's the code:


public class ColorUtils
{
/**
* Returns result, where a is to result as b is to c. The parameters and result are between 0 and 1.
*
* This is somewhat ambiguous. When b is 0.5 and c is 1, should this return a * 2, or a + 0.5?
* There's no simple answer, so use both approaches and return the average.
*/
private static function applyDifference(a:Number, b:Number, c:Number):Number
{
var linearAdjusted:Number = a + c - b;
var multiplierAdjusted:Number;
if (b == 0)
multiplierAdjusted = 2;
else
{
multiplierAdjusted = a * c / b;
multiplierAdjusted = Math.min(multiplierAdjusted, 2);
}
a = (linearAdjusted + multiplierAdjusted) / 2;
return Math.max(0, Math.min(a, 1));
}


/**
* Return a color derived from baseColor using sampleStartColor and sampleEndColor as a guide.
* For example, this function would return dark blue if baseColor was light blue, sampleStartColor
* was pink and sampleEndColor was dark red.
*
* It's like an SAT question: red is to x as dark green is to greenish gray. What's x?
*/
public static function calculateDerivedColor(baseColor:uint, sampleStartColor:uint, sampleEndColor:uint):uint
{
var baseColorHSL:HslColor = new HslColor(baseColor);

var sampleStartColorHSL:HslColor = new HslColor(sampleStartColor);
var sampleEndColorHSL:HslColor = new HslColor(sampleEndColor);

// rotate the hue and offset the saturation and lightness based on the inputs
baseColorHSL.h = (baseColorHSL.h + sampleEndColorHSL.h - sampleStartColorHSL.h + 360) % 360;

baseColorHSL.s = applyDifference(baseColorHSL.s, sampleStartColorHSL.s, sampleEndColorHSL.s);
baseColorHSL.l = applyDifference(baseColorHSL.l, sampleStartColorHSL.l, sampleEndColorHSL.l);

return baseColorHSL.rgb;
}

Wednesday, July 15, 2009

Color manipulation in Flex (HSL class +)

Flex has some great color manipulation tools built in. The ColorMatrixFilter class is amazing, particularly when paired with the ColorMatrix class found here:
http://www.quasimondo.com/archives/000599.php

However, for my current project, I've also needed the ability to recolor specific RGB values. The reason is simple: I'm not necessarily using a filter, but am in some cases changing CSS values on the fly. Let's say the DataGrid is defined like so in CSS:


DataGrid {
borderColor:#1b3244;
rollOverColor: #3c5c80;
selectionColor: #bacfe7;
...


Now let's say I want to change borderColor in the DataGrid to an arbitrary color and have all the other colors change along with it, but maintain their relationships to the first color (e.g., rollOverColor is somewhat lighter, selectionColor is much lighter), et cetera.

To solve this problem, I made two classes, the first based on the great C# project found here:
http://www.codeproject.com/KB/recipes/colorspace1.aspx


public class HslColor
{
/**
* adapted from:
* http://www.codeproject.com/KB/recipes/colorspace1.aspx
*
* A simple class to convert between RGB and HSL values. This can be constructed with a RGB value,
* HSL values may be extracted and manipulated, and the result can be returned as a RGB value.
*/

/**
* 0 to 359
*/
public var h:uint;

/**
* 0 to 1
*/
public var s:Number;

/**
* 0 to 1
*/
public var l:Number;

/**
* Construct a HslColor from a rgb value. The h, s, l values can be manipulated, and the rgb
* value can be then retreived using getRgbColor().
*/
public function HslColor(color:uint)
{
rgb = color;
}

static public function fromString(color:String):HslColor
{
return new HslColor(uint(color.replace("#", "0x")));
}

static public function toRGB(h:Number, s:Number, l:Number):uint
{
var hslColor:HslColor = new HslColor(0);
hslColor.h = h;
hslColor.s = s;
hslColor.l = l;
return hslColor.rgb;
}

public function get rgb():uint
{
var r:uint =0;
var g:uint = 0;
var b:uint = 0;

if(s == 0)
{
r = g = b = l * 255;
}
else
{
var q:Number = (l<0.5)?(l * (1.0+s)):(l+s - (l*s));
var p:Number = (2.0 * l) - q;

var Hk:Number = h/360.0;
var T:Array = new Array(3);
T[0] = Hk + (1.0/3.0); // Tr
T[1] = Hk; // Tb
T[2] = Hk - (1.0/3.0); // Tg

for(var i:int = 0; i < 3; i++)
{
if(T[i] < 0) T[i] += 1.0;
if(T[i] > 1) T[i] -= 1.0;

if((T[i]*6) < 1)
{
T[i] = p + ((q-p)*6.0*T[i]);
}
else if((T[i]*2.0) < 1)
{
T[i] = q;
}
else if((T[i]*3.0) < 2)
{
T[i] = p + (q-p) * ((2.0/3.0) - T[i]) * 6.0;
}
else T[i] = p;
}

r = uint(255 * T[0]);
g = uint(255 * T[1]);
b = uint(255 * T[2]);
}

return (r << 16) | (g << 8) | b;
}

public function set rgb(color:uint):void
{
var r:Number = color >> 16 & 0xFF;
var g:Number = color >> 8 & 0xFF;
var b:Number = color & 0xFF;

h = 0;
s = l = 0;

// normalizes red-green-blue values
var rFraction:Number = Number(r/255);
var gFraction:Number = Number(g/255);
var bFraction:Number = Number(b/255);

var max:Number = Math.max(rFraction, Math.max(gFraction, bFraction));
var min:Number = Math.min(rFraction, Math.min(gFraction, bFraction));

// h
if(max == min)
{
h = 0; // undefined
}
else if (max == rFraction && gFraction >= bFraction)
{
h = 60.0 * (gFraction - bFraction) / (max - min);
}
else if (max == rFraction && gFraction < bFraction)
{
h = 60.0 * (gFraction - bFraction) / (max - min) + 360.0;
}
else if (max == gFraction)
{
h = 60.0 * (bFraction - rFraction) / (max - min) + 120.0;
}
else if (max == bFraction)
{
h = 60.0 * (rFraction - gFraction) / (max - min) + 240.0;
}

// luminance
l = (max + min) / 2.0;

// s
if(l == 0 || max == min)
{
s = 0;
}
else if (0 < l && l <= 0.5)
{
s = (max - min) / (max + min);
}
else if (l > 0.5)
{
s = (max - min) / (2 - (max + min)); //(max-min > 0)?
}
}
}


This is a very useful little class. You can create one with a RGB value passed in and read its HSL(hue, saturation and lightness) properties. What's more, you can modify the h, s or l properties, and get the rgb property.

For example, you could desaturate a color as follows:


var hslColor:HslColor = new HslColor(rgb);
hslColor.s *= .5;
rgb = hslColor.rgb;


Going back to the original example, how do I change the DataGrid.borderColor to an arbitrary color and have all the other color values change correspondingly?

Well, with the aid of the HslColor class and the following ColorUtils class I wrote:

public class ColorUtils
{
/**
* Return a color derived from baseColor using sampleStartColor and sampleEndColor as a guide.
* For example, this function would return dark blue if baseColor was light blue, sampleStartColor
* was pink and sampleEndColor was dark red.
*
*/
public static function calculateDerivedColor(baseColor:uint, sampleStartColor:uint, sampleEndColor:uint):uint
{
var baseColorHSL:HslColor = new HslColor(baseColor);

var sampleStartColorHSL:HslColor = new HslColor(sampleStartColor);
var sampleEndColorHSL:HslColor = new HslColor(sampleEndColor);

// rotate the hue and offset the saturation and lightness based on the inputs
baseColorHSL.h = (baseColorHSL.h + sampleEndColorHSL.h - sampleStartColorHSL.h + 360) % 360;
baseColorHSL.s = Math.min(1,Math.max(0, baseColorHSL.s + sampleEndColorHSL.s - sampleStartColorHSL.s));
baseColorHSL.l = Math.max(0, baseColorHSL.l + sampleEndColorHSL.l - sampleStartColorHSL.l);

return baseColorHSL.rgb;
}

}


Here calculateDerivedColor() returns a new color based on baseColor but with the differences between sampleStartColor and sampleEndColor applied to it. So if sampleStartColor and sampleEndColor were the same color, it should simply return baseColor.

To set DataGrid's borderColor to an arbitrary color and set rollOverColor and selectionColor to a related color, we can do the following:


var datagridCSS:CSSStyleDeclaration = StyleManager.getStyleDeclaration("DataGrid");
datagridCSS.setStyle('borderColor', newBorderColor);
datagridCSS.setStyle('rollOverColor', ColorUtils.calculateDerivedColor(newBorderColor, 0x1b3244, 0x3c5c80));
datagridCSS.setStyle('selectionColor', ColorUtils.calculateDerivedColor(newBorderColor, 0x1b3244, 0xbacfe7));


Note that 0x1b3244 was the original borderColor (which we replaced with newBorderColor) and 0x3c5c80 and 0xbacfe7 were the original rollOverColor and selectionColor values, respectively.

Feel free to use this code however you see fit. A link back here would be appreciated.

Friday, July 3, 2009

Problem de jour

Okay, I'm hoping to publish some cool tips n' tricks I've picked up or developed one day...but at the moment, I'm stuck on a new issue. Using Flex SDK 3.3 and Internet Explorer 8, I am suddenly getting this crash whenever I launch:

undefined
at mx.controls::SWFLoader/doScaleContent()
at mx.controls::SWFLoader/updateDisplayList()
at mx.controls::Image/updateDisplayList()
at mx.core::UIComponent/validateDisplayList()
at mx.managers::LayoutManager/validateDisplayList()
at mx.managers::LayoutManager/doPhasedInstantiation()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at mx.core::UIComponent/callLaterDispatcher2()
at mx.core::UIComponent/callLaterDispatcher()

There was a SWFLoader in this project, but commenting it out didn't solve the problem.

SOLVED: the "fix" seems to be to switch to Firefox. Hopefully this is a debug issue only.

Wednesday, July 1, 2009

Problem from Hell (part III - back to hell with you)

My partner took a look at this and somehow came up with this "solution".

In the project, there's a MXML file called something like TestHohoCanvasHelper.mxml, containing this declaration:

<containers:HohoCanvas
id="foo"
width="100%"
height="100%">

Elsewhere there's an AS file called something like TestHohoCanvas.as which creates a TestHohoCanvasHelper dynamically like this:

private var
testHohoCanvasHelper:TestHohoCanvasHelper;
private var testHohoCanvas:HohoCanvas;

override public function setUp():void
{
testHohoCanvasHelper = new TestHohoCanvasHelper();
Application(Application.application).addChild(testHohoCanvasHelper );
testHohoCanvas = testHohoCanvasHelper.foo;
}


This used to work fine, but I think the Flex compiler detected I was working long hours, figured I was on deadline, and decided to crap out with compilation errors galore - even when building applications that didn't even reference this file.

The solution was to remove "id=" from TestHohoCanvasHelper.mxml and reference the HohoCanvas a different way.

As a friend of mine put it, "ah, life with open sores".

Hope this was useful to someone.

Problem from Hell (part II)

Okay, I have steps now. I'd hoped to submit this to Adobe as a bug, but it would mean sending the entire project as well, and the client is leery of that. But here are the steps (again, names changed). Note that all the .mxml files described here are applications at the top level of the project.

Flex plug-in version 3.0.214193
Open project

  1. Build TestsUnit.mxml, note it succeeds.
  2. Set TestsHohoSpecial.mxml as default and build. Note errors (66, none of which make sense).
  3. Save original content of TestsHohoSpecial.mxml by selecting all, copying and pasting into the Notepad.
  4. Replace contents of TestsHohoSpecial.mxml with contents of TestsHoho.mxml (open TestsHoho.mxml, select all, copy, open TestsHohoSpecial.mxml, select all, paste).
  5. Build TestsHohoSpecial.mxml, note it succeeds.
  6. Replace contents of TestsHohoSpecial.mxml with its original contents (from the Notepad).
  7. Build TestsHohoSpecial.mxml, note that now it succeeds.
  8. Set TestsUnit.mxml as the default application, build. Note the same 66 errors.
Okay, so why is the state of step #2 different from step #7? And why is step #1 different from step #8? And can I bill for the time I'm wasting on this without looking entirely incompetent?

Problem from Hell (part 1)

For days I'd been promising an important client that I would commit some Flex code I've been working on. Unfortunately, I'd had little time to work on it because another client had an urgent issue I kept getting pulled back into.

Finally, I resolve to wrap it up, hell or high water. I'm working well into the night and getting closer, which is good, because I'm getting tired...and then, well...let's just say that I wasn't expecting hell AND high water. Without any obvious trigger, suddenly I got 66 compile errors compiling the TestHohoSpecial.mxml application within the project (names changed). And not a single one made a lick of sense. A sampling of these errors:

1067: Implicit coercion of a value of type com.hoho.cairngorm.view.containers:HoHoCanvas to an unrelated type Object.
Hoho/src/com/hoho/tests/unit/cairngorm/view/containers TestHohoHelper.mxml

Invalid Embed directive in stylesheet - can't resolve source 'Embed(source = "/com/hoho/assets/hoho.png")'.
Hoho/src/com/hoho/assets/css

The first error made no sense because HoHoCanvas, which is a Canvas subclass, compiled without errors. The line the compiler barfed at was basically this:

<containers:HoHoCanvas/>

The embedding errors also made no sense, because the same style sheet was being used by other applications in the project with no errors.

Because TestHohoSpecial wasn't a critical application, I checked it in and asked the client to try to build it. My computer's been a little funky, and I thought I might need to reinstall. He gave it a try, and had the same experience.

The compile errors were obviously wrong, but there was something about TestHohoSpecial that was triggering them. I'd based it on an application called TestHoho in the project that compiled fine, so I set about to methodically remove any differences between the two until I found the trigger. I removed them all, and still got the errors. Weird, okay, revert TestHohoSpecial and start over...

Okay, except that now TestHohoSpecial built without any errors. However, TestHoho - which is critical, and which I hadn't touched - now fails with the exact same errors.

I feel like a carpenter whose hammer just turned into rubber in midswing. This is completely obnoxious and unacceptable. It's impossible to schedule my time when the tools just break like this. Granted, I'm a little cranky now because I was up to 4AM beating my head against a wall...a wall which just blinked out of existence and reappeared elsewhere.

Ugh. Silverlight is looking better all the time.

Firsties!

I created this blog to document my challenges (and hopefully some solutions) with the Flex programming environment. There are thousands of blogs like this, and I don't claim any special insight...but maybe one day this blog will contain just the answer someone needs. It seems a shame to learn a lesson the hard way and not document it.

I also actively program in C# (.NET) and C++.