Please take a moment to check out the Web application.
Introduction
This article introduces a class that can read CPBitmap files, which are used (primarily, from what I can see) as the file format for Wallpaper images on an iDevice (iPod, iPhone, iPad, etc.).
Background
(Skip past the first 4 paragraphs if you don't care about why I did this! :D)
Today, I was asked by my CEO to recover the wallpaper from an old iPhone 3GS (currently on an iPhone 4). This seemed straight forward -> I had the backup on my PC, and I had bought a nifty program called "iPhone backup extractor". Off I went, opened up iPhone backup extractor, and exported the photos it found. Unfortunately, it found everything EXCEPT the wallpaper.
After a bit of Googling around, I managed to figure out that the wallpaper is actually stored under the Springboard folder in the "Library". So, I extracted the file (called HomeBackground.cpbitmap) to my PC, and had a look.
Unfortunately, changing the extension to one of the known types didn't work (PNG, JPEG, BMP, etc.). So I again decided to turn to Google (at this point, I will tell you that the iPhone isn't jailbroken.. and in fact, I was looking at a backup of an iPhone, not the physical device itself). Turning to Google turned up a lot of sites where people had jailbroken their phones and they were all extremely frustrated that they couldn't produce CPBitmap files. So I decided to open the file in Wordpad and take a look.
How I Figured It Out
I don't claim to be a genius, but I figure this may help some people somehow.
Both the top and bottom of the file were almost identical.
This made me say, "hey wait.. there isn't any header for this image, it jumps straight into pixel data".
With that, I decided to look up a 3GS display resolution, which just so happens to be 320x480. With a little brain work, I came up with this extremely difficult equation: 320x480x4. Why 4? There are 4 bytes in a 32-bit Integer (int, or, Int32), 1 byte for the Red component, 1 byte for the Green component, 1 byte for the Blue component and 1 byte for the Alpha component.
I double checked the files and sure enough, Windows displays 600KB (614,424 bytes) for both the LockBackground and HomeBackground, even though they were completely different pictures. 320x480x4 = 614,400 ... we're close!
The rest was basically trial and error. The file has the width and height stored at the end, as well as some random unknown fields that I'm not sure what they're for. Reading those into the Bitmap crashed the application, so I just debugged it until I figured out what the issue was!
Using the Code
With all of that being said... I present the first, open source attempt at loading CPBitmap files. They aren't complicated at all.. so I'm surprised I seem to be the first one who attempted to load them. If that is a false statement, please link me to the other authors work.. I couldn't find anything after half a day of looking!
Usage of the class couldn't be simpler, you just need to supply a FileStream:
FileStream fileStream = new FileStream("<path_to_file_here>.cpbitmap",
FileMode.Open, FileAccess.Read);
CPBitmap cpBitmap = new CPBitmap(fileStream);
fileStream.Close();
The provided example paints the Bitmap into a PictureBox, and also allows you to save the Bitmap as a JPEG file. Feel free to change the constructor... the only reason I supply a filestream is for the example.
The drawing is done using an unsafe pointer and the BitmapData class. It doesn't have to be done this way... SetPixel is "quick enough" if you have trouble understanding the pointer. The "meat" of the code is below for reference:
private unsafe void Load()
{
Bitmap _bitmap = new Bitmap(320, 480, PixelFormat.Format32bppPArgb);
BitmapData _bitmapData = _bitmap.LockBits(new Rectangle
(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadWrite,
PixelFormat.Format32bppPArgb);
int* xy = (int*)_bitmapData.Scan0.ToPointer();
using (BinaryReader _binaryReader = new BinaryReader(_fileStream))
{
int _filePosition = 0;
int _fileLength = (int)_binaryReader.BaseStream.Length;
while (_filePosition < _fileLength)
{
if (_filePosition < _endOfPixelDataCalculation) {
*(xy++) = _binaryReader.ReadInt32();
}
else
{
switch (_filePosition)
{
case _endOfPixelDataCalculation:
UnknownField1 = _binaryReader.ReadInt32();
break;
case _endOfPixelDataCalculation + 4:
WidthInFile = _binaryReader.ReadInt32();
break;
case _endOfPixelDataCalculation + 8:
HeightInFile = _binaryReader.ReadInt32();
break;
case _endOfPixelDataCalculation + 12:
UnknownField2 = _binaryReader.ReadInt32();
break;
case _endOfPixelDataCalculation + 16:
UnknownField3 = _binaryReader.ReadInt32();
break;
case _endOfPixelDataCalculation + 20:
UnknownField4 = _binaryReader.ReadInt32();
break;
}
}
_filePosition += sizeof(int);
}
}
_bitmap.UnlockBits(_bitmapData);
BitmapObject = _bitmap;
}
Points of Interest
I searched high and low for this and couldn't find it. I hope this helps someone. Losing a wallpaper in a backup was previously unavoidable... not anymore!
History
- 8/10/2011 12:00:00 AM - Initial version released