I need to scale my TabWidget background images so they maintain aspect ratio.
I am using a TabHost with a TabWidget. I am then using setBackgroundDrawable to set the images.
I found a close answer here - Background in tab widget ignore scaling. However, I'm not sure just where to add the new Drawable code. (Working with the HelloTabWidget example, none of my modules use RelativeLayout, and I don't see any layout for "tabcontent".)
I also found this thread - Android: Scale a Drawable or background image?. According to it, it sounds like I would have to pre-scale my images, which defeats the whole purpose of making them scaleable.
I also found another thread where someone subclassed the Drawable class so it would either not scale, or it would scale properly. I can't find it now, but that seems like a LOT to go through when you should just be able to do something simple like mTab.setScaleType(centerInside).
Here's my code:
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/main_background">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"/>
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>
</LinearLayout>
</TabHost>
main activity:
tabHost.setOnTabChangedListener(new OnTabChangeListener() {
TabHost changedTabHost = getTabHost();
TabWidget changedTabWidget = getTabWidget();
View changedView = changedTabHost.getTabWidget().getChildAt(0);
public void onTabChanged(String tabId) {
int selectedTab = changedTabHost.getCurrentTab();
TabWidget tw = getTabWidget();
if(selectedTab == 0) {
//setTitle("Missions Timeline");
View tempView = tabHost.getTabWidget().getChildAt(0);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_timeline_on));
tempView = tabHost.getTabWidget().getChildAt(1);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_map_off));
tempView = tabHost.getTabWidget().getChildAt(2);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_search_off));
tempView = tabHost.getTabWidget().getChildAt(3);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_news_off));
tempView = tabHost.getTabWidget().getChildAt(4);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_license_off));
//ImageView iv = (ImageView)tabHost.getTabWidget().getChildAt(0).findViewById(android.R.id.icon);
//iv.setImageDrawable(getResources().getDrawable(R.drawable.tab_timeline_on));
//iv = (ImageView)tabHost.getTabWidget().getChildAt(1).findViewById(android.R.id.icon);
//iv.setImageDrawable(getResources().getDrawable(R.drawable.tab_map_off));
} else if (selectedTab == 1) {
//setTitle("Spinoffs Around You");
View tempView = tabHost.getTabWidget().getChildAt(0);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_timeline_off));
tempView = tabHost.getTabWidget().getChildAt(1);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_map_on));
tempView = tabHost.getTabWidget().getChildAt(2);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_search_off));
tempView = tabHost.getTabWidget().getChildAt(3);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_news_off));
tempView = tabHost.getTabWidget().getChildAt(4);
tempView.setBackgroundDrawable(getResources().getDrawable(R.drawable.tab_license_off));
}
I also tried 9patch images, but they wind up being too small.
So, what's the best way to go about this?
Check this out and let me know whether it worked for you.
Custom Android Tab
I suspect the reason you're struggling to find an out-of-the-box way to perform background image scaling is that it's not generally seen as good practice. Scaling of images tends to introduce artifacts and banding which can make your UI look nasty. The exception to this is within an Imageview, but I would argue the scaling support this class provides is more intended to support things like photos or web content (i.e. images you have not shipped with your app).
Your UI layout should be designed in such a way that any resource-based background images should be pre-scaled to the correct resolution for the density / screen size of the device. In other words you should be providing multiple versions of each drawable to cater for multiple screen densities and sizes using the resource folder naming convention outlined in the Supporting Multiple Screens dev guide.
The UI should then be laid out such that any slight differences of screen size between devices can be handled without needing to scale background images of its contained views. Obviously I don't know what your proposed UI looks like so it's difficult to make any concrete suggestions, but generally I use simple background block colors or ShapeDrawables to dynamically fill the space between views.
However, if you're really really sure you want to scale your background images despite the preaching above :-), why not try using a ScaleDrawable to wrap your existing drawable?
If you know the height and width of both your view and background image drawable, then you can do something like:
Drawable backgroundDrawable = getResources().getDrawable(R.drawable.tab_license_off);
tempView.setBackgroundDrawable(
new ScaleDrawable(
backgroundDrawable,
Gravity.CENTER,
tempView.getWidth() / backgroundDrawable.getIntrinsicWidth(),
tempView.getHeight() / backgroundDrawable.getIntrinsicHeight());
Related
My app has an Activity that displays a vertically scrolling list of ImageButtons. I want each buttons image to (A) come from the assets folder and (B) retain it's aspect ratio as it scales. Unfortunately, my ImageButton doesn't size correctly when it's image comes from the assets folder.
ImageButton src set from drawable
The first screenshot is my test app where the images all come from my apps drawables. That's the "correct" aspect ratio for that image, which is what I want to keep (all of the image buttons have been given scaleType "fitXY" "centerCrop").
ImageButton src set from assets
The second screenshot is my test app where the images all come from my apps "assets" folder — as you can see, the images are stretched out to the full width of the screen as desired, but the original aspect ratio has been lost:
Activity code (MainActivity.java)
LinearLayout buttons = (LinearLayout) findViewById(R.id.buttons);
View button = layoutInflater.inflate(R.layout.snippet_imagebutton, null);
ImageButton imageButton = (ImageButton) button.findViewById(R.id.imageButton);
if (GET_IMAGE_FROM_ASSETS) {
InputStream s = assetManager.open("image.png");
Bitmap bmp = BitmapFactory.decodeStream(s);
imageButton.setImageBitmap(bmp);
} else {
imageButton.setImageResource(R.drawable.image);
}
TextView buttonText = (TextView) button.findViewById(R.id.textView);
buttonText.setText("Button text!");
buttons.addView(button,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
Button layout (snippet_imagebutton.xml)
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageButton
android:id="#+id/imageButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:gravity="center"
android:textColor="#FFF"
android:background="#88000000"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
Dodgy hack
I've found a dodgy hack which achieves what I want and illustrates the problem — the ImageButton will size correctly to match a scaled up image from the resources, but it won't size correctly to match a scaled up image from the assets. I'd prefer a "real" solution over the dodgy hack if there is one. :)
// to start with, scale the ImageButton based on an image in our
// resources that has the same dimensions as the image in our assets
imageButton.setImageResource(R.drawable.image);
// when we eventually render (we don't have width / height yet)...
imageButton.post(new Runnable() {
public void run() {
int height = imageButton.getHeight();
int width = imageButton.getWidth();
// replace our "resource" image with our "asset" image
imageButton.setImageBitmap(bmp);
// and force the ImageButton to keep the same scale as previously
imageButton.setLayoutParams(
new RelativeLayout.LayoutParams(width, height));
}
});
Summary
I want to get the images for these buttons from my apps "assets" folder. How do I fix my app so that the button images are all scaled properly (i.e. retain their original aspect ratio)?
I'm assuming this has something to do with the framework not actually knowing the width / height of the image before it renders the ImageButton to screen — so how do I fix that? I tried setting adjustViewBounds to "true" on both the RelativeLayout and the ImageButton itself, but that didn't seem to have any effect.
The aspect ratio is not kept when you give the scale type attribute as "fitXY". Try "center", "center_crop" or "center_inside" according to your needs. Check ImageViewScaleType as well.
From what I gather, AdjustViewBounds is actually the method that provides the exact functionality you need. However, as Roman Nurik explains here, AdjustViewBounds does NOT increase the ViewBounds beyond the natural dimensions of the drawable.
Without seeing how you allocated resources/assets, my suspicion would be that this is why you see a difference when loading from Resources and when loading from Assets. Your resource file is - presumably - being scaled up when you load it (since that is the default behavior for resources), so that the actual size of the drawable returned from /res is significantly larger than the bitmap you decode from /assets.
Overriding ImageButton (as Nurik suggests), is of course one option, but probably overkill.
Assuming I am correct about the problem, you should be able to fix it simply by setting your BitmapFactory.options correctly when you load the Assets file. You need to set inDensity and inTargetDensity correctly according to your device (get DisplayMetrics), and set inScaled to true.
Alternatively, you can always rescale the bitmap manually before loading it on the ImageButton. Just grab the screen width to determine how large you need to make it.
P.S.
Auto-scaling is obviously the elegant solution, but it does have a weakness (which applies equally to both resources and assets) - if the width of your display > bitmap witdh when scaled up, you'll likely still have the aspect ratio messed up. You can check this pretty easily by going to landscape mode and seeing whether the resource file still result in the right aspect ratio. If you have a large enough base image (or use different layouts according to the screen size/orientation), then this should not be a problem though. Just something to keep in mind.
I have developed this custom ImageView class to override some of the default behavior to fit my needs. Let me describe what this custom ImageView does...
Let's say you have a bunch of icons to display in GridView both in the drawable-mdpi and drawable-hdpi folder, they are 48x48px and 72x72px in size, respectively. There are no icons available in the drawable-xhdpi folder. The GridView attributes are so that all the icons size will be in 48x48dp (this will translate to 48px, 72px and 96px for mpdi, hdpi and xhdpi densities, respectively).
Since there are no icons in the drawable-xhdpi folder, when this app is ran on a device with such density, the icons will be pulled from the drawable-hdpi folder. And since they are only 72px and the xhdpi devices are expecting 96px images, the icons will be stretched to fill the remaining pixels.
This is the behavior my custom ImageView attempts to override. With my custom component, what will happen is that the images will simply not get stretched. For instance, in the example above using my class, each ImageView inside the GridView will still be 96x96px (because of the 48x48dp size defined) but the images used are from the drawable-hdpi folder which are 72x72px. What will happen is that these images from the drawable-hdpi folder will be placed in the center of the ImageView which is 96x96px in size without stretching the image to fit the whole view size.
If the above is confusing, let's try with a few pictures. The example below does not use GridView, I'm trying to simplify the idea behind my custom class. These are the source pictures I'm using for this example:
This is the result on HDPI device:
And this is the result on XHDPI device:
The code for the layout on the screenshots above is this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Standard ImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Custom UnscaledImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
</LinearLayout>
Is it more clear now? This is what I want to do and this is working nicely, besides a small performance issue... Now let me post the code I'm using for such custom component:
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="UnscaledImageView">
<attr name="android:src" />
</declare-styleable>
</resources>
UnscaledImageView.java:
public class UnscaledImageView extends ImageView {
private int mDeviceDensityDpi;
public UnscaledImageView(Context context) {
super(context);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
}
public UnscaledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.UnscaledImageView);
int resourceId = styledAttrs.getResourceId(R.styleable.UnscaledImageView_android_src, 0);
if(resourceId != 0) {
setUnscaledImageResource(resourceId);
}
styledAttrs.recycle();
}
public void setUnscaledImageResource(int resId) {
setImageBitmap(decodeBitmapResource(resId));
}
#SuppressWarnings("deprecation")
public void setUnscaledBackgroundResource(int resId) {
BitmapDrawable drawable = new BitmapDrawable(null, decodeBitmapResource(resId));
drawable.setTargetDensity(mDeviceDensityDpi);
drawable.setGravity(Gravity.CENTER);
setBackgroundDrawable(drawable);
}
private Bitmap decodeBitmapResource(int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = mDeviceDensityDpi;
return BitmapFactory.decodeResource(getResources(), resId, options);
}
}
So, this class will do it's thing if the UnscaledImageView view is used in XML layouts or directly initialized in code. I've also provided 2 methods so the image can be changed in code while keeping it from being stretched. As you can see, these methods only take resource ids, so far I haven't felt the need to use drawables or bitmaps directly.
Now the real issue I'm having with this...
If this class is used as single image view in some layout, no problem, it's only decoding one image. But if it's used in a GridView where there can be like 40 icons (I'm taking this value from what really happens on my app running on my xhdpi device) visible at the same time, scrolling the GridView will be very slow because the decodeBitmapResource() is calling BitmapFactory.decodeResource() for each and every image.
This is my problem and that is my question. How can I optimize this? If possible, at all...
Putting those images into the res/drawable-nodpi/ could do what you want (I'm saying to put different resolution images side by side).
It would be a bit tricky because probably you'd have to follow a naming convention to be able to find the best resource for a given image that you are trying to draw. Probably this will require you to try finding images by their name and that's not a very efficient way to retrieve resources.
The way I imagin this is: on the layout (or anywhere else), you specify the name (string, not id!) of the image resource you want to use.
In that nodpi folder, you'd have the images with a suffix for the intended screen density.
Then, in the setter method you have to try different combinations in order to find the best available resource.
Problem for you to think: what if you're scaling down an image? The resource would be bigger than the view where you'd draw it!
Although the answer by Pedro Loureiro could be a possible solution, I decided to take a different approach after realizing something...
I've timed both the native ImageView loading and my UnscaledImageView loading in a GridView, non-scientifically of course and I've realized that my class loads all the images a little bit faster than the native one. Maybe the native methods have something else going on, besides decoding the resource (they still have to do it, right?) while my class simply decodes a resource (using BitmapFactory) and that's basically it.
I thought that it was my class that was making the GridView kinda slow while scrolling but after a few more tests, using the original ImageView without any tweaks, also revealed to be a little choppy while scrolling the GrivView.
The solution I found to solve this issue (either with my class or the native one) was to cache the icons in the GridView and for that I used the LruCache which is the recommended way of caching images in a GridView.
So that's the solution I'll be using to solve my issue. For more details please refer to the official training guide: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
For reference, I've also found the following tutorial useful: http://andrewbrobinson.com/2012/03/05/image-caching-in-android/
Situtation:
I am working on a small application which should enable user to trigger something depending on which part of the screen they clicked. Imagine a picture of a teddy bear and if you click a nose it says "nose". What I have done is put in layout xml a LinearLayout. Because I have to cover different screen sizes I didn't set background in a xml layout file.
snippet form xml:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewMain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="visible" >
<LinearLayout
android:id="#+id/viewThouShaltRespondToClicks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:orientation="vertical"
android:longClickable="true"
android:visibility="visible" />
<!-- mask -->
<LinearLayout
android:id="#+id/viewInvisibleMask"
android:visibility="invisible"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
And then in onCreate event of activity I check the display resolution and via setBackgroundResource() set the appropriate background. I have prepared background images for each resolution.
final Point QVGA = new Point(240,320); // portrait
// Obtain the screen resolution if the device
Display defaultDisplay = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point displayResolution = new Point(defaultDisplay.getWidth(), defaultDisplay.getHeight());
if (displayResolution.equals(QVGA))
{
viewThouShaltRespondToClicks.setBackgroundResource(R.drawable.image_to_be_clicked_upon_240x320);
viewInvisibleMask.setBackgroundResource(R.drawable.mask_240x320);
}
else if ... // check for another resolution
Even if the image in the resources is not in the correct dimension it is stretched/shrinked to the screen size. This would be OK as long as the width-height ratio of background image is similar to ratio of display and the resulting image is not kewed to much. But there is a problem, explained below.
To detect which region was clicked, the region itself might be irregular shaped I have taken the following approach. I create an image – mask (bmp) with same dimensions as background image only that it has a white background and the clickable areas are in different colors. The color identifies the area. All I have to is get a coordinate of a click event (no problem here), go to the mask image and read the color of pixel on this coordinates. The problem is that the mask image is not of the correct size. On my device it is set to 1200x700, but I guess it takes on some arbitrary size on other devices.
First question: Is there a way to somehow convince the invisible layout to load background image and then stretch/shrink it to display size as it happens for visible layout by itself?
Another approach would be to load mask image (bmp, png) into some memory structure and resize it to display size.
I have tried with something like:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // do not scale
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmapMask = BitmapFactory.decodeResource(this.getResources(), R.drawable.mask, options);
// on this place stretch shrink should follow but I have no idea how
But I don’t know how to scale Bitmap to proper size.
Any suggestions?
First question: yes, you can have a separate View in your layout containing the mask image, and set it to be invisible with android:visibility=invisible.
Second question: you can read a pixel value from the bitmap with Bitmap.getPixel(). Docs are here.
Just a simple suggestion. You could have the reference image in an imageview behind the real image. So both images are loaded in the exact same way, placed in ImageViews, and then inserted into a relativelayout. That way they should be equal size, and only one of them is visible.
This might not be a very pretty way of doing it, and i'm not sure if it will work, but you can try it out.
I have a RelativeLayout which holds an ImageView and an ImageButton. The ImageView serves as an container for a background image. Now I'm trying to set the button at a fixed position so that it always appears on the same position on the background image.
Here is the layout file I'm using:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/relativeLayout1" android:layout_height="match_parent"
android:layout_width="match_parent" android:gravity="center_horizontal">
<ImageView android:src="#drawable/bg_1" android:id="#+id/imgView"
android:adjustViewBounds="true" android:layout_height="match_parent"
android:layout_width="match_parent" />
<ImageButton android:layout_width="120dp"
android:background="#drawable/button_1" android:layout_height="30dp"
android:id="#+id/imgButton" android:layout_marginLeft="100dp"
android:layout_marginTop="170dp" />
</RelativeLayout>
As you can see I've tried positioning the button with it's left-/top-margin using dp as unit, but this doesn't work. Since the background image is beeing scaled down/up, the position would have to be dynamic in some kind of way.
I understand that absolute positioning, with pixel-values for x-/y-position, is something that won't work on Android, like it is explained here. I still need to solve this and am not sure how.
Would I have to calculate the values for left-/top-margin (not sure how that would be) and then set them with something like this?
final float density = getResources().getDisplayMetrics().density;
int width = (int)((float)120 * density);
int height = (int)((float)120 * density);
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(width, height);
rlp.leftMargin = newMargin;
rlp.topMargin = newTopMargin;
ImageButton imgButton = (ImageButton) findViewById(R.id.imgButton);
imgButton.setLayoutParams(rlp);
Hope I didn't forget something ...
//EDIT:
I was thinking, the reason for the issue might be, that the scaled image has different "borders", depending on the screen size.
With an image at a 1:1.6 ratio on a HVGA screen I have black bars on the left and right, whereas on a WVGA screen the bars are on the left. Considering I'm using the default scaling. I will look into it and post again, if necessaray...
Why scaling happens? Because of different dpi on different devices? Do you have different drawabled for different dpi settings? If it isn't just dpi issue and you want to scale that background image freely then you can't do the job using standard layouts. You should implement a custom one.
I have implement something like the following on J2me.
Is it possible to implement the same for Android?
i> I have a strip of image which I am keeping on the drawable folder. The image width & height is small.
ii> Now , using Android code & layout xml , I want to repeat displaying the small image strip so that an entire status bar / header is drawn for a screen with the use of repeating images.(mutiple image strips will ultimately draw the entire header)
The reason for implementing the header / status bar in this manner is to avoid keeping the entire header image on the drawable folder which increases the mobile application size.
Kindly provide me your inputs/sample code if anyone has done any implementation with the above logic.
Thanks in advance.
Yes, you can have tiled bitmaps in android.
If your tile is in drawable/background.png, then in drawable/background_tile.xml:
<bitmap android="http://schemas.android.com/apk/res/android"
android:src="#drawable/background"
android:tileMode="repeat"/>
And then you can set the background of a View to be your tiled bitmap in your layout:
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="20dp"
android:background="#drawable/background_tile"/>
You could set the background of a LinearLayout or other View subclass as well.
Two things:
1) I would strongly recommend using a nine patch if at all possible (Especially recommend at least checking out the draw9Patch tool if you havent already) Android draw9Patch info
2.) I've had issues with the tileMode xml attribute being ignored, so had to use the following code to enable the tileMode (this may have been a bug in 1.5)
/**
* Set up any UI elements
*/
private void setUpUI() {
// Add tiling background
View backgroundLayout = findViewById(R.id.backgroundLayout);
BitmapDrawable bgImage = (BitmapDrawable) this.getResources().getDrawable(R.drawable.wood_bg);
bgImage.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
backgroundLayout.setBackgroundDrawable(bgImage);
}
Code snippet taken from Gaunt Face - Where-To-Do Post