Mobile Development 13 min read

Parsing and Using NinePatch PNG Images for Resizable UI Elements

The article explains how to create, parse, and use NinePatch (.9.png) images by extracting the custom “npTc” chunk from a PNG file, building a PNGNinePatch object in Objective‑C, and converting its stretchable region data into UIEdgeInsets for resizable iOS UI elements.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Parsing and Using NinePatch PNG Images for Resizable UI Elements

Background: The project needs a web page card UI with different sizes and a cover border. The design provides a slice image and requires the top‑right corner to stay unchanged while other parts stretch.

Solution: Use NinePatch (点九图) images, which contain stretchable region metadata. On iOS the method - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode creates a resizable image. The two parameters are capInsets (protected area) and resizingMode (tile or stretch).

CapInsets define the four green corners that are not stretchable; the blue area in the middle can be stretched. Different source images need different capInsets, which can be tedious to hard‑code.

Creating a NinePatch:

Designers provide the original slice (e.g., top1.png).

Use Android Studio to add a 1‑pixel black border, producing top1.9.png.

Run the Android SDK aapt tool to convert the .9.png into a regular PNG (top1_out.png) that embeds the NinePatch chunk.

Deploy the resulting PNG to iOS or a CDN.

PNG file format basics: a PNG starts with an 8‑byte signature followed by a series of chunks (IHDR, IDAT, IEND, etc.). The NinePatch information is stored in a custom chunk of type “npTc”.

Parsing the NinePatch chunk:

* The PNG chunk type is "npTc".
struct Res_png_9patch {
    int8_t wasDeserialized;
    int8_t numXDivs;
    int8_t numYDivs;
    int8_t numColors;
    int32_t* xDivs;
    int32_t* yDivs;
    int32_t paddingLeft, paddingRight;
    int32_t paddingTop, paddingBottom;
};

The table below lists the fields of the NinePatch data structure (wasDeserialized, numXDivs, numYDivs, numColors, offsets, paddings, xDivs, yDivs, Colors).

Implementation (Objective‑C) that reads a PNG file, locates the “npTc” chunk, extracts the metadata, and builds a PNGNinePatch object:

// PNGNinePatch.h
@interface PNGNinePatch : NSObject
@property (nonatomic, assign) int32_t width;
@property (nonatomic, assign) int32_t height;
@property (nonatomic, assign) int8_t numXDivs;
@property (nonatomic, assign) int8_t numYDivs;
@property (nonatomic, assign) int8_t numColors;
@property (nonatomic, assign) int32_t paddingLeft;
@property (nonatomic, assign) int32_t paddingRight;
@property (nonatomic, assign) int32_t paddingTop;
@property (nonatomic, assign) int32_t paddingBottom;
@property (nonatomic, strong) NSArray
*xDivsArray;
@property (nonatomic, strong) NSArray
*yDivsArray;
+ (nullable instancetype)ninePatchWithPNGFileData:(NSData *)data;
- (UIEdgeInsets)resizableCapInsets;
@end

// PNGNinePatch.m (excerpt)
+ (instancetype)ninePatchWithPNGFileData:(NSData *)data {
    if (data.length < 32) return nil;
    int index = 0;
    if ([[self class] readInt32:data fromIndex:&index] != 0x89504e47 ||
        [[self class] readInt32:data fromIndex:&index] != 0x0D0A1A0A) {
        return nil;
    }
    const char npTc[4] = {'n','p','T','c'};
    BOOL hasNinePatchChunk = NO;
    int32_t chunk_length = 0;
    while (YES) {
        if (index >= data.length - 8) break;
        chunk_length = [[self class] readInt32:data fromIndex:&index];
        [data getBytes:bytes range:NSMakeRange(index,4)];
        index += 4;
        if (memcmp(bytes, npTc, 4) == 0) { hasNinePatchChunk = YES; break; }
        index += chunk_length + 4;
    }
    PNGNinePatch *ninePatch = nil;
    if (hasNinePatchChunk && chunk_length > 0 && data.length > index + chunk_length) {
        ninePatch = PNGNinePatch.new;
        int8_t wasDeserialized = [[self class] readInt8:data fromIndex:&index];
        ninePatch.numXDivs = [[self class] readInt8:data fromIndex:&index];
        ninePatch.numYDivs = [[self class] readInt8:data fromIndex:&index];
        ninePatch.numColors = [[self class] readInt8:data fromIndex:&index];
        index += 8; // skip offsets
        ninePatch.paddingLeft = [[self class] readInt32:data fromIndex:&index];
        ninePatch.paddingRight = [[self class] readInt32:data fromIndex:&index];
        ninePatch.paddingTop = [[self class] readInt32:data fromIndex:&index];
        ninePatch.paddingBottom = [[self class] readInt32:data fromIndex:&index];
        index += 4; // skip colorOffset
        // read xDivs
        NSMutableArray *xDivs = NSMutableArray.new;
        for (int i=0;i

Usage example:

PNGNinePatch *ninePatch = [PNGNinePatch ninePatchWithPNGFileData:imageFileData];
UIEdgeInsets insets = [ninePatch resizableCapInsets];
image = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];

The article also lists useful references on PNG specifications, NinePatch metadata, and cross‑platform usage.

Mobile DevelopmentiOSAndroidCapInsetsImage ResizingNinePatchpng
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.