Aligning textfields close together produces inaccurate spacing appearance - actionscript-3

I've added Arial as an embedded font to my font format, which I've added to numerous textfields that must be aligned accurately next to each other to create the appearance of spaces. They are aligned approximately 1 - 2 pixels next to each other. The problem is that they look inaccurately spaced when viewed at the normal stage size, but when I zoom in the text looks fine.
I've tried messing around and adding advanced antialiasing, but nothing is working. Perhaps Arial doesn't work at small sizes? I know not setting a font it produces the results I want, however it's not the font I want to use; it must be using Times?
Any ideas?
var myFont = new Font1();
format1 = new TextFormat();
format1.color = 0x000000;
format1.size = 16;
format1.font = myFont.fontName;
EDIT:
Essentially I'm splitting up each character into its own textfield, so I can manipulate each character and animate it. But to do so I need to space the characters as though it was one textfield.
private var bmd:BitmapData; // bitmapdata to draw textField in;
private function getExactMetrics(textField:TextField):Object
{
// copy textField to bitmap
bmd = new BitmapData(textField.width,textField.height,true,0x000000);
bmd.draw(textField);
// loop through pixels of bitmap data and store the x location of pixels found.
var foundx:Array = [];
for (var nx:int = 0; nx < bmd.width;nx++)
{
for (var ny:int = 0; ny < bmd.height; ny++)
{
var px:uint = bmd.getPixel32(nx, ny);
if (px != 0)
{
foundx.push(nx);
break;
}
}
}
// get the values for the metrics
var startX:int = foundx[0]; // first pixel found representing the x start of the text in pixels
var endX:int = foundx[foundx.length-1]; t
var realWidth:Number = endX - startX;
// clear the array with found x positions
foundx = [];
// wrap values in object
var metrics:Object = { };
metrics.width = realWidth;
metrics.x = startX;
// clear bitmapdata
bmd = null;
return metrics; // retrurn metric object;
}
}

