位深度为 1 的 ZLib PNG 压缩

ZLib PNG Compression with a bit depth of 1

我有一个字节数组,其中每个字节都与图像的像素值相关。这些字节值恰好是 0 或 255。数组从左到右然后从上到下打包。我想从这个数组创建一个位深度为 1 的灰度 .PNG 图像。我不能对 zlib 使用任何包装器。

该函数在创建有效的 png 文件时起作用。但是创建的图像不正确。我相当确定(尽管可能是错误的)问题出在将数据打包到我传递给 Zlib deflate 函数的字节数组中。我已通读规范:https://www.w3.org/TR/PNG/ 无济于事。

我有以下代码:

#include "zlib.h"

enum E_PNGImageType
{
  eGreyScale = 0,
  eTrueColour = 2,
  eIndexedColour = 3,
  eGreyScaleAlpha = 4,
  eTrueColourAlpha = 6
};

enum E_PNGBitDepth
{
  eOne = 1,
  eTwo = 2,
  eFour = 4,
  eEight = 8,
  eSixteen = 16
};

enum E_PNGCompressionMethod
{
  eDeflate = 0
};


enum E_PNGFilterMethod
{
  eAdaptive = 0
};

enum E_PNGInterlaceMethod
{
  eNone = 0,
  eAdam7 = 1
};

void CreatePNG(BYTE *pData, int iWidth, int iHeight)
{
  /* Convert each byte to a bit and package the bits into a byte */
  std::vector<BYTE> vBitData;
  int bit = 0;
  BYTE value = 0;
  vBitData.clear();
  for (int h = 0; h < iHeight; h++)
  {
    for (int w = 0; w < iWidth; w++)
    {
      if (pData[(h * iWidth) + w] > 0)
      {
        value += (1 << (7 - bit));
      }
      bit++;
      if (bit == 8)
      {
        bit = 0;
        vBitData.push_back(value);
        value = 0;
      }
    }
  }
  if (bit > 0)
  {
    vBitData.push_back(value);
  }

  GeneratePNGData(vBitData.data(), iWidth, iHeight, vBitData.size(), &vPNGData);
}

