Click here to Skip to main content
15,883,623 members
Articles / Programming Languages / C++

PNG animation

Rate me:
Please Sign up or sign in to vote.
3.81/5 (7 votes)
10 Apr 2017CPOL2 min read 15.7K   9   3
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer zapsolution
France France
I am a low level SDK programmer, focusing mainly on graphic imaging and multimedia applications.
I am using several languages, including C, C++, PowerBASIC, WinDev.
I wrote also a few demos in C#, but i never used DotNET in real code production.

Comments and Discussions

 
QuestionMy vote of 5 Pin
Ecodfert24-Jun-17 3:09
Ecodfert24-Jun-17 3:09 
The animations from George Redhawk are fascinating, thank you for sharing them with us.
Question[My vote of 1] Wrong format Pin
virtualnik9-Jun-17 6:08
virtualnik9-Jun-17 6:08 
AnswerRe: [My vote of 1] Wrong format Pin
zapsolution13-Jun-17 21:45
zapsolution13-Jun-17 21:45 
QuestionWhat about .MNG ? Pin
Kochise18-Apr-17 0:18
Kochise18-Apr-17 0:18 
AnswerRe: What about .MNG ? Pin
zapsolution18-Apr-17 0:33
zapsolution18-Apr-17 0:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.