Box2d body generation dynamically in cocos2dx using c++ in Xcode - cocos2d-x

I am a new developer in cocos2dx.I am developing a game in which i am using the box2d bodies that are loading through physics editor.There are more than 20 bodies using in a level of the game,that i am making separate body for separate sprite attached with them and the similar bodies are used in the other levels and there are 50 levels in the game and for each level,i have made separate class and again making the b2body loading function,all the code is working properly but I just want to make a generic function for loading the bodies in a class so that i can use the same b2body loading function in all the levels.Also i have to destroy the particular body and the sprite on touching the sprite
//Sprites:
rect_sprite1=CCSprite::create("rect1.png");
rect_sprite1->setScaleX(rX);
rect_sprite1->setScaleY(rY);
this->addChild(rect_sprite1,1);
rect_sprite2=CCSprite::create("rect2.png");
rect_sprite2->setScaleX(rX);
rect_sprite2->setScaleY(rY);
this->addChild(rect_sprite2,1);
rect_sprite3=CCSprite::create("circle.png");
rect_sprite3->setScale(rZ);
this->addChild(rect_sprite3,1);
GB2ShapeCache::sharedGB2ShapeCache()->addShapesWithFile("obs.plist");
//body loading function
void Level1::addNewSpriteWithCoords()
{
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
b2BodyDef bodyDef1;
bodyDef1.type=b2_dynamicBody;
bodyDef1.position.Set((winSize.width*0.38)/PTM_RATIO,(winSize.height*0.4) /PTM_RATIO);
bodyDef1.userData = rect_sprite1;
body1 = (MyPhysicsBody*)world->CreateBody(&bodyDef1);
body1->setTypeFlag(7);
// add the fixture definitions to the body
GB2ShapeCache *sc1 = GB2ShapeCache::sharedGB2ShapeCache();
sc1->addFixturesToBody(body1,"rect1", rect_sprite1);
rect_sprite1->setAnchorPoint(sc1->anchorPointForShape("rect1"));
b2BodyDef bodyDef2;
bodyDef2.type=b2_dynamicBody;
bodyDef2.position.Set((winSize.width*0.62)/PTM_RATIO,
(winSize.height*0.4)/PTM_RATIO);
bodyDef2.userData = rect_sprite2;
body2 = (MyPhysicsBody*)world->CreateBody(&bodyDef2);
body2->setTypeFlag(7);
// add the fixture definitions to the body
GB2ShapeCache *sc2 = GB2ShapeCache::sharedGB2ShapeCache();
sc2->addFixturesToBody(body2,"rect2", rect_sprite2);
rect_sprite2->setAnchorPoint(sc2->anchorPointForShape("rect2"));
b2BodyDef bodyDef3;
bodyDef3.type=b2_dynamicBody;
bodyDef3.position.Set((winSize.width*0.5)/PTM_RATIO, (winSize.height*0.23)/PTM_RATIO);
bodyDef3.userData = rect_sprite3;
body3 = (MyPhysicsBody*)world->CreateBody(&bodyDef3);
body3->setTypeFlag(7);
// add the fixture definitions to the body
GB2ShapeCache *sc3 = GB2ShapeCache::sharedGB2ShapeCache();
sc3->addFixturesToBody(body3,"circle", rect_sprite3);
rect_sprite3->setAnchorPoint(sc3->anchorPointForShape("circle"));
}
void Level1::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event)
{
if(box->containsPoint(touchPoint))
{
this->removeChild(((CCSprite*)rect),true);
if(((CCSprite*)rect)==rect_sprite1)
{
rect_sprite1=NULL;
world->DestroyBody(body1);
Utils::setCount(Utils::getCount()-1);
}
if(((CCSprite*)rect)==rect_sprite2)
{
rect_sprite2=NULL;
world->DestroyBody(body2);
Utils::setCount(Utils::getCount()-1);
}
if(((CCSprite*)rect)==rect_sprite3)
{
rect_sprite3=NULL;
world->DestroyBody(body3);
Utils::setCount(Utils::getCount()-1);
}
}
Similarly i am doing for other levels.
If anyone know about it,please suggest.Thanks

This seems more like a "What design pattern should I use?" than a specific problem with loading the code.
In general, when I want to create "Entities" that require a physical body, I use a base class that contains the Box2D body as one of its members. The base class is a container for the body (which is assigned to it) and is responsible for destroying the body (removing it from the Box2D world) when the Entity is destroyed.
Derived classes can load the body from the Box2D shape cache. This is best shown through an example. There is a game I am working on where I have a swarm of different shaped asteroids circling a sun. Here is a screen shot:
The base class, Entity, contains the body and destroys it when the Entity is destroyed:
class Entity : public HasFlags
{
public:
enum
{
DEFAULT_ENTITY_ID = -1,
};
private:
uint32 _ID;
// Every entity has one "main" body which it
// controls in some way. Or not.
b2Body* _body;
// Every entity has a scale size from 1 to 100.
// This maps on to the meters size of 0.1 to 10
// in the physics engine.
uint32 _scale;
protected:
void SetScale(uint32 value)
{
assert(value >= 1);
assert(value <= 100);
_scale = value;
}
public:
void SetBody(b2Body* body)
{
assert(_body == NULL);
if(_body != NULL)
{
CCLOG("BODY SHOULD BE NULL BEFORE ASSIGNING");
_body->GetWorld()->DestroyBody(_body);
_body = NULL;
}
_body = body;
if(body != NULL)
{
_body->SetUserData(this);
for (b2Fixture* f = _body->GetFixtureList(); f; f = f->GetNext())
{
f->SetUserData(this);
}
}
}
inline void SetID(uint32 ID)
{
_ID = ID;
}
inline uint32 GetID() const
{
return _ID;
}
virtual string ToString(bool updateDescription = false)
{
string descr = "ID: ";
descr += _ID;
descr += "Flags: ";
if(IsFlagSet(HF_IS_GRAPH_SENSOR))
descr += "IS_FLAG_SENSOR ";
return descr;
}
Entity() :
_ID(DEFAULT_ENTITY_ID),
_body(NULL),
_scale(1)
{
}
Entity(uint32 flags, uint32 scale) :
HasFlags(flags),
_ID(DEFAULT_ENTITY_ID),
_body(NULL),
_scale(scale)
{
}
virtual void Update()
{
}
virtual void UpdateDisplay()
{
}
virtual ~Entity()
{
if(_body != NULL)
{
_body->GetWorld()->DestroyBody(_body);
}
}
inline static float32 ScaleToMeters(uint32 scale)
{
return 0.1*scale;
}
inline Body* GetBody()
{
return _body;
}
inline const Body* GetBody() const
{
return _body;
}
inline uint32 GetScale()
{
return _scale;
}
inline float32 GetSizeMeters()
{
return ScaleToMeters(_scale);
}
};
The Asteroid class itself is responsible for loading one of several different "Asteroid" shapes from the shape cache. However, ALL the asteroids have common logic for making them move about the center of the screen. They have a rope joint attached and the Update(...) function adds some "spin" to them so they rotate around the center:
class Asteroid : public Entity
{
private:
b2Fixture* _hull;
Vec2 _anchor;
CCSprite* _sprite;
float32 _targetRadius;
public:
// Some getters to help us out.
b2Fixture& GetHullFixture() const { return *_hull; }
float32 GetTargetRadius() { return _targetRadius; }
CCSprite* GetSprite() { return _sprite; }
void UpdateDisplay()
{
// Update the sprite position and orientation.
CCPoint pixel = Viewport::Instance().Convert(GetBody()->GetPosition());
_sprite->setPosition(pixel);
_sprite->setRotation(-CC_RADIANS_TO_DEGREES(GetBody()->GetAngle()));
}
virtual void Update()
{
Body* body = GetBody();
Vec2 vRadius = body->GetPosition();
Vec2 vTangent = vRadius.Skew();
vTangent.Normalize();
vRadius.Normalize();
// If it is not moving...give it some spin.
if(fabs(vTangent.Dot(body->GetLinearVelocity())) < 1)
{
body->SetLinearDamping(0.001);
body->ApplyForceToCenter(body->GetMass()*1.5*vTangent);
body->ApplyForce(vRadius,body->GetMass()*0.05*vRadius);
}
else
{
body->SetLinearDamping(0.05);
}
}
~Asteroid()
{
}
Asteroid() :
Entity(HF_CAN_MOVE | HF_UPDATE_PRIO_5,50)
{
}
bool Create(b2World& world, const string& shapeName,const Vec2& position, float32 targetRadius)
{
_targetRadius = targetRadius;
_anchor = position;
string str = shapeName;
str += ".png";
_sprite = CCSprite::createWithSpriteFrameName(str.c_str());
_sprite->setTag((int)this);
_sprite->setAnchorPoint(ccp(0.5,0.5));
// _sprite->setVisible(false);
b2BodyDef bodyDef;
bodyDef.position = position;
bodyDef.type = b2_dynamicBody;
Body* body = world.CreateBody(&bodyDef);
assert(body != NULL);
// Add the polygons to the body.
Box2DShapeCache::instance().addFixturesToBody(body, shapeName, GetSizeMeters());
SetBody(body);
return true;
}
};
The Create(...) function for the Asteroid is called from the loading code in the scene, which could easily be a .csv file with the shape names in it. It actually reuses the names several times (there are only about 10 asteroid shapes).
You will notice that the Asteroid also has a CCSprite associated with it. Not all Entities have a sprite, but some do. I could have created an Entity-Derived class (EntityWithSprite) for this case so that the sprite could be managed as well, but I try to avoid too many nested classes. I could have put it into the base class, and may still. Regardless, the Asteroids contain their own sprites and load them from the SpriteCache. They are updated in a different part of the code (not relevant here, but I will be happy to answer questions about it if you are curious).
NOTE: This is part of a larger code base and there are features like zooming, cameras, graph/path finding, and lots of other goodies in the code base. Feel free to use what you find useful.
You can find all the (iOS) code for this on github and there are videos and tutorials posted on my site here.

Related

Transferable custom classes with ES6 web workers

In Javascript ES6, in the browser, I want to transfer custom class objects to a web worker using the "Transferable" interface. Is this possible? I can find documentation about this for ArrayBuffer objects, but not for custom class objects.
This is not a duplicate of How to pass custom class instances through Web-Workers? since my question is specifically about the Transferable interface. I want to pass my custom class instance to the worker without copying it.
I already addressed this question a few times, in different ways. I'm sorry, but the answer to your particular version of this inquiry is definitely no.
There are a few reasons for that.
Individual JavaScript objects are typically not allocated on continuous memory chunks (which would make it possible to transfer them in theory at least).
Any code that converts normal object/class to a ArrayBuffer would really just be overhead over the existing structured clone algorithm, that does the job well.
What you can do,
if you really want to which I'm not so sure you should.
Imagine a class like this:
class Vector2 {
constructor(existing) {
this._data = new Float64Array(2);
}
get x() {
return this._data[0];
}
set x(x) {
this._data[0] = x;
}
get y() {
return this._data[1];
}
set y(y) {
this._data[1] = y;
}
}
It's properties are stored in an array buffer and you can transfer it. But it's not much use yet, for it to work well, we need to make sure it can be constructed from received array buffer. That can be done for sure:
class Vector2 {
constructor(existing) {
if(existing instanceof ArrayBuffer) {
this.load(existing);
}
else {
this.init();
}
}
/*
* Loads from existing buffer
* #param {ArrayBuffer} existing
*/
load(existing) {
this._data = existing;
this.initProperties();
}
init() {
// 16 bytes, 8 for each Float64
this._data = new ArrayBuffer(16);
this.initProperties();
}
initProperties() {
this._coordsView = new Float64Array(this._data, 0, 2);
}
get x() {
return this._coordsView[0];
}
set x(x) {
this._coordsView[0] = x;
}
get y() {
return this._coordsView[1];
}
set y(y) {
this._coordsView[1] = y;
}
}
Now you can even subclass it, by passing larger array buffer from the subclass, where both parents and child's attributes will fit:
class Vector2Altitude extends Vector2 {
constructor(existing) {
super(existing instanceof ArrayBuffer ? existing : new ArrayBuffer(16 + 8));
this._altitudeView = new Float64Array(this._data, 16, 1);
}
get altitude() {
return this._altitudeView[0];
}
set altitude(alt) {
this._altitudeView[0] = alt;
}
}
A simple test:
const test = new Vector2();
console.log(test.x, test.y);
const test2 = new Vector2Altitude();
test2.altitude = 1000;
console.log(test2.x, test2.y, test2.altitude, new Uint8Array(test2._data));
To make some real use of it, you need to solve many other problems, and essentially implement your own memory allocation for complex objects.

Libgdx crop image to circle

I'm looking for a way to crop a photo taken from the users gallery to a circle, to be displayed as a profile picture basically.
I've been recommended to use Masking .
But I can't work out how to actually do it. There are virtually no examples of it, except with android code. But since I'm going to port my game to IOS as well I need a Libgdx solution.
So has anyone done this before and have an working example perhaps?
Here's how I'll fetch the image:
ublic void invokeGallery() {
if (utils != null) {
loading = true;
utils.pickImage(new utilsInterface.Callback() {
#Override
public ImageHandler onImagePicked(final InputStream stream) {
loading = true;
final byte[] data;
try {
data = StreamUtils.copyStreamToByteArray(stream);
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
loading = false;
}
});
} catch (IOException e) {
e.printStackTrace();
}
loading = false;
return null;
}
});
}
}
I use this, it works, but there is no interpolation, which means the edges are pixelated. You can chose to either implement interpolation or use a border!
public static Pixmap roundPixmap(Pixmap pixmap)
{
int width = pixmap.getWidth();
int height = pixmap.getHeight();
Pixmap round = new Pixmap(pixmap.getWidth(),pixmap.getHeight(),Pixmap.Format.RGBA8888);
if(width != height)
{
Gdx.app.log("error", "Cannot create round image if width != height");
round.dispose();
return pixmap;
}
double radius = width/2.0;
for(int y=0;y<height;y++)
{
for(int x=0;x<width;x++)
{
//check if pixel is outside circle. Set pixel to transparant;
double dist_x = (radius - x);
double dist_y = radius - y;
double dist = Math.sqrt((dist_x*dist_x) + (dist_y*dist_y));
if(dist < radius)
{
round.drawPixel(x, y,pixmap.getPixel(x, y));
}
else
round.drawPixel(x, y, 0);
}
}
Gdx.app.log("info", "pixmal rounded!");
return round;
}
Do keep in mind this will all be unmanaged! To make life easier I usually save the image and load it as a managed texture.

