mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-08 05:51:09 +00:00
21869a6c08
probably switch to the IJG library once I pare it down. (Ken's code is 18K of C source but does not support progressive JPEG. The IJG library is over a megabyte of source and supports pretty much everything you would ever need ever.) - Fixed endianness issue in FTextureManager::CreateTexture(). - Added support for interlaced PNGs. Now ZDoom is a mostly complete PNG reader. The only thing missing is 48-bit RGB and 16-bit grayscale support, which are just wastes of bits here, but also less likely to appear than an interlaced PNG. (However, if you are using interlaced PNGs for textures, then you are needlessly wasting space, since the image won't display progressively.) - Fixed: Writing named screenshots didn't work. SVN r292 (trunk)
1095 lines
28 KiB
C++
1095 lines
28 KiB
C++
/*
|
|
** m_png.cpp
|
|
** Routines for manipulating PNG files.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2002-2006 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Currently, only 256-color paletted images are supported, as these are all
|
|
** that ZDoom needs to support. Expect this to become more complete in the
|
|
** future if I decide to add support for graphic patches stored as PNGs.
|
|
*/
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <zlib.h>
|
|
#ifdef _MSC_VER
|
|
#include <malloc.h> // for alloca()
|
|
#endif
|
|
|
|
#include "m_crc32.h"
|
|
#include "m_swap.h"
|
|
#include "c_cvars.h"
|
|
#include "v_video.h"
|
|
#include "m_png.h"
|
|
#include "templates.h"
|
|
#include "files.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
// The maximum size of an IDAT chunk ZDoom will write. This is also the
|
|
// size of the compression buffer it allocates on the stack.
|
|
#define PNG_WRITE_SIZE 32768
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
struct IHDR
|
|
{
|
|
DWORD Width;
|
|
DWORD Height;
|
|
BYTE BitDepth;
|
|
BYTE ColorType;
|
|
BYTE Compression;
|
|
BYTE Filter;
|
|
BYTE Interlace;
|
|
};
|
|
|
|
PNGHandle::PNGHandle (FILE *file) : File(0), bDeleteFilePtr(true), ChunkPt(0)
|
|
{
|
|
File = new FileReader(file);
|
|
}
|
|
|
|
PNGHandle::PNGHandle (FileReader *file) : File(file), bDeleteFilePtr(false), ChunkPt(0) {}
|
|
PNGHandle::~PNGHandle ()
|
|
{
|
|
for (unsigned int i = 0; i < TextChunks.Size(); ++i)
|
|
{
|
|
delete[] TextChunks[i];
|
|
}
|
|
if (bDeleteFilePtr)
|
|
{
|
|
delete File;
|
|
}
|
|
}
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
static inline void MakeChunk (void *where, DWORD type, size_t len);
|
|
static inline void StuffPalette (const PalEntry *from, BYTE *to);
|
|
static bool StuffBitmap (const DCanvas *canvas, FILE *file);
|
|
static bool WriteIDAT (FILE *file, const BYTE *data, int len);
|
|
static void UnfilterRow (int width, BYTE *dest, BYTE *stream, BYTE *prev, int bpp);
|
|
static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout);
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
CUSTOM_CVAR(Int, png_level, 5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
{
|
|
if (self < 0)
|
|
self = 0;
|
|
else if (self > 9)
|
|
self = 9;
|
|
}
|
|
CVAR(Float, png_gamma, 0.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_CreatePNG
|
|
//
|
|
// Passed a newly-created file, writes the PNG signature and IHDR, gAMA, and
|
|
// PLTE chunks. Returns true if everything went as expected.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_CreatePNG (FILE *file, const DCanvas *canvas, const PalEntry *palette)
|
|
{
|
|
BYTE work[8 + // signature
|
|
12+2*4+5 + // IHDR
|
|
12+4 + // gAMA
|
|
12+256*3]; // PLTE
|
|
DWORD *const sig = (DWORD *)&work[0];
|
|
IHDR *const ihdr = (IHDR *)&work[8 + 8];
|
|
DWORD *const gama = (DWORD *)((BYTE *)ihdr + 2*4+5 + 12);
|
|
BYTE *const plte = (BYTE *)gama + 4 + 12;
|
|
|
|
sig[0] = MAKE_ID(137,'P','N','G');
|
|
sig[1] = MAKE_ID(13,10,26,10);
|
|
|
|
ihdr->Width = BigLong (canvas->GetWidth ());
|
|
ihdr->Height = BigLong (canvas->GetHeight ());
|
|
ihdr->BitDepth = 8;
|
|
ihdr->ColorType = 3;
|
|
ihdr->Compression = 0;
|
|
ihdr->Filter = 0;
|
|
ihdr->Interlace = 0;
|
|
MakeChunk (ihdr, MAKE_ID('I','H','D','R'), 2*4+5);
|
|
|
|
// Assume a display exponent of 2.2 (100000/2.2 ~= 45454.5)
|
|
*gama = BigLong (int (45454.5f * (png_gamma == 0.f ? Gamma : png_gamma)));
|
|
MakeChunk (gama, MAKE_ID('g','A','M','A'), 4);
|
|
|
|
StuffPalette (palette, plte);
|
|
MakeChunk (plte, MAKE_ID('P','L','T','E'), 256*3);
|
|
|
|
if (fwrite (work, 1, sizeof(work), file) != sizeof(work))
|
|
return false;
|
|
|
|
return StuffBitmap (canvas, file);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_CreateDummyPNG
|
|
//
|
|
// Like M_CreatePNG, but the image is always a grayscale 1x1 blacksquare.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_CreateDummyPNG (FILE *file)
|
|
{
|
|
static const BYTE dummyPNG[] =
|
|
{
|
|
137,'P','N','G',13,10,26,10,
|
|
0,0,0,13,'I','H','D','R',
|
|
0,0,0,1,0,0,0,1,8,0,0,0,0,0x3a,0x7e,0x9b,0x55,
|
|
0,0,0,10,'I','D','A','T',
|
|
104,222,99,96,0,0,0,2,0,1,0x9f,0x65,0x0e,0x18
|
|
};
|
|
return fwrite (dummyPNG, 1, sizeof(dummyPNG), file) == sizeof(dummyPNG);
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_FinishPNG
|
|
//
|
|
// Writes an IEND chunk to a PNG file. The file is left opened.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_FinishPNG (FILE *file)
|
|
{
|
|
static const BYTE iend[12] = { 0,0,0,0,73,69,78,68,174,66,96,130 };
|
|
return fwrite (iend, 1, 12, file) == 12;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_AppendPNGChunk
|
|
//
|
|
// Writes a PNG-compliant chunk to the file.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_AppendPNGChunk (FILE *file, DWORD chunkID, const BYTE *chunkData, DWORD len)
|
|
{
|
|
DWORD head[2] = { BigLong((unsigned int)len), chunkID };
|
|
DWORD crc;
|
|
|
|
if (fwrite (head, 1, 8, file) == 8 &&
|
|
(len == 0 || fwrite (chunkData, 1, len, file) == len))
|
|
{
|
|
crc = CalcCRC32 ((BYTE *)&head[1], 4);
|
|
if (len != 0)
|
|
{
|
|
crc = AddCRC32 (crc, chunkData, len);
|
|
}
|
|
crc = BigLong((unsigned int)crc);
|
|
return fwrite (&crc, 1, 4, file) == 4;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_AppendPNGText
|
|
//
|
|
// Appends a PNG tEXt chunk to the file
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_AppendPNGText (FILE *file, const char *keyword, const char *text)
|
|
{
|
|
struct { DWORD len, id; char key[80]; } head;
|
|
int len = (int)strlen (text);
|
|
int keylen = MIN ((int)strlen (keyword), 79);
|
|
DWORD crc;
|
|
|
|
head.len = BigLong(len + keylen + 1);
|
|
head.id = MAKE_ID('t','E','X','t');
|
|
memset (&head.key, 0, sizeof(head.key));
|
|
strncpy (head.key, keyword, keylen);
|
|
head.key[keylen] = 0;
|
|
|
|
if ((int)fwrite (&head, 1, keylen + 9, file) == keylen + 9 &&
|
|
(int)fwrite (text, 1, len, file) == len)
|
|
{
|
|
crc = CalcCRC32 ((BYTE *)&head+4, keylen + 5);
|
|
if (len != 0)
|
|
{
|
|
crc = AddCRC32 (crc, (BYTE *)text, len);
|
|
}
|
|
crc = BigLong((unsigned int)crc);
|
|
return fwrite (&crc, 1, 4, file) == 4;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_FindPNGChunk
|
|
//
|
|
// Finds a chunk in a PNG file. The file pointer will be positioned at the
|
|
// beginning of the chunk data, and its length will be returned. A return
|
|
// value of 0 indicates the chunk was either not present or had 0 length.
|
|
// This means there is no way to conclusively determine if a chunk is not
|
|
// present in a PNG file with this function, but since we're only
|
|
// interested in chunks with content, that's okay. The file pointer will
|
|
// be left sitting at the start of the chunk's data if it was found.
|
|
//
|
|
//==========================================================================
|
|
|
|
unsigned int M_FindPNGChunk (PNGHandle *png, DWORD id)
|
|
{
|
|
png->ChunkPt = 0;
|
|
return M_NextPNGChunk (png, id);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_NextPNGChunk
|
|
//
|
|
// Like M_FindPNGChunk, but it starts it search at the current chunk.
|
|
//
|
|
//==========================================================================
|
|
|
|
unsigned int M_NextPNGChunk (PNGHandle *png, DWORD id)
|
|
{
|
|
for ( ; png->ChunkPt < png->Chunks.Size(); ++png->ChunkPt)
|
|
{
|
|
if (png->Chunks[png->ChunkPt].ID == id)
|
|
{ // Found the chunk
|
|
png->File->Seek (png->Chunks[png->ChunkPt++].Offset, SEEK_SET);
|
|
return png->Chunks[png->ChunkPt - 1].Size;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_GetPNGText
|
|
//
|
|
// Finds a PNG text chunk with the given signature and returns a pointer
|
|
// to a NULL-terminated string if present. Returns NULL on failure.
|
|
//
|
|
//==========================================================================
|
|
|
|
char *M_GetPNGText (PNGHandle *png, const char *keyword)
|
|
{
|
|
unsigned int i;
|
|
size_t keylen, textlen;
|
|
|
|
for (i = 0; i < png->TextChunks.Size(); ++i)
|
|
{
|
|
if (strncmp (keyword, png->TextChunks[i], 80) == 0)
|
|
{
|
|
// Woo! A match was found!
|
|
keylen = MIN<size_t> (80, strlen (keyword) + 1);
|
|
textlen = strlen (png->TextChunks[i] + keylen) + 1;
|
|
char *str = new char[textlen];
|
|
strcpy (str, png->TextChunks[i] + keylen);
|
|
return str;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// This version copies it to a supplied buffer instead of allocating a new one.
|
|
|
|
bool M_GetPNGText (PNGHandle *png, const char *keyword, char *buffer, size_t buffsize)
|
|
{
|
|
unsigned int i;
|
|
size_t keylen;
|
|
|
|
for (i = 0; i < png->TextChunks.Size(); ++i)
|
|
{
|
|
if (strncmp (keyword, png->TextChunks[i], 80) == 0)
|
|
{
|
|
// Woo! A match was found!
|
|
keylen = MIN<size_t> (80, strlen (keyword) + 1);
|
|
strncpy (buffer, png->TextChunks[i] + keylen, buffsize);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_VerifyPNG
|
|
//
|
|
// Returns a PNGHandle if the file is a PNG or NULL if not. CRC checking of
|
|
// chunks is not done in order to save time.
|
|
//
|
|
//==========================================================================
|
|
|
|
PNGHandle *M_VerifyPNG (FILE *file)
|
|
{
|
|
PNGHandle::Chunk chunk;
|
|
FileReader *filer;
|
|
PNGHandle *png;
|
|
DWORD data[2];
|
|
bool sawIDAT = false;
|
|
|
|
if (fread (&data, 1, 8, file) != 8)
|
|
{
|
|
return false;
|
|
}
|
|
if (data[0] != MAKE_ID(137,'P','N','G') || data[1] != MAKE_ID(13,10,26,10))
|
|
{ // Does not have PNG signature
|
|
return false;
|
|
}
|
|
if (fread (&data, 1, 8, file) != 8)
|
|
{
|
|
return false;
|
|
}
|
|
if (data[1] != MAKE_ID('I','H','D','R'))
|
|
{ // IHDR must be the first chunk
|
|
return false;
|
|
}
|
|
|
|
// It looks like a PNG so far, so start creating a PNGHandle for it
|
|
png = new PNGHandle (file);
|
|
filer = png->File;
|
|
chunk.ID = data[1];
|
|
chunk.Offset = 16;
|
|
chunk.Size = BigLong((unsigned int)data[0]);
|
|
png->Chunks.Push (chunk);
|
|
filer->Seek (16, SEEK_SET);
|
|
|
|
while (filer->Seek (chunk.Size + 4, SEEK_CUR) == 0)
|
|
{
|
|
// If the file ended before an IEND was encountered, it's not a PNG.
|
|
if (filer->Read (&data, 8) != 8)
|
|
{
|
|
break;
|
|
}
|
|
// An IEND chunk terminates the PNG and must be empty
|
|
if (data[1] == MAKE_ID('I','E','N','D'))
|
|
{
|
|
if (data[0] == 0 && sawIDAT)
|
|
{
|
|
return png;
|
|
}
|
|
break;
|
|
}
|
|
// A PNG must include an IDAT chunk
|
|
if (data[1] == MAKE_ID('I','D','A','T'))
|
|
{
|
|
sawIDAT = true;
|
|
}
|
|
chunk.ID = data[1];
|
|
chunk.Offset = ftell (file);
|
|
chunk.Size = BigLong((unsigned int)data[0]);
|
|
png->Chunks.Push (chunk);
|
|
|
|
// If this is a text chunk, also record its contents.
|
|
if (data[1] == MAKE_ID('t','E','X','t'))
|
|
{
|
|
char *str = new char[chunk.Size + 1];
|
|
|
|
if (filer->Read (str, chunk.Size) != (long)chunk.Size)
|
|
{
|
|
delete[] str;
|
|
break;
|
|
}
|
|
str[chunk.Size] = 0;
|
|
png->TextChunks.Push (str);
|
|
chunk.Size = 0; // Don't try to seek past its contents again.
|
|
}
|
|
}
|
|
|
|
delete png;
|
|
return false;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_FreePNG
|
|
//
|
|
// Just deletes the PNGHandle. The file is not closed.
|
|
//
|
|
//==========================================================================
|
|
|
|
void M_FreePNG (PNGHandle *png)
|
|
{
|
|
delete png;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_CreateCanvasFromPNG
|
|
//
|
|
// Creates a simple canvas containing the contents of the PNG file's IDAT
|
|
// chunk(s). Only 8-bit images are supported.
|
|
//
|
|
//==========================================================================
|
|
|
|
DCanvas *M_CreateCanvasFromPNG (PNGHandle *png)
|
|
{
|
|
IHDR imageHeader;
|
|
DSimpleCanvas *canvas;
|
|
int width, height;
|
|
unsigned int chunklen;
|
|
|
|
if (M_FindPNGChunk (png, MAKE_ID('I','H','D','R')) == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (png->File->Read (&imageHeader, sizeof(IHDR)) != sizeof(IHDR))
|
|
{
|
|
return NULL;
|
|
}
|
|
if (imageHeader.Width == 0 ||
|
|
imageHeader.Height == 0 ||
|
|
// Only images that M_CreatePNG can write are supported
|
|
imageHeader.BitDepth != 8 ||
|
|
imageHeader.ColorType != 3 ||
|
|
imageHeader.Compression != 0 ||
|
|
imageHeader.Filter != 0 ||
|
|
imageHeader.Interlace != 0
|
|
)
|
|
{
|
|
return NULL;
|
|
}
|
|
chunklen = M_FindPNGChunk (png, MAKE_ID('I','D','A','T'));
|
|
if (chunklen == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
width = BigLong((int)imageHeader.Width);
|
|
height = BigLong((int)imageHeader.Height);
|
|
canvas = new DSimpleCanvas (width, height);
|
|
if (canvas == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
canvas->Lock ();
|
|
bool success = M_ReadIDAT (png->File, canvas->GetBuffer(), width, height, canvas->GetPitch(), 8, 3, 0, chunklen);
|
|
canvas->Unlock ();
|
|
if (!success)
|
|
{
|
|
delete canvas;
|
|
canvas = NULL;
|
|
}
|
|
return canvas;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ReadIDAT
|
|
//
|
|
// Reads image data out of a PNG
|
|
//
|
|
//==========================================================================
|
|
|
|
bool M_ReadIDAT (FileReader *file, BYTE *buffer, int width, int height, int pitch,
|
|
BYTE bitdepth, BYTE colortype, BYTE interlace, unsigned int chunklen)
|
|
{
|
|
// Uninterlaced images are treated as a conceptual eighth pass by these tables.
|
|
static const BYTE passwidthshift[8] = { 3, 3, 2, 2, 1, 1, 0, 0 };
|
|
static const BYTE passheightshift[8] = { 3, 3, 3, 2, 2, 1, 1, 0 };
|
|
static const BYTE passrowoffset[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
|
|
static const BYTE passcoloffset[8] = { 0, 4, 0, 2, 0, 1, 0, 0 };
|
|
|
|
Byte *inputLine, *prev, *curr, *adam7buff[3], *bufferend;
|
|
Byte chunkbuffer[4096];
|
|
z_stream stream;
|
|
int err;
|
|
int i, pass, passbuff, passpitch, passwidth;
|
|
bool lastIDAT;
|
|
int bytesPerRowIn, bytesPerRowOut;
|
|
int bytesPerPixel;
|
|
bool initpass;
|
|
|
|
switch (colortype)
|
|
{
|
|
case 2: bytesPerPixel = 3; break; // RGB
|
|
case 4: bytesPerPixel = 2; break; // LA
|
|
case 6: bytesPerPixel = 4; break; // RGBA
|
|
default: bytesPerPixel = 1; break;
|
|
}
|
|
|
|
bytesPerRowOut = width * bytesPerPixel;
|
|
i = 4 + bytesPerRowOut * 2;
|
|
if (interlace)
|
|
{
|
|
i += bytesPerRowOut * 2;
|
|
}
|
|
inputLine = (Byte *)alloca (i);
|
|
adam7buff[0] = inputLine + 4 + bytesPerRowOut;
|
|
adam7buff[1] = adam7buff[0] + bytesPerRowOut;
|
|
adam7buff[2] = adam7buff[1] + bytesPerRowOut;
|
|
bufferend = buffer + pitch * height;
|
|
|
|
stream.next_in = Z_NULL;
|
|
stream.avail_in = 0;
|
|
stream.zalloc = Z_NULL;
|
|
stream.zfree = Z_NULL;
|
|
err = inflateInit (&stream);
|
|
if (err != Z_OK)
|
|
{
|
|
return false;
|
|
}
|
|
lastIDAT = false;
|
|
initpass = true;
|
|
pass = interlace ? 0 : 7;
|
|
|
|
while (err != Z_STREAM_END && pass < 8 - interlace)
|
|
{
|
|
if (initpass)
|
|
{
|
|
int rowoffset, coloffset;
|
|
|
|
initpass = false;
|
|
pass--;
|
|
do
|
|
{
|
|
pass++;
|
|
rowoffset = passrowoffset[pass];
|
|
coloffset = passcoloffset[pass];
|
|
}
|
|
while ((rowoffset >= height || coloffset >= width) && pass < 7);
|
|
if (pass == 7 && interlace)
|
|
{
|
|
break;
|
|
}
|
|
passwidth = (width + (1 << passwidthshift[pass]) - 1 - coloffset) >> passwidthshift[pass];
|
|
prev = adam7buff[0];
|
|
passbuff = 1;
|
|
memset (prev, 0, passwidth * bytesPerPixel);
|
|
switch (bitdepth)
|
|
{
|
|
case 8: bytesPerRowIn = passwidth * bytesPerPixel; break;
|
|
case 4: bytesPerRowIn = (passwidth+1)/2; break;
|
|
case 2: bytesPerRowIn = (passwidth+3)/4; break;
|
|
case 1: bytesPerRowIn = (passwidth+7)/8; break;
|
|
default: return false;
|
|
}
|
|
curr = buffer + rowoffset*pitch + coloffset*bytesPerPixel;
|
|
passpitch = pitch << passheightshift[pass];
|
|
stream.next_out = inputLine;
|
|
stream.avail_out = bytesPerRowIn + 1;
|
|
}
|
|
if (stream.avail_in == 0 && chunklen > 0)
|
|
{
|
|
stream.next_in = chunkbuffer;
|
|
stream.avail_in = (uInt)file->Read (chunkbuffer, MIN<long>(chunklen,sizeof(chunkbuffer)));
|
|
chunklen -= stream.avail_in;
|
|
}
|
|
|
|
err = inflate (&stream, Z_SYNC_FLUSH);
|
|
if (err != Z_OK && err != Z_STREAM_END)
|
|
{ // something unexpected happened
|
|
inflateEnd (&stream);
|
|
return false;
|
|
}
|
|
|
|
if (stream.avail_out == 0)
|
|
{
|
|
if (pass >= 6)
|
|
{
|
|
// Store pixels directly into the output buffer
|
|
UnfilterRow (bytesPerRowIn, curr, inputLine, prev, bytesPerPixel);
|
|
prev = curr;
|
|
}
|
|
else
|
|
{
|
|
const BYTE *in;
|
|
BYTE *out;
|
|
int colstep, x;
|
|
|
|
// Store pixels into a temporary buffer
|
|
UnfilterRow (bytesPerRowIn, adam7buff[passbuff], inputLine, prev, bytesPerPixel);
|
|
prev = adam7buff[passbuff];
|
|
passbuff ^= 1;
|
|
in = prev;
|
|
if (bitdepth < 8)
|
|
{
|
|
UnpackPixels (passwidth, bytesPerRowIn, bitdepth, in, adam7buff[2]);
|
|
in = adam7buff[2];
|
|
}
|
|
// Distribute pixels into the output buffer
|
|
out = curr;
|
|
colstep = bytesPerPixel << passwidthshift[pass];
|
|
switch (bytesPerPixel)
|
|
{
|
|
case 1:
|
|
for (x = passwidth; x > 0; --x)
|
|
{
|
|
*out = *in;
|
|
out += colstep;
|
|
in += 1;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
for (x = passwidth; x > 0; --x)
|
|
{
|
|
*(WORD *)out = *(WORD *)in;
|
|
out += colstep;
|
|
in += 2;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
for (x = passwidth; x > 0; --x)
|
|
{
|
|
out[0] = in[0];
|
|
out[1] = in[1];
|
|
out[2] = in[2];
|
|
out += colstep;
|
|
in += 3;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
for (x = passwidth; x > 0; --x)
|
|
{
|
|
*(DWORD *)out = *(DWORD *)in;
|
|
out += colstep;
|
|
in += 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if ((curr += passpitch) >= bufferend)
|
|
{
|
|
++pass;
|
|
initpass = true;
|
|
}
|
|
stream.next_out = inputLine;
|
|
stream.avail_out = bytesPerRowIn + 1;
|
|
}
|
|
|
|
if (chunklen == 0 && !lastIDAT)
|
|
{
|
|
DWORD x[3];
|
|
|
|
if (file->Read (x, 12) != 12)
|
|
{
|
|
lastIDAT = true;
|
|
}
|
|
else if (x[2] != MAKE_ID('I','D','A','T'))
|
|
{
|
|
lastIDAT = true;
|
|
}
|
|
else
|
|
{
|
|
chunklen = BigLong((unsigned int)x[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
inflateEnd (&stream);
|
|
|
|
if (bitdepth < 8)
|
|
{
|
|
// Noninterlaced images must be unpacked completely.
|
|
// Interlaced images only need their final pass unpacked.
|
|
passpitch = pitch << interlace;
|
|
for (curr = buffer + pitch * interlace; curr <= prev; curr += passpitch)
|
|
{
|
|
UnpackPixels (width, bytesPerRowIn, bitdepth, curr, curr);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// PRIVATE CODE ------------------------------------------------------------
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// MakeChunk
|
|
//
|
|
// Prepends the chunk length and type and appends the chunk's CRC32.
|
|
// There must be 8 bytes available before the chunk passed and 4 bytes
|
|
// after the chunk.
|
|
//
|
|
//==========================================================================
|
|
|
|
static inline void MakeChunk (void *where, DWORD type, size_t len)
|
|
{
|
|
BYTE *const data = (BYTE *)where;
|
|
*(DWORD *)(data - 8) = BigLong ((unsigned int)len);
|
|
*(DWORD *)(data - 4) = type;
|
|
*(DWORD *)(data + len) = BigLong ((unsigned int)CalcCRC32 (data-4, (unsigned int)(len+4)));
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// StuffPalette
|
|
//
|
|
// Converts 256 4-byte palette entries to 3 bytes each.
|
|
//
|
|
//==========================================================================
|
|
|
|
static inline void StuffPalette (const PalEntry *from, BYTE *to)
|
|
{
|
|
for (int i = 256; i > 0; --i)
|
|
{
|
|
to[0] = from->r;
|
|
to[1] = from->g;
|
|
to[2] = from->b;
|
|
from += 1;
|
|
to += 3;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// StuffBitmap
|
|
//
|
|
// Given a bitmap, creates one or more IDAT chunks in the given file.
|
|
// Returns true on success.
|
|
//
|
|
//==========================================================================
|
|
|
|
static bool StuffBitmap (const DCanvas *canvas, FILE *file)
|
|
{
|
|
const int pitch = canvas->GetPitch();
|
|
const int width = canvas->GetWidth();
|
|
const int height = canvas->GetHeight();
|
|
BYTE *from = canvas->GetBuffer();
|
|
|
|
Byte buffer[PNG_WRITE_SIZE];
|
|
Byte zero = 0;
|
|
z_stream stream;
|
|
int err;
|
|
int y;
|
|
|
|
stream.next_in = Z_NULL;
|
|
stream.avail_in = 0;
|
|
stream.zalloc = Z_NULL;
|
|
stream.zfree = Z_NULL;
|
|
err = deflateInit (&stream, png_level);
|
|
|
|
if (err != Z_OK)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
y = height;
|
|
stream.next_out = buffer;
|
|
stream.avail_out = sizeof(buffer);
|
|
|
|
while (y > 0 && err == Z_OK)
|
|
{
|
|
y--;
|
|
for (int i = 2; i && err == Z_OK; --i)
|
|
{
|
|
const int flushiness = (y == 0 && i == 1) ? Z_FINISH : 0;
|
|
if (i == 2)
|
|
{ // always use filter type 0
|
|
stream.next_in = &zero;
|
|
stream.avail_in = 1;
|
|
}
|
|
else
|
|
{
|
|
stream.next_in = from;
|
|
stream.avail_in = width;
|
|
from += pitch;
|
|
}
|
|
err = deflate (&stream, flushiness);
|
|
if (err != Z_OK)
|
|
{
|
|
break;
|
|
}
|
|
while (stream.avail_out == 0)
|
|
{
|
|
if (!WriteIDAT (file, buffer, sizeof(buffer)))
|
|
{
|
|
return false;
|
|
}
|
|
stream.next_out = buffer;
|
|
stream.avail_out = sizeof(buffer);
|
|
if (stream.avail_in != 0)
|
|
{
|
|
err = deflate (&stream, flushiness);
|
|
if (err != Z_OK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (err == Z_OK)
|
|
{
|
|
err = deflate (&stream, Z_FINISH);
|
|
if (err != Z_OK)
|
|
{
|
|
break;
|
|
}
|
|
if (stream.avail_out == 0)
|
|
{
|
|
if (!WriteIDAT (file, buffer, sizeof(buffer)))
|
|
{
|
|
return false;
|
|
}
|
|
stream.next_out = buffer;
|
|
stream.avail_out = sizeof(buffer);
|
|
}
|
|
}
|
|
|
|
y = sizeof(buffer) - stream.avail_out;
|
|
deflateEnd (&stream);
|
|
|
|
if (err != Z_STREAM_END)
|
|
{
|
|
return false;
|
|
}
|
|
return WriteIDAT (file, buffer, sizeof(buffer)-stream.avail_out);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// WriteIDAT
|
|
//
|
|
// Writes a single IDAT chunk to the file. Returns true on success.
|
|
//
|
|
//==========================================================================
|
|
|
|
static bool WriteIDAT (FILE *file, const BYTE *data, int len)
|
|
{
|
|
DWORD foo[2], crc;
|
|
|
|
foo[0] = BigLong (len);
|
|
foo[1] = MAKE_ID('I','D','A','T');
|
|
crc = CalcCRC32 ((BYTE *)&foo[1], 4);
|
|
crc = BigLong ((unsigned int)AddCRC32 (crc, data, len));
|
|
|
|
if (fwrite (foo, 1, 8, file) != 8 ||
|
|
fwrite (data, 1, len, file) != (size_t)len ||
|
|
fwrite (&crc, 1, 4, file) != 4)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// UnfilterRow
|
|
//
|
|
// Unfilters the given row. Unknown filter types are silently ignored.
|
|
// bpp is bytes per pixel, not bits per pixel.
|
|
// width is in bytes, not pixels.
|
|
//
|
|
//==========================================================================
|
|
|
|
void UnfilterRow (int width, BYTE *dest, BYTE *row, BYTE *prev, int bpp)
|
|
{
|
|
int x;
|
|
|
|
switch (*row++)
|
|
{
|
|
case 1: // Sub
|
|
x = bpp;
|
|
do
|
|
{
|
|
*dest++ = *row++;
|
|
}
|
|
while (--x);
|
|
for (x = width - bpp; x > 0; --x)
|
|
{
|
|
*dest = *row++ + *(dest - bpp);
|
|
dest++;
|
|
}
|
|
break;
|
|
|
|
case 2: // Up
|
|
x = width;
|
|
do
|
|
{
|
|
*dest++ = *row++ + *prev++;
|
|
}
|
|
while (--x);
|
|
break;
|
|
|
|
case 3: // Average
|
|
x = bpp;
|
|
do
|
|
{
|
|
*dest++ = *row++ + (*prev++)/2;
|
|
}
|
|
while (--x);
|
|
for (x = width - bpp; x > 0; --x)
|
|
{
|
|
*dest = *row++ + (BYTE)((unsigned(*(dest - bpp)) + unsigned(*prev++)) >> 1);
|
|
dest++;
|
|
}
|
|
break;
|
|
|
|
case 4: // Paeth
|
|
x = bpp;
|
|
do
|
|
{
|
|
*dest++ = *row++ + *prev++;
|
|
}
|
|
while (--x);
|
|
for (x = width - bpp; x > 0; --x)
|
|
{
|
|
int a, b, c, pa, pb, pc;
|
|
|
|
a = *(dest - bpp);
|
|
b = *(prev);
|
|
c = *(prev - bpp);
|
|
pa = b - c;
|
|
pb = a - c;
|
|
pc = abs (pa + pb);
|
|
pa = abs (pa);
|
|
pb = abs (pb);
|
|
*dest = *row + (BYTE)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c);
|
|
dest++;
|
|
row++;
|
|
prev++;
|
|
}
|
|
break;
|
|
|
|
default: // Treat everything else as filter type 0 (none)
|
|
memcpy (dest, row, width);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// UnpackPixels
|
|
//
|
|
// Unpacks a row of pixels whose depth is less than 8 so that each pixel
|
|
// occupies a single byte. The outrow must be "width" bytes long.
|
|
// "bytesPerRow" is the number of bytes for the packed row. The in and out
|
|
// rows may overlap, but only if rowin == rowout.
|
|
//
|
|
//==========================================================================
|
|
|
|
static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const BYTE *rowin, BYTE *rowout)
|
|
{
|
|
const BYTE *in;
|
|
BYTE *out;
|
|
BYTE pack;
|
|
int lastbyte;
|
|
|
|
out = rowout + width;
|
|
in = rowin + bytesPerRow;
|
|
|
|
switch (bitdepth)
|
|
{
|
|
case 1:
|
|
|
|
lastbyte = width & 7;
|
|
if (lastbyte != 0)
|
|
{
|
|
in--;
|
|
pack = *in;
|
|
out -= lastbyte;
|
|
out[0] = (pack >> 7) & 1;
|
|
if (lastbyte >= 2) out[1] = (pack >> 6) & 1;
|
|
if (lastbyte >= 3) out[2] = (pack >> 5) & 1;
|
|
if (lastbyte >= 4) out[3] = (pack >> 4) & 1;
|
|
if (lastbyte >= 5) out[4] = (pack >> 3) & 1;
|
|
if (lastbyte >= 6) out[5] = (pack >> 2) & 1;
|
|
if (lastbyte == 7) out[6] = (pack >> 1) & 1;
|
|
}
|
|
|
|
while (in-- > rowin)
|
|
{
|
|
pack = *in;
|
|
out -= 8;
|
|
out[0] = (pack >> 7) & 1;
|
|
out[1] = (pack >> 6) & 1;
|
|
out[2] = (pack >> 5) & 1;
|
|
out[3] = (pack >> 4) & 1;
|
|
out[4] = (pack >> 3) & 1;
|
|
out[5] = (pack >> 2) & 1;
|
|
out[6] = (pack >> 1) & 1;
|
|
out[7] = pack & 1;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
|
|
lastbyte = width & 3;
|
|
if (lastbyte != 0)
|
|
{
|
|
in--;
|
|
pack = *in;
|
|
out -= lastbyte;
|
|
out[0] = pack >> 6;
|
|
if (lastbyte >= 2) out[1] = (pack >> 4) & 3;
|
|
if (lastbyte == 3) out[2] = (pack >> 2) & 3;
|
|
}
|
|
|
|
while (in-- > rowin)
|
|
{
|
|
pack = *in;
|
|
out -= 4;
|
|
out[0] = pack >> 6;
|
|
out[1] = (pack >> 4) & 3;
|
|
out[2] = (pack >> 2) & 3;
|
|
out[3] = pack & 3;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
lastbyte = width & 1;
|
|
if (lastbyte != 0)
|
|
{
|
|
in--;
|
|
pack = *in;
|
|
out -= lastbyte;
|
|
out[0] = pack >> 4;
|
|
}
|
|
|
|
while (in-- > rowin)
|
|
{
|
|
pack = *in;
|
|
out -= 2;
|
|
out[0] = pack >> 4;
|
|
out[1] = pack & 15;
|
|
}
|
|
break;
|
|
}
|
|
}
|