Remember that fonts have kerning which means that letters are not just put one next to each other, but spacing depends on settings of the font. For example in word 'To' 'o' is slightly below the 'top beam' of letter 'T'.
If you just put letter next to each other it will look bad, you have to consider kerning.
TextField class has a getCharBoundaries method which can be used to get accurate char position. Not sure if it will work with htmlText, though.
To make it simpler you could use a third party library. There is a great SplitTextField from Greensock (but it's not free) or Textanim (open source).

Fonts such as Verdana might have greater readability at smaller sizes; however, it sounds like you are experiencing text metrics issues.
In this example, TextLineMetrics are used to accurately measure each text field.
Result:
Code:
package
{
import flash.display.Sprite;
import flash.text.AntiAliasType;
import flash.text.Font;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.TextLineMetrics;
[SWF(percentWidth = 100, percentHeight = 100, backgroundColor = 0xefefef, frameRate = 30)]
public class X extends Sprite
{
public function X()
{
super();
// collection of words
var words:Array = [ "These", "are", "the", "words", "to", "be", "placed" ];
// define a text format
var textFormat:TextFormat = new TextFormat();
textFormat.font = "Arial";
textFormat.bold = false;
textFormat.size = 8;
textFormat.color = 0x0;
textFormat.align = TextFormatAlign.LEFT;
// destination x coordinate for next word text field
var dx:Number = 0;
// for every word
for each (var word:String in words)
{
// create a text field
var textField:TextField = new TextField();
textField.setTextFormat(textFormat);
textField.defaultTextFormat = textFormat;
textField.autoSize = TextFieldAutoSize.LEFT;
textField.antiAliasType = AntiAliasType.ADVANCED;
// set text and place the text field
textField.text = word;
textField.x = dx;
textField.y = 20;
addChild(textField);
// measure the text field for accurate width
var textLineMetrics:TextLineMetrics = textField.getLineMetrics(0);
dx += textLineMetrics.width + 2;
}
}
}
}

Related

Remove randomly generated sprites with Mouse.Event

I am making a game for my class where different chemical elements are generated as sprites at the top of the screen and then fall down. Different types are made and I want students to mouse over specific types depending on where they are in the game.
My question is how to write the function to remove them when they are correctly selected? I've tried a lot of different ways but am having a lot of trouble. An example of the code that I wrote to make each element is below and then I have a separate function to move down all of the sprites created.
var spriteArray:Array = new Array();
var halogenArray:Array = new Array("F", "Cl", "Br", "I");
var rndnum:Number = Math.random();
//Halogens
if (rndnum < 0.05)
{
var halo:Sprite = new Sprite();
halo.graphics.beginFill(0x00FF00, 1);
halo.graphics.drawCircle(7.5, 7.5, 15);
halo.graphics.endFill();
halo.addEventListener(MouseEvent.MOUSE_OVER, removeElement);
halo.x = Math.random()*500 + 50;
halo.y = -18;
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
textField.height = 30;
textField.x = (15 - textField.textWidth)/2; // center it horizontally
textField.y = (15 - textField.textHeight)/2; // center it vertically
halo.addChild(textField);
spriteArray.push(halo);
addChild(halo);
}
At what point are you struggling?
I am assuming it is in determining the types of the halogens.
In your remove function I assume you have the desired type already figured out, you would then compare it to
element.getChildAt(0).text
and you would get the element by either looping across every element in the spriteArray, or using the mouseEvent's target
My suggestion is to use a halogen Class to contain the grapics & textfield, and a vector to hold the objects. It would then be easier to get the type rather than searching the anonymous children of the sprite.
I believe you are looking for something like this:
//give your textfields a name, it isn't totally necessary as we can do getChildAt(0)
//but it's more readable, and if you decide to add more children before you
//add the text field, then this will still work
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
...
textField.name = "haloTx"; //for tracking later
//assuming you have some variable set to the correct answer
var correctAnswer:String = "F";
function removeElement( e:MouseEvent ):void {
var element:TextField = ( e.target as Sprite ).getChildByName( "haloTx" );
//if we have the correct element, remove from parent and list
if ( element && element.text == correctAnswer ) {
var index:int = spriteArray.indexOf( e.target as Sprite );
removeChild( spriteArray.splice( index, 1 )[0] );
}
}
Although #VBCPP is right, doing that in a separate class is definitely the best way organizationally. Which might look something like:
class ElementSprite extends Sprite {
public var textField:TextField;
//pass shapeArgs as string, so say a circle at x=7.5, y=7.5, and radius=15 -- shapeArgs = "7.5, 7.5, 15"
public function ElementSprite( element:String, drawShape:String="Circle", shapeArgs:String="7.5, 7.5, 15", fillColor:uint=0x00FF00 ) {
//set textfield properties etc. or textFormat
textField = new TextField();
textField.text = element;
addChild( textField );
//if you passed some arguments to draw our shape
if ( shapeArgs != "" ) {
graphics.beginFill( fillColor );
graphics[ "draw" + drawShape ].apply( this, shapeArgs.split( "," ) );
}
}
public function get currentElement():String { return textField.text }
}
Then you would use it like so in your if statement if (rndnum < 0.05):
var elementSprite:ElementSprite = new ElementSprite( "A" );
//elementSprite.x = set your x;
//elementSprite.y = set your y;
addChild(elementSprite);
That would be replacing all your current code in that if statement. This is all a working example, if you have an questions feel free to comment.

Applying blur filter to freeform areas

I have to build a flash application for simple wrinkle retouching. The user should be able to upload a portrait photo, select the wrinkled areas and then a blur filter will be applied to the selected parts. My question is - is it somehow possible to apply the filter to a freeform area? I know it would be easy to make the selection rectangular, but that would not be very useful to really mark the correct areas. Ideally the user should get some kind of round brush to use for marking the areas, press "OK" and then the filter will be applied.
Is there any way to do this? And do you maybe have some further recommendations on how to approach this task? I have very little experience with manipulating bitmap data with ActionScript.
Any help is appreciated! Thanks very much in advance :-)
The algorithm is as follows:
create a BitmapData of selection shape using BitmapData.draw() (let it be A)
cut a rectangular piece of original image that is covered by selection area, add some margins for blurred area using Bitmapdata.copyPixels() (let it be B).
remove all pixels from B, that are not covered by shape A with BitmapData.threshold() using A as source bitmap.
blur the resulting image
copy blurred pixels back to target image using Bitmapdata.copyPixels()
Here's a complete and working example.
I've written the code anyway while figuring out the solution.
Hope you find it usefull.
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.LoaderContext;
[SWF(width="800", height="600",backgroundColor="#FFFFFF")]
public class TestBlur extends Sprite
{
private const loader:Loader = new Loader();
public function TestBlur()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest("http://i.stack.imgur.com/u3iEv.png"), new LoaderContext(true));
}
protected function onLoadComplete(event:Event):void
{
trace(event);
var bmp:Bitmap = loader.content as Bitmap;
addChild(bmp);
// create some test selection area
var selection:Shape = new Shape();
selection.graphics.lineStyle(30, 0xFF0000, .5);
selection.graphics.curveTo(75, -50, 200, 10);
selection.x = 40;
selection.y = 60;
addChild(selection);
// create a duplicate of the original image
var target:BitmapData = bmp.bitmapData.clone();
var targetBmp:Bitmap = new Bitmap(target);
targetBmp.x = bmp.x + bmp.width;
addChild(targetBmp);
//
// *** main work starts here ***
// by now we have selection shape and a bitmap to blur
const destPoint:Point = new Point();
const drawMatrix:Matrix = new Matrix();
const blurMargin:uint = 10;
const blur:BlurFilter = new BlurFilter(2, 2, 3);
var rect:Rectangle;
// 0: prepare an image of selection area
// we'll need it at step 3
rect = selection.getBounds(selection);
rect.x -= blurMargin;
rect.y -= blurMargin;
rect.width += blurMargin*2;
rect.height += blurMargin*2;
var selectionImage:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
drawMatrix.identity();
drawMatrix.translate(-rect.x, -rect.y);
selectionImage.draw(selection, drawMatrix);
// just some testing
var test0:Bitmap = new Bitmap(selectionImage.clone());
test0.y = bmp.y + bmp.height;
addChild(test0);
// 1: cut a rectangular piece of original image that is covered by selection area
rect = selection.getBounds(selection.parent);
rect.x -= blurMargin;
rect.y -= blurMargin;
rect.width += blurMargin*2;
rect.height += blurMargin*2;
var area:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
area.copyPixels(bmp.bitmapData, rect, destPoint);
// just some testing
var test1:Bitmap = new Bitmap(area.clone());
test1.y = bmp.y + bmp.height;
test1.x = test0.x + test0.width;
addChild(test1);
// 2: remove all pixels that are not covered by selection
area.threshold(selectionImage, area.rect, destPoint, "==", 0, 0, 0xFF000000);
// just some testing
var test2:Bitmap = new Bitmap(area.clone());
test2.y = test0.y + test0.height;
test2.x = test0.x;
addChild(test2);
// 3: blur copied area
area.applyFilter(area, area.rect, destPoint, blur);
// just some testing
var test3:Bitmap = new Bitmap(area.clone());
test3.y = test0.y + test0.height;
test3.x = test2.x + test2.width;
addChild(test3);
// 4: copy blurred pixels back to target image
destPoint.x = rect.x;
destPoint.y = rect.y;
target.copyPixels(area, area.rect, destPoint);
}
}
}