Intermediate image

I have a problem with intermediate image. The image is showing only once. After i move the image "line" is not showing anymore.
public void paintLine(Graphics g) {
if (line == null) {
line = new BufferedImage(1, height, BufferedImage.TYPE_INT_ARGB);
Graphics gImg = line.getGraphics();
float[] data = datas[index];
for (float f : data) {
float[] rgb = ColorMap.getPixelColor(f);
gImg.setColor(new Color(rgb[0], rgb[1], rgb[2]));
gImg.drawRect(0, (int)yPos--, 1, 1);
}
gImg.dispose();
}
xIncr++;
g.drawImage(line, (int)xPos - xIncr, (int)yPos, null);
graph.repaint();
}
This method is called in paintComponent ofJPanel.
If i recreate the image "line" each time, it is displaying properly with really poor performance.
Emm... I am not pretty sure owing to less information in your question but, as a rule, image may disappear because of doubleBuffered(false) option. So you need to set the double buffered option to true manually for your canvas. Code something like this
public class MyLabel extends JLabel{
public MyLabel()
{
this.setDoubleBuffered(true);
}
public void paintComponent(Graphics g)
{
this.paintLine(g);
}
public void paintLine(Graphics g) {
if (line == null) {
line = new BufferedImage(1, height, BufferedImage.TYPE_INT_ARGB);
Graphics gImg = line.getGraphics();
float[] data = datas[index];
for (float f : data) {
float[] rgb = ColorMap.getPixelColor(f);
gImg.setColor(new Color(rgb[0], rgb[1], rgb[2]));
gImg.drawRect(0, (int)yPos--, 1, 1);
}
//gImg.dispose();
}
xIncr++;
g.drawImage(line, (int)xPos - xIncr, (int)yPos, null);
graph.repaint();
}
}
Now I guess the conception is more clear
Good luck

