Introduction
There is already the .APNG format, to perform animation like with GIF animated, however because of the chunk mode being used, it is rather complex to use when opening/writing to file.
Thus i took the decision to create my own format that could be displayed by any application, including the Windows Explorer, even if they are not able to show it in animated mode.
I am using a compound file to store the data, in a very simple way like this:
Where:
1st png
is a regular png file matching the first frame of the animation.
2nd png
is a regular png file matching all the other frames of the animation.
footer
is a ANIHEADER structure using these members
const int ANIM = 1296649793;
struct ANIHEADER {
WORD nFrame; // number of frames in the animation
WORD nWidth; // width of a single frame
WORD nHeight; // height of a single frame
WORD nSpeed; // speed of the animation
DWORD noffset; // offset to the start of the 2nd png file
DWORD reserved; // for further extension
DWORD nSign; // the ANIM signature, to detect if the file is a valid png animation
} ;
Note: All frame size are using exactly the same size than the "1st png", that is the leading frame.
The drawback of this format, is that if you load it inside of a standard graphic application you will get only the first frame,
and if ever you save it, you will loose the animation, because the "2nd png" section, and the "footer" will be missing.
Here is how this png animation format renders inside of a browser:
Except for its file size, it looks just like a regular 377 x 512 pixels png static file.
Using the code
And here is how to read it in C++
long ZI_IsPNGanimation(IN WCHAR* szFile)
long nRet = 0;
if (zExist(szFile)) {
HANDLE hFileIn = 0;
if (zFOpen(szFile, 0, 0, hFileIn) == 0) {
DWORD nSize = 4;
string sBuffer; sBuffer.assign(nSize, 0);
if (zFGetAt(hFileIn, zFlof(hFileIn) - nSize, sBuffer) == 0) {
DWORD nSign = 0;
MoveMemory(&nSign, (CHAR*) sBuffer.c_str(), nSize);
if (nSign == ANIM) { nRet = -1; }
}
zFClose(hFileIn);
}
}
return nRet;
}
long ZI_GetPNGanimation(IN WCHAR* szFile, OUT ANIHEADER &anih) {
long nRet = 0;
DWORD nSize = sizeof(anih);
ZeroMemory(&anih, nSize);
if (zExist(szFile)) {
HANDLE hFileIn = 0;
if (zFOpen(szFile, 0, 0, hFileIn) == 0) {
string sBuffer; sBuffer.assign(nSize, 0);
if (zFGetAt(hFileIn, zFlof(hFileIn) - nSize, sBuffer) == 0) {
MoveMemory(&anih, (CHAR*) sBuffer.c_str(), nSize);
if (anih.nSign == ANIM) {
nRet = -1;
} else {
ZeroMemory(&anih, nSize);
}
}
zFClose(hFileIn);
}
}
return nRet;
}
HBITMAP ZI_LoadPNGanimation(IN WCHAR* szFile, OUT ANIHEADER &anih) {
HBITMAP hDIB = 0;
DWORD BufferSize = 0;
HANDLE hFileIn = 0;
LPSTREAM pImageStream = 0;
HGLOBAL hGlobal = 0;
LPVOID pGlobalBuffer = 0;
LONG_PTR hImage = 0;
if (ZI_GetPNGanimation(szFile, anih)) {
if (zFOpen(szFile, 0, 0, hFileIn) == 0) {
LONG_PTR graphics = 0;
DWORD nSize = 0;
HDC hDC = zDisplayDC();
HDC ImgHDC = CreateCompatibleDC(hDC);
hDIB = zCreateDIBSection(ImgHDC, anih.nWidth * anih.nFrame, anih.nHeight, 32);
SelectObject(ImgHDC, hDIB);
if (GdipCreateFromHDC(ImgHDC, graphics) == 0) {
BufferSize = zFlof(hFileIn) - anih.noffset - sizeof(anih) + 1;
string sBuffer; sBuffer.assign(BufferSize, 0);
if (zFGetAt(hFileIn, anih.noffset - 1, sBuffer) == 0) {
hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, BufferSize);
if (hGlobal) {
pGlobalBuffer = GlobalLock(hGlobal);
if (pGlobalBuffer) {
MoveMemory(pGlobalBuffer, (CHAR*) sBuffer.c_str(), BufferSize);
if (CreateStreamOnHGlobal(hGlobal, NULL, &pImageStream) == 0) {
if (GdipCreateBitmapFromStream(pImageStream, hImage) == 0) {
GdipDrawImageRectI(graphics, hImage, anih.nWidth, 0, anih.nWidth * anih.nFrame - anih.nWidth, anih.nHeight);
GdipDisposeImage(hImage);
}
ReleaseObject((PFUNKNOWN*) pImageStream);
}
GlobalUnlock(pGlobalBuffer);
}
GlobalFree(hGlobal);
}
}
BufferSize = anih.noffset - 1;
sBuffer.assign(BufferSize, 0);
if (zFGetAt(hFileIn, 0, sBuffer) == 0) {
hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, BufferSize);
if (hGlobal) {
pGlobalBuffer = GlobalLock(hGlobal);
if (pGlobalBuffer) {
MoveMemory(pGlobalBuffer, (CHAR*) sBuffer.c_str(), BufferSize);
if (CreateStreamOnHGlobal(hGlobal, NULL, &pImageStream) == 0) {
if (GdipCreateBitmapFromStream(pImageStream, hImage) == 0) {
GdipDrawImageRectI(graphics, hImage, 0, 0, anih.nWidth, anih.nHeight);
GdipDisposeImage(hImage);
}
ReleaseObject((PFUNKNOWN*) pImageStream);
}
GlobalUnlock(pGlobalBuffer);
}
GlobalFree(hGlobal);
}
}
zFClose(hFileIn);
}
GdipDeleteGraphics(graphics);
DeleteDC(ImgHDC);
DeleteDC(hDC);
}
}
return hDIB;
}
Because the demo project is provided with 7 png animated files, its size exceeds the 10 Mb limit allowed here, thus you have to download the C++ 64-bit Visual Studio 2015 Community project using this link.
http://www.objreader.com/download/demo/RedHawk.zip
This demo has been made to give a tribute to the amazing work of George Redhawk, read more about him here
http://www.objreader.com/index.php?topic=67.0
You can learn more about this PNG animation proprietary format, on my www.objreader.com private forum into this dedicated section: http://www.objreader.com/index.php?topic=69.0
See also:
The GIFtoPNG utility (VS2015 C++ project)
http://www.objreader.com/index.php?topic=69.msg302#msg302
and
PNGanim (VS2015 C++ project) to change the speed of a PNG animation
http://www.objreader.com/index.php?topic=69.msg309#msg309
To avoid spam bot, and all kind of illegitimates, automatic registration is disabled.
If you want to register please use your real name, and send a request at:
objreader (at) gmail.com
Note: Attachments are only available to registered users.