how to write on screen in AS3

So I am creating a module and I have a screen that I need to be able to allow the users to write questions that they have on their screens in a text box. Does anyone know how to do this?
This is the basic setup that I use for every screen:
package screens
{
import flash.filters.*;
import flash.text.*;
import mapSystem.screenSystem.*;
import mapSystem.*;
import screens.*;
import caurina.transitions.Tweener;
public class screen4 extends screenBase
{
public function screen4(pSystem:mapManager)
{
super(pSystem);
numActions = 1;
}
public override function onAction()
{
if (actionStep == 1)
{
map.fID("54");
}
}
public override function onEnter()
{
map.zoomTo("full");
}
}
}
For users to input text, simply create a textfield and set its "type" property to TextFieldType.INPUT. When you go to retrieve this data, just access the textFields "text" prop.
Update -
Ok = simple google search on "AS3 textField tutorial", first hit was this tutorial, which I yanked and added a couple things to for you. Its fairly basic and well documented, so, depending on your level of experience, should prove illuminating.
//Creating the textfield object and naming it "myTextField"
var myTextField:TextField = new TextField();
//Here we add the new textfield instance to the stage with addchild()
addChild(myTextField);
//Here we define some properties for our text field, starting with giving it some text to contain.
//A width, x and y coordinates.
myTextField.text = "input text here";
myTextField.width = 250;
myTextField.x = 25;
myTextField.y = 25;
//#b99 addition
myTextField.type = TextFieldType.INPUT;
//This is the section for our text styling, first we create a TextFormat instance naming it myFormat
var myFormat:TextFormat = new TextFormat();
//Giving the format a hex decimal color code
myFormat.color = 0xAA0000;
//Adding some bigger text size
myFormat.size = 24;
//Last text style is to make it italic.
myFormat.italic = true;
//Now the most important thing for the textformat, we need to add it to the myTextField with setTextFormat.
myTextField.setTextFormat(myFormat);
Hope that helps!

