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. It is build upon the existing
GDIPLUS API that is available on all Windows OS.
I am using a compound file to store the data, in a very simple way like this:
Where:
1st pngis a regular png file matching the first frame of the animation.
2nd pngis a regular png file matching all the other frames of the animation.
footeris 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.
And here is how to read it in C++
long ZI_IsPNGanimation(IN WCHAR* szFile) { // dllexport
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) { // dllexport
long nRet = 0;
// First clear the header
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) { // dllexport
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) {
// sBuffer 2 loaded
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) {
// sBuffer 1 loaded
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;
}
The attached C++ 64-bit source code project has been created with the free Visual Studio 2015 Community version.
It is provided with 7 png animated files for test purpose.If you have questions or comments about this project, please post them into the dedicated
WIP section,
here.