Search code examples
androidnine-patch

nine-patch images with 1px width or height -- special case or faulty files?


In the process of altering the images found in Launcher2.apk, I found some files that are obviously nine-patches, but have a height or width of 1px only:

$ file **/*.9.png G "\( 1 x\|x 1,\)"
Launcher2/res/drawable-land-xhdpi/workspace_bg.9.png:                   PNG image, 400 x 1, 8-bit gray+alpha, non-interlaced
Launcher2/res/drawable-sw720dp-xhdpi/workspace_bg.9.png:                PNG image, 1 x 400, 8-bit gray+alpha, non-interlaced
Launcher2/res/drawable-xhdpi/workspace_bg.9.png:                        PNG image, 1 x 400, 8-bit gray+alpha, non-interlaced

Are these files that are messed up somehow by their creators or are they special cases, i. e. fixed width but variable height or vice versa?

Is there any formal definition of the format of nine-patch files? Of course I googled, which took me quite some time, without finding any information that would help me here. Would be best if it'd also cover other aspects, because I found many other suspicious images in several packages that contain none-binary information in there left-/top-/right-/bottom-most pixels; hence I'd also like to know exactly which values encode repeat/stretch/as-is, and which are ignored.

This section in Googles Canvas and Drawables document doesn't give many details (actually none). and the distributed draw9patch.jar doesn't help much either.

Just in case it is of interest: The device I took Launcher2.apk and other packages from is a Moto G, stock 4.3 firmware.

Thanks in advance


Solution

  • Okay, what I learned since I investigated more on nine-patch images:

    The main error is probably to think that the repeat/stretch/keep meta data of nine-patches are always encoded in the border pixels of the image files. This is not true.

    As a matter of fact, there are two nine-patch formats, apparently called source form and compiled form, the former having the well-known special borders in which pixels specify the patches (of which you can have more than nine, indeed). This is most likely called source form, because the pixels show off in image editors (however, under some circumstances they may not; see below).

    According to the apktool wiki the second — compiled — form has this meta information encoded in a special (private) PNG chunk; the same page claims those chunks are called npTc, and my attempt to verify this with basic Linux utilities could confirm this:

    $ strings search_frame.9.png 
    IHDR
    TnpTc
    yIDATX
    [...]
    

    Also TweakPNG shows this chunk.

    So, the answer to my actual question is: I thought those 1 pixel high/wide images were nine-patches in source form, while they actually were in the compiled crunched format.


    Since there doesn't seem to be an official specification, and this was part of the question, I'm just starting a collection of information here.


    Tools that convert from one form to the other

    • aapt (coming with the Android sdk) has, among others, two subcommands,

      • s[inglecrunch] compiles a single bordered nine-patch image to the chunked/crunched format (use command line options -i and -o for input and output file, respectively),
      • c[runch] takes a resource directory (option -S) and the name of an output directory (option -C), traverses the whole source directory and creates target subdirectories as appropriate.

       

      (Be warned: if you forget to create the root of the target directory tree beforehand, aapt doesn't error out, but is busy with $whatever, taking a whole CPU core/thread's attention and power, so effectively showing that it's busy, while it does at least not what the user expects. (Just for the sake of completeness: I'm running SDK 19.0.1))

    • apktool is a custom java application that is able to unpack whole *.apk files, converting *.xml files to plain text files and *.9.png files to their source format.

      To put everything (potentially modified) back into an *.apk archive, you'd need to manually extract some files from the original package, which most zip/unzip utilities should be able to do, but without conversion. For the process of repackaging, apktool apparently makes use of aapt, of which it has a version bundled.

      This bundled aapt is probably extracted from an older SDK (pre 19), as it shows off the same flaws (the above mentioned tight busy loop, and several segfaults where a simple if would prevent such nasty behaviour (e. g. it segfaults when you don't give the -C option to the c[runch] subcommand)), while it lacks support for the s[ingleCrunch] subcommand.

    • xUltimate-d9pc (this seems to be a source-to-crunched-format converter, but I didn't really test it.)

    • WebLaF (also untested; it seems to have sources, where one could get more info.)

    Formats of nine-patches

    source form

    Most people will already know that the source form of a nine-patch does include the normal pixel data of the image, plus an extra border. I will try to illustrate with a simple ASCII representation (and hope your font allows to recognize what it means):

        -------##--#####--##-------
        -......XXXXXXXXXXXXX......-
        -...XXX.............XXX...-
        -..X...................X..-
        -.X.....................X.-         Legend
        -.X.....................X.-
        #X.......................X-         (top and left borders)
        #X.......................X-         #  denotes a "stretch"/"repeat" mark
        -X.......XXXXXXXXX.......X-         -  denotes a "keep" mark
        -X.......X.......X.......X#
        #X.......X.......X.......X#         (bottom and right borders)
        #X.......X.......X.......X#         #  marks the content area (aka fill area)
        -X.......X.......X.......X#         -  marks ... umm... what is it called?
        -X.......XXXXXXXXX.......X-
        #X.......................X-         (image area)
        #X.......................X-         .  pixel in "background" color
        -.X.....................X.-         X  pixel in foreground color
        -.X.....................X.-
        -..X...................X..-
        -...XXX.............XXX...-
        -......XXXXXXXXXXXXX......-
        ----------#######----------
    

    This is some kind of double box, the outer of which has rounded corners. The size of this form of nine-patch is 27x22px, but the actual image size is 25x20px only — this is also the minimum size at which it can be shown in an app, but it can be stretched to an arbitrary size.

    The repeat marks tell the framework which lines to repeat when the image is stretched, e. g. when the image is shown at an actual height of 26 pixels, lines 7, 8, 11, 12, 15, and 16 and repeat, each once, making up for the extra lines on the screen. This way it is ensured that the borders of both boxes will always remain 1 pixel thin and the rounded corners of the outer one will always have the same size/radius. while the marked regions will be adequately stretched (well if the difference of the stretched size to the original size is not a multiple of the marked lines, there will be irregularities, but on today's 160+ dpi display that doesn't probably matter much; however, less repeat/stretch markers will probably be more performant, but then again... on today's 1.6+ GHz quad core phones that doesn't matter either...)

    Values for the repeat/stretch markers When I posted the question, I wasn't sure whether there is any semantic difference between stretching and repeating, and I still am not.

    According to A simple guide to 9-patch for Android UI the markers must be solid black for repetition of the row/column in question, otherwise the result will be wrong without any notice to the user except when incorrectly displaying on the screen; this may be correct for previous SDK versions (the article being from May 2011).

    However, both aapt versions (in SDK 19 and the one coming with apktool 1.5.2) complain about arbitrary colors in the borders, for example:

    ERROR: 9-patch image test/search_frame_.9.png malformed.
           Ticks in transparent frame must be black or red.
           Found at pixel #21 along top edge.
    ERROR: 9-patch image test/tab_unselected_pressed_focused_holo.9.png malformed.
           Must have one-pixel frame that is either transparent or white.
    

    where the second file is a compiled/crunched nine-patch already, while the first is not.

    What I read from the "black or red" message is that the border may not contain pixel values other than #00000000 (solid black, for repeat/stretch marks), #00ff0000 (solid red, maybe for the same), and #xx000000 (black with arbitrary transparancy (xx>0) for "keep" marks, maybe also #xxFF0000, but I didn't test that).

    What I'm still not sure about is whether red and black have distinct semantics, or if red is just an alternative for image editors that use a black background, where it's not easy to visually distinguish between #00000000 and #01000000, for example).

    compiled/crunched form

    The SDK contains two classes android.graphics.NinePatch and android.graphics.NinePatch_Delegate, which make use of calls to native libraries, so the sources don't give us much. I don't have the NDK installed and I even don't know if it supplies sources for the relevant libraries.

    The best that I've found was the answer to Android: compiling 9-patch files to be used outside of the drawable folder?