Length of a string in pixels

I'm populating a dropDownList with arrayCollection of strings. I want the width of the drop down list control to match with the size (in pixels) of the longest string in the array collection. The problem I'm facing is: the font width of the strings in the collection are different e.g. 'W' looks wider than 'l'. So I estimated the width of a character to be 8 pixels but that's not pretty neat. If a string that has many 'W' and 'M' is encountered the estimation is wrong. So I want precise pixel width of strings. How can i get the exact length of a string in pixels??
My solution that estimates all character to be 8 pixels wide is given below:
public function populateDropDownList():void{
var array:Array = new Array("o","two","three four five six seven eight wwww");
var sampleArrayCollection:ArrayCollection = new ArrayCollection(array);
var customDropDownList:DropDownList = new DropDownList();
customDropDownList.dataProvider=sampleArrayCollection;
customDropDownList.prompt="Select ...";
customDropDownList.labelField="Answer Options:";
//calculating the max width
var componentWidth=10; //default
for each(var answerText in array){
Alert.show("Txt size: "+ answerText.length + " pixels: " + answerText.length*9);
if(answerText.length * 8 > componentWidth){
componentWidth=answerText.length * 8;
}
}
customDropDownList.width=componentWidth;
answers.addChild(customDropDownList);
}
Any idea or solution is highly valued.
Thanks
To get a more accurate measurement, you can populate a TextField with the string, then measure the width of that TextField's text.
Code:
function measureString(str:String, format:TextFormat):Rectangle {
var textField:TextField = new TextField();
textField.defaultTextFormat = format;
textField.text = str;
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
Usage:
var format:TextFormat = new TextFormat();
format.font = "Times New Roman";
format.size = 16;
var strings:Array = [ "a", "giraffe", "foo", "!" ];
var calculatedWidth:Number = 50; // Set this to minimum width to start with
for each (var str:String in strings) {
var stringWidth:Number = measureString(str, format).width;
if (stringWidth > calculatedWidth) {
calculatedWidth = stringWidth;
}
}
trace(calculatedWidth);
I don't have edit priv on other people's post's so I'm posting this as a separate answer but credit should go to Cameron if this works:
function measureString(str:String, format:TextFormat):Rectangle {
var textField:TextField = new TextField();
textField.autoSize = TextFieldAutoSize.LEFT;
textField.defaultTextFormat = format;
textField.text = str;
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
If I see that it does and his is edited I'd delete this one for cleanliness.
Sorry for the garbage post initially was trying to answer the question just did so erroneously... anyhow tested this one and it appears to work. I did this in Flex but you should be able to just use the AS3 part no problem I just wrapped up the textfield in a UIComponent to get it on stage but using the autosize seems to work fine:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.core.UIComponent;
protected function textinput1_changeHandler(event:TextOperationEvent):void
{
// TODO Auto-generated method stub
var rect:Rectangle = measureString(event.target.text);
holderBox.graphics.clear();
holderBox.graphics.beginFill(0xFF0000);
holderBox.graphics.drawRect(rect.x,rect.y,rect.width,rect.height);
}
private function measureString(str:String):Rectangle {
var textField:TextField = new TextField();
textField.autoSize = TextFieldAutoSize.LEFT;
textField.text = str;
var uiComponent:UIComponent = new UIComponent();
uiComponent.addChild(textField);
holderBox.addChild(uiComponent);
return new Rectangle(0, 0, textField.textWidth, textField.textHeight);
}
]]>
</fx:Script>
<mx:VBox>
<s:TextInput text="" change="textinput1_changeHandler(event)"/>
<mx:Box id="holderBox"/>
</mx:VBox>
</s:Application>
This is a more polished version of some of the above code. Accounting for linebreaks (html break and \n) and nullifying the created Textfield object with some other optimizations. Hope this is helpful.
function measureString(str:String, font:String="Times New Roman", size:Number=12):Rectangle
{
var textField:TextField = new TextField();
textField.defaultTextFormat = new TextFormat( font, size );
textField.border = true;
textField.multiline = true;
textField.autoSize = TextFieldAutoSize.LEFT;
textField.htmlText = str;
// Grab with and height before nullifying Textfield.
var w:Number = textField.textWidth;
var h:Number = textField.textHeight;
//addChild( textField );// This will add the Textfield to the stage so you can visibly see it.
//if( contains( textField ) ) removeChild( textField );// If it exists onstage, remove it.
textField = null;//nullify it to make it available for garbage collection.
return new Rectangle(0, 0, w, h);
}
var str:String = "Jeremy is a good boy.<br>He also has a red bike. \nSometimes Jeremy rides his bike to the store to buy bread for his family.<br>He likes wholewheat.";
trace( measureString( str, "Times New Roman", 25 ).width );
If you prefer this in a class, check it out in my GIT framework:
https://github.com/charlesclements/as3-tools/blob/master/net/charlesclements/util/text/TextUtil.as
AS3-tools:https://github.com/charlesclements/as3-tools
Also, our Flash/JS brother Jack Doyle # GreenSock has some handy stuff to do with manipulating text. Well worth it to check it out:http://www.greensock.com/splittext/
Here is a routine I wrote for Java. I guess you can convert it to Javascript or whatever.
It is not exact, but gives an approximate answer. It assumes that a space is about 200 wide, and M and W is about 1000 wide. Obviously you'll need to scale that based on your particular font size.
static int picaSize(String s)
{
// the following characters are sorted by width in Arial font
String lookup = " .:,;'^`!|jl/\\i-()JfIt[]?{}sr*a\"ce_gFzLxkP+0123456789<=>~qvy$SbduEphonTBCXY#VRKZN%GUAHD#OQ&wmMW";
int result = 0;
for (int i = 0; i < s.length(); ++i)
{
int c = lookup.indexOf(s.charAt(i));
result += (c < 0 ? 60 : c) * 7 + 200;
}
return result;
}