Android: How to use the Html.TagHandler?

I am trying to build an android application for a message board. To display formatted html for the post contents I have chosen the TextView and the Html.fromHtml() method. That, unfortunately, covers only a few html tags. The unknown tags are handled by a class that implements TagHandler and has to be generated by myself.
Now, I googled a lot and can't find an example of how this class should work. Let's consider I have an u tag for underlining some text (I know that this is deprecated, but whatever). How does my TagHandler look like?
It is called in the following way:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
The first two arguments are fine. I guess I have to modify output using output.append(). But how do I attach something underlined there?
So, i finally figured it out by myself.
public class MyHtmlTagHandler implements TagHandler {
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
processStrike(opening, output);
}
}
private void processStrike(boolean opening, Editable output) {
int len = output.length();
if(opening) {
output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, StrikethroughSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for(int i = objs.length;i>0;i--) {
if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
return objs[i-1];
}
}
return null;
}
}
}
And for your TextView you can call this like:
myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));
if anybody needs it.
Cheers
This solution is found in the Android sdk
In android.text.html. Lines 596 - 626. Copy/pasted
private static <T> Object getLast(Spanned text, Class<T> kind) {
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
return objs[objs.length - 1];
}
}
private static void start(SpannableStringBuilder text, Object mark) {
int len = text.length();
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}
private static <T> void end(SpannableStringBuilder text, Class<T> kind,
Object repl) {
int len = text.length();
Object obj = getLast(text, kind);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
To use, override TagHandler like so:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
if(opening){
start((SpannableStringBuilder) output, new Strike();
} else {
end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
}
}
}
/*
* Notice this class. It doesn't really do anything when it spans over the text.
* The reason is we just need to distinguish what needs to be spanned, then on our closing
* tag, we will apply the spannable. For each of your different spannables you implement, just
* create a class here.
*/
private static class Strike{}
I took janoliver's answer and came up with my version that attempts to support more options
String text = ""; // HTML text to convert
// Preprocessing phase to set up for HTML.fromHtml(...)
text = text.replaceAll("<span style=\"(?:color: (#[a-fA-F\\d]{6})?; )?(?:font-family: (.*?); )?(?:font-size: (.*?);)? ?\">(.*?)</span>",
"<font color=\"$1\" face=\"$2\" size=\"$3\">$4</font>");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )face=\"'(.*?)', .*?\"", "face=\"$1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-small\"", "$1size=\"1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-small\"", "$1size=\"2\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"small\"", "$1size=\"3\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"medium\"", "$1size=\"4\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"large\"", "$1size=\"5\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-large\"", "$1size=\"6\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-large\"", "$1size=\"7\"");
text = text.replaceAll("<strong>(.*?)</strong>", "<_em>$1</_em>"); // we use strong for bold-face
text = text.replaceAll("<em>(.*?)</em>", "<strong>$1</strong>"); // and em for italics
text = text.replaceAll("<_em>(.*?)</_em>", "<em>$1</em>"); // but Android uses em for bold-face
text = text.replaceAll("<span style=\"background-color: #([a-fA-F0-9]{6}).*?>(.*?)</span>", "<_$1>$2</_$1>");
text_view.setText(Html.fromHtml(text, null, new Html.TagHandler() {
private List<Object> _format_stack = new LinkedList<Object>();
#Override
public void handleTag(boolean open_tag, String tag, Editable output, XMLReader _) {
if (tag.startsWith("ul"))
processBullet(open_tag, output);
else if (tag.matches(".[a-fA-F0-9]{6}"))
processBackgroundColor(open_tag, output, tag.substring(1));
}
private void processBullet(boolean open_tag, Editable output) {
final int length = output.length();
if (open_tag) {
final Object format = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void processBackgroundColor(boolean open_tag, Editable output, String color) {
final int length = output.length();
if (open_tag) {
final Object format = new BackgroundColorSpan(Color.parseColor('#' + color));
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private Object getLast(Editable text, Class kind) {
#SuppressWarnings("unchecked")
final Object[] spans = text.getSpans(0, text.length(), kind);
if (spans.length != 0)
for (int i = spans.length; i > 0; i--)
if (text.getSpanFlags(spans[i-1]) == Spannable.SPAN_MARK_MARK)
return spans[i-1];
return null;
}
private void applySpan(Editable output, int length, int flags) {
if (_format_stack.isEmpty()) return;
final Object format = _format_stack.remove(0);
final Object span = getLast(output, format.getClass());
final int where = output.getSpanStart(span);
output.removeSpan(span);
if (where != length)
output.setSpan(format, where, length, flags);
}
}));
This does seem to get the bullets, foreground color, and background color. It might work for the font-face but you might need to supply the fonts as it doesn't seem that Android supports fonts other than Droid/Roboto.
This is more of a proof-of-concept, you might probably want to convert the regex into String processing, since regex doesn't support combining the preprocessing in any way, meaning this takes a lot of passes over the String. This also doesn't seem to get the font size to change, I've tried defining it like "16sp", "medium", or "4" without seeing changes. If anyone has gotten sizes to work, mind sharing?
I currently would like to be able to add numbered/ordered list support to this, i.e.
item
item
item
NOTE:
To people starting with any of this, it seems that the "tag" that is given to handleTag(...) is just the name of the tag (like "span"), and doesn't contain any of the attributes assigned in the tag (like if you have "), you can see my loophole around this for the background color.
We have been developing internally this library https://github.com/square1-io/rich-text-android for a while now and we use it in a number of content intensive news apps.
The library can parse most common html tags including video and img with remote download of images.
A custom view, RichTextView can then be used as a replacement of TextView to display the parsed content.
We have released it publicly just recently so the doc is still incomplete however the example provided should be easy to follow to see if it fit your needs.
Although I can see it it in Html.java API that style and text-align should be usable with the tags <p> , <div> etc. I am failing to get it to work with <p align="center"> or <p style="text-align: center"> and many other variants. Not being able to do this center alignment of text, and other styles like font size, multiple font faces from my ttf files, background colour, I have made my own htmlTextView based on TextView but with my own tagHandler class. Given one or two minor irritations, most of the tags are fine but my custom alignment tags, left, centre, right work only in special conditions (that I don't understand), otherwise. they don't work or crash the app! This is my alignment tag handle. It has the same structure as all the other custom tag handlers but really behaves weirdly! The basic form of my tag handlers are the same and are not conceived by me! I found the taghandler template after many hours searching on the web. I am grateful to whoever it was that posted it but my memory and organisational ability are such that I can't really remember who or where, so if you recognise this code as yours please let me know. The only link (which is here) I have is in my code: stackoverflow: Android: How to use the Html.TagHandler?
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
I think the problem is that the end tag is not getting connected the correct start tag.
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
This is the total class and something is not right. The largest component is my understanding! Perhaps someone can help me understand better...
public class htmlTextView extends AppCompatTextView {
static Typeface mLogo;
static Typeface mGAMZ;
static Typeface mChalk;
static Typeface mSouvenir;
int GS_PAINTFLAGS = FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG | SUBPIXEL_TEXT_FLAG | HINTING_ON;
public htmlTextView(Context context) {
super(context);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialise();
}
private void initialise() {
mLogo = Typeface.createFromAsset(theAssetManager, "fonts/logo.ttf");
mGAMZ = Typeface.createFromAsset(theAssetManager, "fonts/GAMZ One.ttf");
mChalk = Typeface.createFromAsset(theAssetManager, "fonts/swapfix.ttf");
mSouvenir = Typeface.createFromAsset(theAssetManager, "fonts/Souvenir Regular.ttf");
setPaintFlags(GS_PAINTFLAGS);
}
public void setDefaultTypefaceSouvenir() {
setTypeface(mSouvenir);
}
public void setDefaultTypefaceGAMZ() {
setTypeface(mGAMZ);
}
public void setDefaultTypefaceChalk() {
setTypeface(mChalk);
}
/*public myTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
public void setHTML(String htmltext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Nougat API 24
setText(Html.fromHtml(htmltext, Html.FROM_HTML_MODE_LEGACY,
null, new TypefaceTagHandler()));
} else {
setText(Html.fromHtml(htmltext, null, new TypefaceTagHandler()));
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
public Bitmap getDrawingCache(boolean autoScale) {
return super.getDrawingCache(autoScale);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
// http://stackoverflow.com/questions/4044509/android-how-to-use-the-html-taghandler
private static class TypefaceTagHandler implements Html.TagHandler {
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTypefaceTag(Typeface tf, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new CustomTypefaceSpan("", tf), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, CustomTypefaceSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new CustomTypefaceSpan("", tf), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessScaleTag(float scalefactor, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new RelativeSizeSpan(scalefactor), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, RelativeSizeSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new RelativeSizeSpan(scalefactor), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessBox(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new BackgroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, BackgroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new BackgroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTextColour(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new ForegroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, ForegroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new ForegroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
final HashMap<String, String> attributes = new HashMap<>();
#Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
String Attr = "";
//if (!opening) attributes.clear();
processAttributes(xmlReader);
if ("txt".equalsIgnoreCase(tag)) {
Attr = attributes.get("clr");
System.out.println("clr Attr: " + Attr + ", opening: " + opening);
if (Attr == null || Attr.isEmpty()
|| "black".equalsIgnoreCase(Attr)
|| Attr.charAt(0) == 'k') {
System.out.println("did black, opening: " + opening);
ProcessTextColour(parseColor("#000000"), opening, output);
} else {
if (Attr.equalsIgnoreCase("g")) {
ProcessTextColour(parseColor("#b2b3b3"), opening, output);
} else {
System.out.println("did colour, opening: " + opening);
ProcessTextColour(parseColor(Attr), opening, output);
}
}
return;
}
if ("box".equalsIgnoreCase(tag)) {
ProcessBox(parseColor("#d7d6d5"), opening, output);
return;
}
if ("scl".equalsIgnoreCase(tag)) {
Attr = attributes.get("fac");
System.out.println("scl Attr: " + Attr);
if (Attr != null && !Attr.isEmpty()) {
ProcessScaleTag(parseFloat(Attr), opening, output);
}
return;
}
if ("left".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_NORMAL, opening, output);
return;
}
if ("centre".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_CENTER, opening, output);
return;
}
if ("right".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_OPPOSITE, opening, output);
return;
}
if ("logo".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mLogo, opening, output);
return;
}
if ("gamz".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mGAMZ, opening, output);
return;
}
if ("chalk".equalsIgnoreCase(tag)) {
System.out.println("chalk " + (opening ? "opening" : "closing"));
ProcessTypefaceTag(mChalk, opening, output);
return;
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
private void processAttributes(final XMLReader xmlReader) {
try {
Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
elementField.setAccessible(true);
Object element = elementField.get(xmlReader);
Field attsField = element.getClass().getDeclaredField("theAtts");
attsField.setAccessible(true);
Object atts = attsField.get(element);
Field dataField = atts.getClass().getDeclaredField("data");
dataField.setAccessible(true);
String[] data = (String[])dataField.get(atts);
Field lengthField = atts.getClass().getDeclaredField("length");
lengthField.setAccessible(true);
int len = (Integer)lengthField.get(atts);
/**
* MSH: Look for supported attributes and add to hash map.
* This is as tight as things can get :)
* The data index is "just" where the keys and values are stored.
*/
for(int i = 0; i < len; i++)
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
catch (Exception e) {
Log.d(TAG, "Exception: " + e);
}
}
}
private static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
public CustomTypefaceSpan(String family, Typeface type) {
super(family);
newType = type;
}
#Override
public void updateDrawState(TextPaint ds) {
applyCustomTypeFace(ds, newType);
}
#Override
public void updateMeasureState(TextPaint paint) {
applyCustomTypeFace(paint, newType);
}
private void applyCustomTypeFace(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
}
The htmlTextView is created from the activity with:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
theAssetManager = getAssets();
htmlTextView tv = new htmlTextView(this);
tv.setDefaultTypefaceSouvenir();
tv.setTextColor(BLACK);
tv.setBackgroundColor(0xfff0f0f0);
tv.setPadding(4, 4, 4, 4);
tv.setTextSize(30);
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setHTML(getString(R.string.htmljumblies));
//tv.setHTML(getString(R.string.htmltest));
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
rl.addView(tv);
}
and htmljumblies is defined in strings.xml as below. This particular version will crash the app but if the first <centre>, </centre> tags are removed from lines 7 and 9, The Jumblies will appear centralised? Confusing and frustrating! Keep them and remove the <centre>, </centre> tags enfolding The Jumblies and nothing happens. The line in the header is not centrally aligned!
<string name="htmljumblies">
<![CDATA[&DoubleLongRightArrow;<logo><scl fac="1.1"><font color="#e5053a">GAMZ</font></scl></logo>
<chalk><scl fac="1.8"> SWAP </scl></chalk>
<scl fac="1.00">Set <b>1</b>, Game <b>1</b></scl>
<br>
<centre>
<gamz><font color="#e5053a"><scl fac="1.50">a</scl></font><scl fac="0.90">(9)</scl></gamz>, <gamz><font color="#00a3dd"><scl fac="1.50">e</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#fba311"><scl fac="1.50">i</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bc5e1e"><scl fac="1.50">o</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bf30b5"><scl fac="1.50">u</scl></font><scl fac="0.90">(9)</scl></gamz>
</centre>
<br>
This is an example of my custom <b>htmlTextView</b> drawn from HTML format
text with custom tags to use custom fonts, colouring typeface sizing and highlight boxes.
The default font is <b><i>Souvenir</i></b>, but 3 other fonts are used:<br>
The <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
<font color="#000080"><gamz><scl fac="0.8"><box>letter</box>
<box>fonts</box><sc></gamz></font>
and <chalk><scl fac="1.8">swapfix</scl></chalk>, essentially
<chalk><scl fac="0.9">Staccato 555</scl></chalk>,
as used in the words <chalk><scl fac="1.2">SWAP</scl></chalk> and
<chalk><scl fac="1.2">FIX</scl></chalk>
on the <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
boxes.
<br>
<centre>
<scl fac="2"><box><b> <u>The Jumblies</u> </b></box></scl><br>
<font color="#0000ff">
They went to sea in a Sieve, they did,<br>
In a Sieve they went to sea:<br>
In spite of all their friends could say,<br>
On a winter\'s morn, on a stormy day,<br>
In a Sieve they went to sea!<br>
And when the Sieve turned round and round,<br>
And every one cried, \'You\'ll all be drowned!\'<br>
They called aloud, \'Our Sieve ain\'t big,<br>
But we don\'t care a button! we don\'t care a fig!<br>
In a Sieve we\'ll go to sea!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed away in a Sieve, they did,<br>
In a Sieve they sailed so fast,<br>
With only a beautiful pea-green veil<br>
Tied with a riband by way of a sail,<br>
To a small tobacco-pipe mast;<br>
And every one said, who saw them go,<br>
\'O won\'t they be soon upset, you know!<br>
For the sky is dark, and the voyage is long,<br>
And happen what may, it\'s extremely wrong<br>
In a Sieve to sail so fast!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
The water it soon came in, it did,<br>
The water it soon came in;<br>
So to keep them dry, they wrapped their feet<br>
In a pinky paper all folded neat,<br>
And they fastened it down with a pin.<br>
And they passed the night in a crockery-jar,<br>
And each of them said, \'How wise we are!<br>
Though the sky be dark, and the voyage be long,<br>
Yet we never can think we were rash or wrong,<br>
While round in our Sieve we spin!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And all night long they sailed away;<br>
And when the sun went down,<br>
They whistled and warbled a moony song<br>
To the echoing sound of a coppery gong,<br>
In the shade of the mountains brown.<br>
\'O Timballo! How happy we are,<br>
When we live in a Sieve and a crockery-jar,<br>
And all night long in the moonlight pale,<br>
We sail away with a pea-green sail,<br>
In the shade of the mountains brown!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed to the Western Sea, they did,<br>
To a land all covered with trees,<br>
And they bought an Owl, and a useful Cart,<br>
And a pound of Rice, and a Cranberry Tart,<br>
And a hive of silvery Bees.<br>
And they bought a Pig, and some green Jack-daws,<br>
And a lovely Monkey with lollipop paws,<br>
And forty bottles of Ring-Bo-Ree,<br>
And no end of Stilton Cheese.<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And in twenty years they all came back,<br>
In twenty years or more,<br>
And every one said, \'How tall they\'ve grown!<br>
For they\'ve been to the Lakes, and the Torrible Zone,<br>
And the hills of the Chankly Bore!\'<br>
And they drank their health, and gave them a feast<br>
Of dumplings made of beautiful yeast;<br>
And every one said, \'If we only live,<br>
We too will go to sea in a Sieve,---<br>
To the hills of the Chankly Bore!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.</centre></font>
]]>
</string>

AS3: Optimizing Object Memory Size

I have have a class that I wrote, and it seems bigger than it should be. It doesn't extend anything, and has very little going on - or so I thought - but each one is taking up just under 100k100 bytes ( thanks back2dos ). I guess that I don't have a very good understanding of what really affects how much memory an object takes up in AS3.
If anyone can point me to some reading on the subject that might be helpful, or perhaps explain some insight into how to think about this, that would be awesome.
I would like to keep a LOT of these objects in memory - and I thought I could until now, but at this size I'm going to have to create them or use an object pooling technique of some kind.
Thanks for the assistance.
Edit: Although I've got this in order, I'm keeping the code I posted here for completeness. The class has been heavily modified from the original version. Values that were referencing other files have been made static as to allow the code to run for someone else ( in theory hehehe... ).
Although my situation is sorted out, I'll give the answer to a good reference for information on classes and memory.
In this case the class has 15 variables. I'm only using a single String and a bunch of ints, Numbers, and Booleans with some references to more of the same in globally available XML data. It also imports Point for the constructor, though no points are stored. In testing, even without the global XML references or Point class it's still around a ~84k each. There are getters for 7 of the variables and a couple methods in addition to the constructor. All of which are less than 20 lines ( and I have a very sparse coding style ).
The class mentioned for reference, but feel free to generalize:
package
{
public class AObject
{
private var _counter:int;
private var _frames:int;
private var _speed:int;
private var _currentState:String;
private var _currentFrame:int;
private var _offset:int;
private var _endFrame:int;
private var _type:int;
private var _object:int;
private var _state:int;
private var _x:Number;
private var _y:Number;
private var _w:int;
private var _h:int;
private var _update:Boolean;
public function AObject( targetX : int, targetY : int, state : int, object : int, type : int )
{
_x = targetX;
_y = targetY;
_type = type;
_object = object;
_state = state;
_counter = 0;
_w = 32;
_h = 32
_update = true;
setState( state );
}
public function setState( state:int ) : void
{
_currentState = "bob";
var frameCounter : int = 0;
var stateCounter : int = state - 1;
while ( state > 0 )
{
frameCounter += 4;
--stateCounter;
}
_offset = frameCounter;
_currentFrame = _offset;
_speed = 10;
_frames = 4;
_endFrame = _offset + _frames - 1;
}
public function get state() : int
{
return _state;
}
public function animate() : Boolean
{
if ( count() )
{
if( _currentFrame < _endFrame )
{
++_currentFrame;
}
else
{
_currentFrame = _offset;
}
_speed = 10;
return true;
}
else
{
return false;
}
}
private var adder: Number = 0;
private function count():Boolean
{
_counter++;
if ( _counter == _speed )
{
_counter = 0;
return true;
}
else
{
return false;
}
}
public function get x():int
{
return _x;
}
public function get y():int
{
return _y;
}
public function get type():int
{
return _type;
}
public function get object():int
{
return _object;
}
public function get currentFrame():int
{
return _currentFrame;
}
public function get w():int
{
return _w;
}
public function get h():int
{
return _h;
}
}
}
i am amazed, this compiles at all ... when i try to compile it with the flex SDK, it creates an enormous collision with the built-in class Object, which is the base class of any class, making my trace output overflow ...
other than that, this is an infinite loop if you pass a value for state bigger than 0
while ( state > 0 )
{
frameCounter += 4;
--stateCounter;
}
but it seems really strange these objects are so big ... after renaming and taking care not to pass in 0 for the state, i ran a test:
package {
import flash.display.Sprite;
import flash.sampler.getSize;
import flash.system.System;
public class Main extends Sprite {
public function Main():void {
const count:int = 100000;
var start:uint = System.totalMemory;
var a:Array = [];
for (var i:int = 0; i < count; i++) {
a.push(new MyObject(1, 2, 0, 4, 5));
}
var mem:uint = System.totalMemory - start - getSize(a);
trace("total of "+mem+" B for "+count+" objects, aprox. avg. size per object: "+(mem/count));
}
}
}
it yields:
total of 10982744 B for 100000 objects, aprox. avg. size per object: 109.82744
so that's quite ok ... i think the actual size should be 4 (for the bool) + 4 * 11 (for the ints) + 4 (for the reference to the string) + 8 * 3 (for the three floats (you have the adder somewhere over the count) + 8 for an empty class (reference to the traits objects + something else), giving you a total of 88 bytes ... which is, what you get, if you getSize the object ... please note however, that getSize will only give you the size of the object itself (as calculated here) ignoring the size of what strings or other objects your object references ...
so yeah, apart from that name you definitely should change, the problem must be somewhere else ...
greetz
back2dos
If you really want to save on space, you can fake shorts by using unsigned integers, and using upper/lower bits for one thing or another.
ints are 4 bytes by nature, you can reuse that int on anything less than 2^8.
width height
0xFFFF + 0xFFFF
offset endframe
0xFFFF + 0xFFFF
This though gets ugly when you want to write anything or read anything, as to write width or height you'd have to:
writing:
size = (width & 0x0000FFFF) << 16 | (height & 0x0000FFFF);
reading:
get width():uint { return (size & 0xFFFF0000) >> 16 };
That's ugly. Since you're using getters anyways, and assuming computation speed is not an issue, you could use internal byte arrays which could give you even more granularity for how you want to store your information. Assuming your strings are more than 4 bytes, makes more sense to use a number rather than a string.
Also, I believe you will actually get some memory increase by declaring the class as final, as I believe final functions get placed into the traits object, rather than