I’m the CTO of IOPixel, a game studio based in France. I’m working on TipOff 2, the sequel of TipOff. The first game size was consequent 20Mo but acceptable for many players. To limit the size of the APK, we use on Android the multiple APKs hack. It was painful to maintain! Seriously, i don’t know the intern at Google who had this idea but what a bad realisation!!!
Things to know:
So if you can’t maintain APK < 50Mo, here comes the Challenge.
Due to performance motives, we at IOPixel use many texture formats on Android Device. The numbers here are the size after zip compression:
PNG are the backup format, used only when no compression format is accepted. DXT is for Tegra 3&4, because NVidia is not compatible OGLES 3.0!!! ATC is used by adreno devices. KTX (ETC1) files is for Mali 400 devices (there is one file for RGB + one file for Alpha). Some data are in PNG format only because there are GUI Images (not used when we need 60fps). In future, i think to add ETC2 format.
Note to everybody not using Texture Compression: you should !!!
So we need at minimum PNG + One format Compressed for multiple APKs => Far More than 50Mo… Ok, i will use the obb system with one unique APK (we could optimize with multiple APKs but because support problem i don’t want it).
And now come the result: one APK of 22Mo (mp3s + few pngs + .so files) and one enormous obb file: 100Mo. 125Mo is what the user see when he want to download TipOff 2. This week we made 4 updates: 500Mo for our beta testers :) Thx guys, i love you.
There are many files format for images… During my life, i have used BMP, TGA, GIF, PNG, JPEG, JP2…. So for TipOff 2, i want an alternative to PNG:
After many research, i found some file formats:
You could compare these file formats with this comparator. These formats could be all acceptable for TipOff 2. I’m looking for a 1/10 ratio. To select one of these file formats: i search sucessful integrations on iOS and Android. Jpeg2000 is native on iOS but on Android… JpegXR seems a serious project but nobody uses it. BGP with the routing protocol is painful to search. Fabrice is a genious but not a marketing one’s :) And WebP is a google beta Product. WebP is available on Android 4.0 (but it is really available with 4.2.1). The integration with NDK is easy (https://gist.github.com/markbeaton/3719812), WebP is available on Port (OS X) and i build a iOS framework in 5 minutes. So i choose WebP. Not sure it’s the best choice but it’s a good one. If i failed with WebP, i will try BGP because of Fabrice Bellard.
I use port as everybody. So:
sudo port install webp
WebP is installed. I could try to compress some PNG:
ellis> cwebp -q 80 animation_loading.png animation_loading.webp
Saving file 'animation_loading.webp'
File: data/textures/animation_loading.png
Dimension: 1024 x 1024 (with alpha)
Output: 52928 bytes Y-U-V-All-PSNR 44.38 44.66 45.37 44.58 dB
block count: intra4: 1307
intra16: 2789 (-> 68.09%)
skipped block: 2594 (63.33%)
bytes used: header: 288 (0.5%)
mode-partition: 7609 (14.4%)
transparency: 3423 (99.0 dB)
Residuals bytes |segment 1|segment 2|segment 3|segment 4| total
macroblocks: | 2%| 9%| 17%| 71%| 4096
quantizer: | 27 | 27 | 23 | 17 |
filter level: | 8 | 5 | 10 | 8 |
Lossless-alpha compressed size: 3420 bytes
* Lossless features used: PALETTE
* Precision Bits: histogram=5 transform=4 cache=0
* Palette size: 255
ellis> ls -al animation_loading.png
-rw-r--r-- 1 ellis staff 557072 Jun 26 21:50 data/textures/animation_loading.png
ellis> ls -al animation_loading.webp
-rw-r--r-- 1 ellis staff 52928 Jul 11 14:11 animation_loading.webp
So with quality factor at 80, i have 1/10 ratio. Let’s look how it compress. I use Chrome as viewer :) Ok it’s great. You could tune this factor with your art director.
Integration in my project was so easy. WebP has a header of 16 bytes. I check i found the signature and decode the data into my internal uncompressed data. In my Irrlicht project, it is easily integrated:
bool CImageLoaderWebP::isALoadableFileExtension(const io::path& filename) const {
return core::hasFileExtension ( filename, "webp" );
}
bool CImageLoaderWebP::isALoadableFileFormat(io::IReadFile* file) const {
if (!file) {
return false;
}
// load Header
SWebPHeader header;
s64 size = file->getSize();
file->seek(0);
file->read((void *)&header, sizeof(SWebPHeader));
// assert
bool check = header.data[0] == 'R';
check |= header.data[1] == 'I';
check |= header.data[2] == 'F';
check |= header.data[3] == 'F';
check |= header.data[8] == 'W';
check |= header.data[9] == 'E';
check |= header.data[10] == 'B';
check |= header.data[11] == 'P';
// header checked
return check;
}
IImage* CImageLoaderWebP::loadImage(io::IReadFile* file) const {
// load image into memory
s64 size = file->getSize();
file->seek(0);
u8* data = new u8[size];
file->read((void *)data, size);
//
s32 width = 0;
s32 height = 0;
s32 res = WebPGetInfo(data, size, &width, &height);
if (res != 1) {
os::Printer::logError("Cannot read WebP file");
return nullptr;
}
uint8_t* data2 = WebPDecodeBGRA(data, size, &width, &height);
CImage* image = new CImage(ECF_A8R8G8B8, core::dimension2d<u32>(width, height), data2, true, true);
uint8_t* data2 = WebPDecodeBGRA(data, size, &width, &height);
CImage* image = new CImage(ECF_A8R8G8B8, core::dimension2d<u32>(width, height), data2, true, true);
return image;
}
That’s all and it works :) The last job for a complete integration is to modify the pipeline workflow. 30 minutes to create a little script and modify my doit script.
Probably the most difficult integration. I have carefully added needed files to decode WebP and added them to my Android.mk:
webp/dec/alpha.c \
webp/dec/buffer.c \
webp/dec/frame.c \
webp/dec/idec.c \
webp/dec/io.c \
webp/dec/quant.c \
webp/dec/tree.c \
webp/dec/vp8.c \
webp/dec/vp8l.c \
webp/dec/webp.c \
webp/dsp/alpha_processing.c \
webp/dsp/alpha_processing_sse2.c \
webp/dsp/cpu.c \
webp/dsp/dec_clip_tables.c \
webp/dsp/dec.c \
webp/dsp/dec_sse2.c \
webp/dsp/dec_neon.c \
webp/dsp/dec_mips32.c \
webp/dsp/lossless.c \
webp/dsp/lossless_sse2.c \
webp/dsp/lossless_neon.c \
webp/dsp/lossless_mips32.c \
webp/dsp/upsampling.c \
webp/dsp/upsampling_neon.c \
webp/dsp/upsampling_sse2.c \
webp/dsp/yuv.c \
webp/dsp/yuv_sse2.c \
webp/dsp/yuv_mips32.c \
webp/utils/bit_reader.c \
webp/utils/bit_writer.c \
webp/utils/color_cache.c \
webp/utils/filters.c \
webp/utils/huffman_encode.c \
webp/utils/huffman.c \
webp/utils/quant_levels.c \
webp/utils/quant_levels_dec.c \
webp/utils/random.c \
webp/utils/rescaler.c \
webp/utils/thread.c \
webp/utils/utils.c
That’s so eaysy. Open terminal, goto into libwebp-0.4.3 and do:
sh iosbuild.sh
5 minutes after, you have a WebP.framework ready to use in your project