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<NSNumber *> *xDivsArray;
@property (nonatomic, strong) NSArray<NSNumber *> *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<ninePatch.numXDivs;i++) {
            [data getBytes:bytes range:NSMakeRange(index,4)];
            index+=4;
            int32_t x = ntohl(*(int32_t *)bytes);
            [xDivs addObject:@(x)];
        }
        // read yDivs
        NSMutableArray *yDivs = NSMutableArray.new;
        for (int i=0;i<ninePatch.numYDivs;i++) {
            [data getBytes:bytes range:NSMakeRange(index,4)];
            index+=4;
            int32_t y = ntohl(*(int32_t *)bytes);
            [yDivs addObject:@(y)];
        }
        ninePatch.xDivsArray = xDivs;
        ninePatch.yDivsArray = yDivs;
    }
    return ninePatch;
}
- (UIEdgeInsets)resizableCapInsetsWithImageSize:(CGSize)size {
    if (self.xDivsArray.count < 2 || self.yDivsArray.count < 2) return UIEdgeInsetsZero;
    int32_t xStart = self.xDivsArray[0].intValue;
    int32_t xEnd   = self.xDivsArray[1].intValue;
    int32_t yStart = self.yDivsArray[0].intValue;
    int32_t yEnd   = self.yDivsArray[1].intValue;
    if (xEnd < xStart || yEnd < yStart) return UIEdgeInsetsZero;
    UIEdgeInsets insets;
    insets.top = yStart;
    insets.left = xStart;
    insets.bottom = size.height - yEnd;
    insets.right = size.width - xEnd;
    return insets;
}

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.