[Galaxy S II Android Development] Goldie:
OK....time for part 2 :D

COLORING IMAGES

This part will cover two things. Coloring images in a similar way to setting text colors from part 1 and also how to create a method and pass it context. Sometimes you wont be able to use mContext or getcontext but most smali files have context already in their init so you can use that and pass it to a new method. Using your own method is more efficient as you can run your code to get the color from the database just once and use it on multiple items and in multiple places.

There are three main ways to color an image. Using the method setColorFilter(I)V which is in android/widget/ImageView smali in framework2.jar, using method public final setColorFilter(ILandroid/graphics/PorterDuff$Mode;)V in the same file and using method public setColorFilter(ILandroid/graphics/PorterDuff$Mode;)V in android/graphics/drawable/Drawable smali in framework.jar. It depends what and where the image is as to which you use. All three methods overlay a color on an existing image and the two with porterduffmode have a means of choosing what effect the overlay has. There is more about this at the bottom of the post.

Lets start....

Things are stored in fields in a smali. At the top of the file you will see them like this:

.field protected mType:Ljava/lang/String;

.field protected mView:Landroid/view/View;

.field protected mIcon:I

These fields can be used to store things of their type. So in the ones shown above you would put a string, a view and an integer respectively. You can see the type a field should hold after the :

In order to use your own method to get the color from the database we need to add a new field to store the value in so we can use it in another method. For this example i am going to use the toggle icons in lidroids toggle mod but this can be done on pretty much any image you want.

At the top of the smali with the other instance fields add your new field. You can call it whatever you want and it is going to store an integer so you will need an I after the colon. Im calling this one mToggleColor for obvious reasons:

.field private mToggleColor:I

We now have somewhere to put our value so now we can write the new method. At this point you should refer to the text color guide for explanations about the content resolver, context and registers as I will assume you have an idea about these now. :P

You can call your method anything you want. It will need 3 registers to store the resolver, default value and integer. This method has no context parameter (in the brackets after the method name) so you would need to use getcontext or mContext....

.method color_toggles()V
.locals 3

If you are going to use existing context and pass it to your method it would look like this. As you can see the method is expecting context as its parameter when called...

.method color_toggles(Landroid/content/Context;)V
.locals 3

Now we get the content resolver so we need context. If using the first method then you would need to call your view and invoke getcontext like this in stock toggles:

iget-object v1, p0, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->mBtnImage:Landroid/widget/ImageView;

invoke-virtual {v1}, Landroid/widget/ImageView;->getContext()Landroid/content/Context;

move-result-object v1

invoke-virtual {v1}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v1

As metioned in the text color guide your contentresolver is now in v1.

If you are passing context to your method then the context is the parameter of the method. Each item in brackets after methods name is stored in a p. So in this case the context would be in p1. All you need to do is use it to get the resolver:

invoke-virtual {p1}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v1

Again...resolver now in v1.

The next part is identical to the text color guide so i wont repeat it. But you should have now something that looks like this:

.method color_toggles()V
.locals 3

iget-object v1, p0, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->mBtnImage:Landroid/widget/ImageView;

invoke-virtual {v1}, Landroid/widget/ImageView;->getContext()Landroid/content/Context;

move-result-object v1

const-string v2, "theme_color"

const v3, -0x4d06ff

invoke-static {v1, v2, v3}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I

move-result v1

or if passing context:

.method color_toggles(Landroid/content/Context;)V
.locals 3

invoke-virtual {p1}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v1

const-string v2, "theme_color"

const v3, -0xcc4a1a

invoke-static {v1, v2, v3}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I

move-result v1

Both of the above now have your color stored in v1. We can now put it into your new field that you created earlier using iput:

iput v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mToggleColor:I

This puts the integer in v1 into the field in p0 (this file) and stores it as mToggleColor

Your method is not expected to return anything meaning something doesnt call it and ask for a value in reply so after this we need:

return-void

....and then to finish the method....

.end method

The final methods look like this:

.method color_toggles()V
.locals 3

iget-object v1, p0, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->mBtnImage:Landroid/widget/ImageView;

invoke-virtual {v1}, Landroid/widget/ImageView;->getContext()Landroid/content/Context;

move-result-object v1

const-string v2, "theme_color"

const v3, -0x4d06ff

invoke-static {v1, v2, v3}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I

move-result v1

iput v1, p0, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->mToggleColor:I

return-void

.end method

.method color_toggles(Landroid/content/Context;)V
.locals 3

invoke-virtual {p1}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver;

move-result-object v1

const-string v2, "theme_color"

const v3, -0xcc4a1a

invoke-static {v1, v2, v3}, Landroid/provider/Settings$System;->getInt(Landroid/content/ContentResolver;Ljava/lang/String;I)I

move-result v1

iput v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mColor:I

return-void

.end method

Now your color can be put into your new field we need to invoke your new method. Otherwise it will just be an empty field. You can do this in a number of places. Either directly before you use it or in the methods init. We will come back to this.

In order to color an image you need to find it first. There are ways of calling the image by its id but looking for it this way will familiarise you more with smali. Images can be set using setImageResource and setImageDrawable. If you search firstly for the images id in the smalis to find the correct file and if that doesnt find anything then have a look at file names and see what you can find that looks like it might be right. In this case PowerButton smali has a method called :

.method private updateImageView(II)V

...which uses setImageResource(I)V. Sounds like a good place to start!

.method private updateImageView(II)V
.locals 2

iget-object v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mView:Landroid/view/View;

invoke-virtual {v1, p1}, Landroid/view/View;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/ImageView;

invoke-virtual {v0, p2}, Landroid/widget/ImageView;->setImageResource(I)V

return-void
.end method

We need to invoke your new method. If you are not passing context you can just do this:

invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->color_toggles()V

...which will invoke your new method and put your color in your field.

Or to pass context you would do:

invoke-virtual {v1}, Landroid/view/View;->getContext()Landroid/content/Context;

move-result-object v1

invoke-virtual {p0, v1}, Lcom/alliance/systemui/quickpanel/PowerButton;->color_toggles(Landroid/content/Context;)V

In the above code the method you are in has a Landroid/view/View; stored in v1. You can use getContext on this and then invoke your method. The invoke says to call your method color_toggles(Landroid/content/Context;)V which is in this file (p0) with the parameter v1 (the context). Now your method has run we can use iget to get the color from the field:

iget v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mColor:I

You can see in the original updateImage method that the imageView is in v0. We can now apply the color (in v1) to the image (v0).

invoke-virtual {v0, v1}, Landroid/widget/ImageView;->setColorFilter(I)V

Final methods would look like this:

.method private updateImageView(II)V
.locals 2

invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/quicksetting/QuickSettingButton;->color_toggles()V

iget-object v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mView:Landroid/view/View;

invoke-virtual {v1, p1}, Landroid/view/View;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/ImageView;

invoke-virtual {v0, p2}, Landroid/widget/ImageView;->setImageResource(I)V

iget v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mColor:I

invoke-virtual {v0, v1}, Landroid/widget/ImageView;->setColorFilter(I)V

return-void
.end method

or

.method private updateImageView(II)V
.locals 2

iget-object v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mView:Landroid/view/View;

invoke-virtual {v1, p1}, Landroid/view/View;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/ImageView;

invoke-virtual {v0, p2}, Landroid/widget/ImageView;->setImageResource(I)V

invoke-virtual {v1}, Landroid/view/View;->getContext()Landroid/content/Context;

move-result-object v1

invoke-virtual {p0, v1}, Lcom/alliance/systemui/quickpanel/PowerButton;->color_toggles(Landroid/content/Context;)V

iget v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mColor:I

invoke-virtual {v0, v1}, Landroid/widget/ImageView;->setColorFilter(I)V

return-void
.end method

Your source image should be white. You can make them semi transparent so the color is not as bright. For example the off toggles could be 50% transparent so they will only appear dim compared to the on ones.

You will notice in PowerButton smali there is another method....

.method private updateImageView(ILandroid/graphics/drawable/Drawable;)V
.locals 2

iget-object v1, p0, Lcom/alliance/systemui/quickpanel/PowerButton;->mView:Landroid/view/View;

invoke-virtual {v1, p1}, Landroid/view/View;->findViewById(I)Landroid/view/View;

move-result-object v0

check-cast v0, Landroid/widget/ImageView;

invoke-virtual {v0, p2}, Landroid/widget/ImageView;->setImageDrawable(Landroid/graphics/drawable/Drawable;)V

return-void
.end method

This is the same except it uses 2 parameters (in the brackets after the name) of and (I)nteger and Landroid/graphics/drawable/Drawable rather than two (I)ntegers.

You don't HAVE to use your own method. You could just use identical code to the textColor code straight after the setImageResource and invoke setColorFilter on the ImageView rather than setTextColor on a TextView. But I think it is good practice to use a new method :)

PorterDuffMode

Sometimes when you implement a color picker you will get an outcome you dont want like a battery that had a a white centre and the fill in a color wouldnt show the level as it would all have the same color overlay. Instead of using setColorFilter(I)V you can use setColorFilter(ILandroid/graphics/PorterDuff$Mode;)V.

If you are familiar with Photoshop you may be better than me at this but basically it changes the way the color is laid over. Using different modes will give different results. As Im not great with Photoshop for me its more trial and error!

There are lots of different modes:

PorterDuff.Mode	ADD	Saturate(S + D)

PorterDuff.Mode	CLEAR	[0, 0]

PorterDuff.Mode	DARKEN	[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

PorterDuff.Mode	DST	[Da, Dc]

PorterDuff.Mode	DST_ATOP	[Sa, Sa * Dc + Sc * (1 - Da)]

PorterDuff.Mode	DST_IN	[Sa * Da, Sa * Dc]

PorterDuff.Mode	DST_OUT	[Da * (1 - Sa), Dc * (1 - Sa)]

PorterDuff.Mode	DST_OVER	[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]

PorterDuff.Mode	LIGHTEN	[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]

PorterDuff.Mode	MULTIPLY	[Sa * Da, Sc * Dc]

PorterDuff.Mode	OVERLAY

PorterDuff.Mode	SCREEN	[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]

PorterDuff.Mode	SRC	[Sa, Sc]

PorterDuff.Mode	SRC_ATOP	[Da, Sc * Da + (1 - Sa) * Dc]

PorterDuff.Mode	SRC_IN	[Sa * Da, Sc * Da]

PorterDuff.Mode	SRC_OUT	[Sa * (1 - Da), Sc * (1 - Da)]

PorterDuff.Mode	SRC_OVER	[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]

PorterDuff.Mode	XOR	[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]

The letters in CAPITALS are the what you put as the parameter.

I have found that MULTIPLY is the most useful. SRC_ATOP is used as default in setColorFilter(I)V

To use one you need to put the mode into a register. You do that using sget:

sget-object v1, Landroid/graphics/PorterDuff$Mode;->MULTIPLY:Landroid/graphics/PorterDuff$Mode;

Then you call the porterduff setcolorfilter method instead of the normal one. Like this:

invoke-virtual {v6, v12, v1}, Landroid/widget/ImageView;->setColorFilter(ILandroid/graphics/PorterDuff$Mode;)V

...instead of this:

invoke-virtual {v6, v12}, Landroid/widget/ImageView;->setColorFilter(I)V

There is a good article here

Please ask if anything is unclear. :)