Papervision3D; rotate child objects within camera view

It's my first time with Papervision3D and I have created a slide show of images that is skewed on the y-axis. Smallest photos on the left, and they increase in size going to the right. So they zoom from left to right, smallest to biggest.
I have a tooltip that pops up when you hovers over the photo, but the tooltip also gets skewed proportionate to the camera view (slanted). I want the tooltip's angle to be independent of the entire camera view.
Any idea how to rotate objects independent of the parent's camera angle?
Thanks!
my_obj = new DisplayObject3D();
my_plane = my_obj.addChild(new Plane(bla bla));
my_obj.lookAt(camera);
The 'lookAt' bit is what you need.
Why not draw the tooltips in 2d? You can get the on-screen position of the images and then just draw a regular Sprite like so:
package
{
import flash.display.Sprite;
import flash.text.TextField;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.typography.Text3D;
import org.papervision3d.view.BasicView;
public class PVTest extends Sprite
{
private var world:BasicView;
private var text:Text3D;
private var text2d:TextField;
public function PVTest()
{
world = new BasicView(stage.width, stage.height, true, true);
var colorMat:ColorMaterial = new ColorMaterial();
colorMat.interactive = true;
var planeContainer:DisplayObject3D = new DisplayObject3D();
var plane:Plane;
for(var i:int = 0; i < 11; i++)
{
plane= new Plane(
colorMat,
100, 100,
10);
plane.x = (i * 105) - 500;
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, handleMouseOver);
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, handleMouseOut);
planeContainer.addChild(plane);
}
planeContainer.rotationY = 10;
world.scene.addChild(planeContainer);
world.camera.z = -500;
addChild(world);
world.startRendering();
}
private function handleMouseOver(event:InteractiveScene3DEvent):void
{
var plane:Plane = Plane(event.displayObject3D);
plane.calculateScreenCoords(world.camera);
const OFFSET_X:int = -20;
const OFFSET_Y:int = 30;
text2d = new TextField();
text2d.text = "toolTip";
text2d.x = plane.screen.x + (stage.width/2) + OFFSET_X;
text2d.y = plane.screen.y + (stage.height/2) + OFFSET_Y;
addChild(text2d);
}
private function handleMouseOut(event:InteractiveScene3DEvent):void
{
removeChild(text2d);
}
}
}
Even for this example you'd have to offset the y position of the tooltip based on the objects scale but it may be easier than working out the rotations and is the best way to get a consistent looking result.