2009-07-02

Extracting 9-patches from apk files

As you may or may not know, 9.png files in compiled android packages have the nine patch metadata info rolled from the image OOB into the PNG file. Following quick and dirty ruby script extracts it back.

#!/usr/bin/env ruby

# Rafał Rzepecki 
# public domain
#
# deserializes metadata of 9-patch png file
# optionally writes out png with 9-patch info embedded (needs imagemagick for that)
#
# quick and dirty hack, no error handling, almost no test, YMMV
#
# for format specs see android/platform/frameworks/base/libs/utils/ResourceTypes.cpp
# (in android platform source)

if ARGV.length == 0
    print "Usage: #{__FILE__} <serialized nine-patch png file> [optional output png with inline 9-patch info]\n"
    exit 1
end

filename = ARGV[0]
png = File.open(filename) { |f|f.read }
index = png.index 'npTc'
data = png[(index+4)..-1]
wasDeserialized, numXDivs, numYDivs, numColors = data[0...4].unpack('C4')
paddings = data[12...(12+16)].unpack('N4') #left right top bottom
data.slice!(0...32)
xDivs = data.unpack("N#{numXDivs}")
data.slice!(0...(4*numXDivs))
yDivs = data.unpack("N#{numYDivs}")
data.slice!(0...(4*numYDivs))
colors = data.unpack("N#{numColors}")

print "was deserialized: #{wasDeserialized}
paddings: #{paddings.join(', ')}
xdivs: #{xDivs.join(', ')}
ydivs: #{yDivs.join(', ')}
colors: #{colors.map{|c| "#%08x"%c}.join(', ')}
"

if ARGV.length == 1
    exit 0
end

# quick and dirty
`identify #{filename}` =~ /PNG (\d+)x(\d+)/
w, h = $1.to_i, $2.to_i
`convert #{filename} -bordercolor white -compose Copy -border 1x1 -stroke black \
-draw 'line #{xDivs[0] + 1},0 #{xDivs[1] + 1},0' \
-draw 'line 0,#{yDivs[0] + 1} 0,#{yDivs[1] + 1}' \
-draw 'line #{paddings[0] + 1},#{h + 1} #{w - paddings[1]},#{h+1}' \
-draw 'line #{w+1},#{paddings[2] + 1} #{w+1},#{h - paddings[3]}' \
#{ARGV[1]}`

No comments: