Understanding Android Nine‑Patch Images: Creation, Usage, and Internals
The article explains Android’s Nine‑Patch format—a bitmap with a 1‑pixel black border defining stretchable and content areas—covers creation methods (Photoshop, Draw9patch, Android Studio), runtime parsing, practical use in network‑loaded chat bubbles, required conversion workflow, and the underlying PNG “npTc” chunk structure with code examples for manual drawable construction.
1. Introduction to Nine‑Patch
This section gives a brief overview of Nine‑Patch images. If you are already familiar with them, you can skip to section 2 to see how they are used in chat bubbles.
1.1 Why Nine‑Patch Exists
Android needs a way to use a single image as a background for text of varying length. The ".9.png" format allows developers to specify stretchable regions on the image. This format is Android‑specific; iOS achieves similar effects by specifying stretch points in code.
1.2 The Essence of Nine‑Patch
A Nine‑Patch image is a normal bitmap with an extra 1 px border marked by pure black pixels (#FF000000). The border defines stretchable and content areas. The following picture shows the black markers:
The markers have the following meanings:
Marker Position
Meaning
Left black line
Vertical stretch area
Top black line
Horizontal stretch area
Right black line
Vertical content area
Bottom black line
Horizontal content area
1.3 Methods to Create Nine‑Patch Images
Because a Nine‑Patch is just a bitmap with a 1 px border, any pixel‑editing tool (e.g., Photoshop) can be used. For better usability and visual feedback, the Draw9patch tool (bundled with early Android SDKs and now integrated into Android Studio) is recommended.
Edge‑line Drawing Method
Pros & Cons
PS and other image editors
1. Designers can work directly.
2. No extra tools needed.
3. Risk of errors (e.g., non‑pure black lines).
Draw9patch (recommended)
1. Requires JRE and the tool (no longer in latest SDKs, but downloadable).
2. Intuitive, prevents mistakes.
Android Studio
1. Requires designers or product staff to install and learn Studio.
2. Convenient for developers.
1.4 Basic Usage in Android
There are three common ways to use Nine‑Patch images:
Place the Nine‑Patch (with black lines) in the res folder and set it as a TextView background. Android will stretch it automatically according to the view size.
Put a processed Nine‑Patch in the assets folder, then manually create a NinePatchDrawable from the bitmap.
Load a Nine‑Patch from a remote URL. This is the most complex case and is the focus of the following sections.
1.5 How Android Parses Nine‑Patch at Runtime
During compilation, the black‑border information is extracted into a byte array called mNinePatchChunk inside the Bitmap object, and the 1 px border is removed. At runtime, if mNinePatchChunk is present and recognized as a Nine‑Patch chunk, Android creates a NinePatchDrawable ; otherwise it falls back to a regular BitmapDrawable .
2. Using Nine‑Patch in Chat Bubbles
2.1 Problems and Solutions
When a Nine‑Patch is fetched from a URL, the image may still contain the black border because it has not been processed by the Android build tools. This leads to visible black lines and no stretching.
Three possible solutions were considered:
Provide a conversion tool for designers to process the image before uploading.
Upload the raw image to a platform that converts it before serving.
Perform conversion on the client side by constructing mNinePatchChunk manually (costly and complex).
The team finally adopted the first approach: designers convert the image locally and upload the processed Nine‑Patch.
2.2 Final Workflow
The agreed workflow consists of nine steps (illustrated in the diagram below). Key points include:
Black lines must be pure black and corners must be transparent; otherwise Android cannot recognize the image.
Convert the image using the aapt tool: aapt c -v -S . -C .\9out
Do not compress the converted Nine‑Patch during upload; compression would strip the auxiliary chunk.
Avoid automatic WebP conversion by ensuring the request header does not contain accept:image/webp and the URL ends with /0 .
When loading from the network, create the drawable manually: byte[] chunk = bitmap.getNinePatchChunk(); if (NinePatch.isNinePatchChunk(chunk)) { NinePatchDrawable ninePatchDrawable = new NinePatchDrawable(bitmap, chunk, new Rect(), null); } else { BitmapDrawable bitmapDrawable = new BitmapDrawable(bitmap); }
Cache the drawable to avoid flickering in list views.
3. Additional Technical Details
3.1 PNG Structure and Nine‑Patch Chunk
A PNG file consists of a signature followed by a series of chunks. Apart from mandatory chunks, optional (auxiliary) chunks can store extra data. The Nine‑Patch information is stored in an auxiliary chunk with the tag "npTc".
3.2 Relevant Source Code
Reading the Nine‑Patch chunk (C++):
bool NinePatchPeeker::readChunk(const char tag[], const void* data, size_t length) {
if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
Res_png_9patch* patch = (Res_png_9patch*) data;
size_t patchSize = patch->serializedSize();
if (length != patchSize) {
return false;
}
Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
memcpy(patchNew, patch, patchSize);
Res_png_9patch::deserialize(patchNew);
patchNew->fileToDevice();
free(mPatch);
mPatch = patchNew;
mPatchSize = patchSize;
} else {
...
}
return true;
}Definition of the Nine‑Patch structure (C++):
struct alignas(uintptr_t) Res_png_9patch {
int8_t wasDeserialized;
uint8_t numXDivs;
uint8_t numYDivs;
uint8_t numColors;
uint32_t xDivsOffset;
uint32_t yDivsOffset;
int32_t paddingLeft, paddingRight;
int32_t paddingTop, paddingBottom;
uint32_t colorsOffset;
// ... getters for xDivs, yDivs, colors ...
} __attribute__((packed));Java method that checks whether a byte array is a Nine‑Patch chunk:
static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
if (obj == NULL) return JNI_FALSE;
if (env->GetArrayLength(obj) < (int)sizeof(Res_png_9patch)) return JNI_FALSE;
const jbyte* array = env->GetByteArrayElements(obj, 0);
if (array != NULL) {
const Res_png_9patch* chunk = reinterpret_cast
(array);
int8_t wasDeserialized = chunk->wasDeserialized;
env->ReleaseByteArrayElements(obj, const_cast
(array), JNI_ABORT);
return (wasDeserialized != -1) ? JNI_TRUE : JNI_FALSE;
}
return JNI_FALSE;
}3.3 Can Stretch Regions Be Specified Without a Nine‑Patch Image?
Yes. By constructing a Res_png_9patch structure manually and filling the stretch and padding fields, developers can create a byte array ( chunk ) and pass it to the NinePatchDrawable constructor: NinePatchDrawable ninePatchDrawable = new NinePatchDrawable(bitmap, chunk, new Rect(), null);
These techniques are useful when the same stretch information is needed on both Android and iOS platforms.
Tencent Music Tech Team
Public account of Tencent Music's development team, focusing on technology sharing and communication.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.