65.9K
CodeProject 正在变化。 阅读更多。
Home

PNG 动画

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.81/5 (7投票s)

2017 年 4 月 10 日

CPOL

2分钟阅读

viewsIcon

16844

一种 32 位动画,工作方式类似于 8 位 GIF 动画格式

引言

已经存在 .APNG 格式,可以执行类似于 GIF 动画的动画,但是由于其使用的块模式,在打开/写入文件时使用起来比较复杂。

因此,我决定创建自己的格式,该格式可以被任何应用程序显示,包括 Windows 资源管理器,即使它们无法以动画模式显示它。

我使用复合文件以非常简单的方式存储数据,如下所示



其中

第一个 PNG
是一个常规 PNG 文件,与动画的第一帧匹配。

第二个 PNG
是一个常规 PNG 文件,与动画的所有其他帧匹配。

footer
是一个 ANIHEADER 结构,使用这些成员

const int ANIM = 1296649793;

struct ANIHEADER {
    WORD  nFrame;   // 动画中的帧数
    WORD  nWidth;   // 单帧的宽度
    WORD  nHeight;  // 单帧的高度
    WORD  nSpeed;   // 动画速度
    DWORD noffset;  // 第二个 PNG 文件开始的偏移量
    DWORD reserved; // 用于进一步扩展
    DWORD nSign;    // ANIM 签名,用于检测文件是否是有效的 PNG 动画
} ;



注意: 所有帧的大小都与“第一个 PNG”完全相同,即引导帧。

此格式的缺点是,如果您将其加载到标准的图形应用程序中,您只会获得第一帧,
并且如果您保存它,您将丢失动画,因为“第二个 PNG”部分和“页脚”将会丢失。

以下是此 PNG 动画格式在浏览器中的渲染方式



除了文件大小之外,它看起来就像一个普通的 377 x 512 像素的静态 PNG 文件。

使用代码

以下是如何在 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;
    // 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;
}


由于演示项目提供了 7 个 PNG 动画文件,因此其大小超过了此处允许的 10 Mb 限制,因此您必须使用此链接下载 C++ 64 位 Visual Studio 2015 Community 项目。
http://www.objreader.com/download/demo/RedHawk.zip

此演示旨在向 George Redhawk 的出色工作致敬,请在此处了解更多信息
http://www.objreader.com/index.php?topic=67.0

您可以在我的 www.objreader.com 专用论坛的此专用部分中了解有关此 PNG 动画专有格式的更多信息:http://www.objreader.com/index.php?topic=69.0

另请参阅
GIFtoPNG 实用程序(VS2015 C++ 项目)
http://www.objreader.com/index.php?topic=69.msg302#msg302

PNGanim(VS2015 C++ 项目)用于更改 PNG 动画的速度
http://www.objreader.com/index.php?topic=69.msg309#msg309

为了避免垃圾邮件机器人和各种非法、自动注册已被禁用。

如果您想注册,请使用您的真实姓名,并发送请求至

objreader (at) gmail.com

注意:附件仅供注册用户使用。

 

© . All rights reserved.