void GeneratePNGData(BYTE *pData, unsigned int uiWidth, unsigned int uiHeight, unsigned int uiSize, std::vector<BYTE> *pPNGData)
{
  int iCRCStartIndex;
  int iSize;
  unsigned int uiCRC;
  z_stream strm;
  const int C_BUFFER_SIZE = 20000;
  unsigned char tempBuffer[C_BUFFER_SIZE];
  int iLengthDataIndex;
  int iRes;

  pPNGData->clear();

  /* PNG Signature */
  pPNGData->push_back(137);
  pPNGData->push_back(80);
  pPNGData->push_back(78);
  pPNGData->push_back(71);
  pPNGData->push_back(13);
  pPNGData->push_back(10);
  pPNGData->push_back(26);
  pPNGData->push_back(10);

  /* IDHR Image Header */
  /* 4 Bytes: Chunk Length */
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(13);
  /* Checksum Start Index */
  iCRCStartIndex = pPNGData->size();
  /* 4 Bytes: Chunk Type */
  pPNGData->push_back(73);
  pPNGData->push_back(72);
  pPNGData->push_back(68);
  pPNGData->push_back(82);
  /* 4 Bytes: Chunk Data - Width */
  pPNGData->push_back(((BYTE*)&uiWidth)[3]);
  pPNGData->push_back(((BYTE*)&uiWidth)[2]);
  pPNGData->push_back(((BYTE*)&uiWidth)[1]);
  pPNGData->push_back(((BYTE*)&uiWidth)[0]);
  /* 4 Bytes: Chunk Data - Height */
  pPNGData->push_back(((BYTE*)&uiHeight)[3]);
  pPNGData->push_back(((BYTE*)&uiHeight)[2]);
  pPNGData->push_back(((BYTE*)&uiHeight)[1]);
  pPNGData->push_back(((BYTE*)&uiHeight)[0]);
  /* 1 Byte: Chunk Data - Bit Depth */
  pPNGData->push_back(E_PNGBitDepth::eOne);
  /* 1 Byte: Chunk Data - Colour Type */
  pPNGData->push_back(E_PNGImageType::eGreyScale);
  /* 1 Byte: Chunk Data - Compression Method */
  pPNGData->push_back(E_PNGCompressionMethod::eDeflate);
  /* 1 Byte: Chunk Data - Filter Method */
  pPNGData->push_back(E_PNGFilterMethod::eAdaptive);
  /* 1 Byte: Chunk Data - Interlace Method */
  pPNGData->push_back(E_PNGInterlaceMethod::eNone);
  /* Size of Data to Perform Checksum Over */
  iSize = pPNGData->size() - iCRCStartIndex;
  /* 4 Bytes: CRC */
  uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
  pPNGData->push_back(((BYTE*)&uiCRC)[3]);
  pPNGData->push_back(((BYTE*)&uiCRC)[2]);
  pPNGData->push_back(((BYTE*)&uiCRC)[1]);
  pPNGData->push_back(((BYTE*)&uiCRC)[0]);

  /* IDAT Image Data */
  /* Length Data Offset */
  iLengthDataIndex = pPNGData->size();
  /* 4 Bytes: Chunk Length */
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  /* Checksum Start Index */
  iCRCStartIndex = pPNGData->size();
  /* 4 Bytes: Chunk Type */
  pPNGData->push_back(73);
  pPNGData->push_back(68);
  pPNGData->push_back(65);
  pPNGData->push_back(84);
  /* Length Bytes: Chunk Data - Compressed Image Data */
  strm.zalloc = Z_NULL;
  strm.zfree = Z_NULL;
  strm.opaque = Z_NULL;
  deflateInit(&strm, Z_DEFLATED);
  strm.avail_in = uiSize;
  strm.next_in = pData;
  strm.avail_out = C_BUFFER_SIZE;
  strm.next_out = tempBuffer;
  iRes = deflate(&strm, Z_FINISH);
  if (iRes != Z_STREAM_END) MessageBox(NULL, "Error", "Error", 0);
  pPNGData->insert(pPNGData->end(), tempBuffer, tempBuffer + strm.total_out);
  deflateEnd(&strm);
  /* Now Length Is Know Edit Length Field */
  (*pPNGData)[iLengthDataIndex + 0] = ((BYTE*)&strm.total_out)[3];
  (*pPNGData)[iLengthDataIndex + 1] = ((BYTE*)&strm.total_out)[2];
  (*pPNGData)[iLengthDataIndex + 2] = ((BYTE*)&strm.total_out)[1];
  (*pPNGData)[iLengthDataIndex + 3] = ((BYTE*)&strm.total_out)[0];
  /* Size of Data to Perform Checksum Over */
  iSize = pPNGData->size() - iCRCStartIndex;
  /* 4 Bytes: CRC */
  uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
  pPNGData->push_back(((BYTE*)&uiCRC)[3]);
  pPNGData->push_back(((BYTE*)&uiCRC)[2]);
  pPNGData->push_back(((BYTE*)&uiCRC)[1]);
  pPNGData->push_back(((BYTE*)&uiCRC)[0]);

  /* IEND Image trailer */
  /* 4 Bytes: Chunk Length */
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  pPNGData->push_back(0);
  /* Checksum Start Index */
  iCRCStartIndex = pPNGData->size();
  /* 4 Bytes: Chunk Type */
  pPNGData->push_back(73);
  pPNGData->push_back(69);
  pPNGData->push_back(78);
  pPNGData->push_back(68);
  /* Size of Data to Perform Checksum Over */
  iSize = pPNGData->size() - iCRCStartIndex;
  /* 4 Bytes: CRC */
  uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
  pPNGData->push_back(((BYTE*)&uiCRC)[3]);
  pPNGData->push_back(((BYTE*)&uiCRC)[2]);
  pPNGData->push_back(((BYTE*)&uiCRC)[1]);
  pPNGData->push_back(((BYTE*)&uiCRC)[0]);

  /* Temp Debug Code */
  FILE* pFile;
  fopen_s(&pFile, "DEBUG_IMAGES\zzz_test_output.png", "wb");
  fwrite((*pPNGData).data(), 1, pPNGData->size(), pFile);
  fclose(pFile);
}

您需要在每一行的开头(在您的代码中 h 循环的开头)压入一个 8 位零。那是 "filter type" 字节,在你的情况下应该是 0,意思是 "no filtering"。

IHDR 中的过滤方法“0”只是说 IDAT 在每一行的开头包含一个过滤字节。 IHDR 中的选项允许其他方法,例如省略那些字节并假设每行的过滤器类型为“0”。最后,PNG 作者除了 filter-byte-per-row 方法之外从未批准任何方法。

此外,还需要在 h 的循环结束时对 bit>0 进行检查——只需将 h 循环上的右括号向下移动到它的下方,并在那里也重置 value=0,所以每行将被填充以填充最后一个字节。