Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PNG animation

0.00/5 (No votes)
10 Apr 2017 1  
32-bit animation working like the 8-bit GIF animated format

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++

Code: [Select]
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;
    // 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) {
    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;
}


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.

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here