Author Topic: PNG Animation  (Read 22744 times)

Patrice Terrier

  • Administrator
  • *****
  • Posts: 1996
    • zapsolution
PNG Animation
« on: October 27, 2016, 02:49:18 pm »
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 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.

And here is how to read it in C++

Code: [Select]
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.
« Last Edit: April 18, 2017, 12:35:17 pm by Patrice Terrier »
Patrice
(Always working with the latest Windows version available...)

Patrice Terrier

  • Administrator
  • *****
  • Posts: 1996
    • zapsolution
GIFtoPNG
« Reply #1 on: October 30, 2016, 01:56:21 pm »
GIFtoPNG

Here is the attached PowerBASIC command line utility to create directly a PNG ani file, from animated GIF.

And a GIF file animation to convert to ani_PNG to display with the RedHawk.exe or PNGanim.exe viewers.




How to customize the source code:
'// For the last parameter of CreateMultiFrameImage,
'// use 0 rather than 512 to keep the size of the original.
imgOUT = CreateMultiFrameImage(szFileIn, anih, 512)

When using a value other than 0, you instruct CreateMultiFrameImage to fit each frame within a square of the requested size.
 (Usefull in case you want to perform 360° image rotation inside of GDImage)
The value 512 is used to be compatible with the size of the RedHawk.exe view port.
Note: if the frame size of the original GIF file is smaller than the MaxSquareSize parameter, then the last parameter is ignored.

How to use it:
GIFtoPNG being a command line utility, you have to select a GIF file animation in Windows Explorer, and perform  drag and drop of the thumbnail onto the GFItoPNG.exe file.

Both EXE and source code provided into the attached ZIP files.

GIFtoPNG_32.zip is the 32-bit version written in PowerBASIC.
GIFtoPNG_64.zip is the 64-bit version written in C++.
« Last Edit: November 09, 2016, 05:59:42 pm by Patrice Terrier »
Patrice
(Always working with the latest Windows version available...)

Patrice Terrier

  • Administrator
  • *****
  • Posts: 1996
    • zapsolution
PNGanim
« Reply #2 on: November 09, 2016, 03:44:13 pm »
PNGanim

Is a WinLIFT/GDImage visual utility to display and adjust the animation speed of ani_PNG FILES.



The animation speed could range between 1 to 100 milliseconds (GetTickCount).

Note: The C++ 64-bit  Visual Studio Community 2015 project is attached to this post.
Because of the size of the animations, there is only one, within PNGanim_64.zip.
However, a few more are provided into the ani_png.zip file, and they must be uncompressed into the EXE\images subfolder.
(PowerBASIC 32-bit version within PNGanim_32.zip).
« Last Edit: November 10, 2016, 06:29:09 pm by Patrice Terrier »
Patrice
(Always working with the latest Windows version available...)