Xteink-X4-crosspoint-reader/lib/CrossPointFont/Group5/g5enc.inl

316 lines
11 KiB
C++

//
// G5 Encoder
// A 1-bpp image encoding library
//
// Written by Larry Bank (bitbank@pobox.com)
//
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "Group5.h"
/* Number of consecutive 1 bits in a byte from MSB to LSB */
static uint8_t bitcount[256] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0-15 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 16-31 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 32-47 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 48-63 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 64-79 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 80-95 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 96-111 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 112-127 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 128-143 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 144-159 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 160-175 */
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 176-191 */
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 192-207 */
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 208-223 */
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 224-239 */
4,4,4,4,4,4,4,4,5,5,5,5,6,6,7,8}; /* 240-255 */
/* Table of vertical codes for G5 encoding */
/* code followed by length, starting with v(-3) */
static const uint8_t vtable[14] =
{3,7, /* V(-3) = 0000011 */
3,6, /* V(-2) = 000011 */
3,3, /* V(-1) = 011 */
1,1, /* V(0) = 1 */
2,3, /* V(1) = 010 */
2,6, /* V(2) = 000010 */
2,7}; /* V(3) = 0000010 */
static void G5ENCInsertCode(G5_BUFFERED_BITS *bb, BIGUINT ulCode, int iLen)
{
if ((bb->ulBitOff + iLen) > REGISTER_WIDTH) { // need to write data
bb->ulBits |= (ulCode >> (bb->ulBitOff + iLen - REGISTER_WIDTH)); // partial bits on first word
*(BIGUINT *)bb->pBuf = __builtin_bswap32(bb->ulBits);
bb->pBuf += sizeof(BIGUINT);
bb->ulBits = ulCode << ((REGISTER_WIDTH*2) - (bb->ulBitOff + iLen));
bb->ulBitOff += iLen - REGISTER_WIDTH;
} else {
bb->ulBits |= (ulCode << (REGISTER_WIDTH - bb->ulBitOff - iLen));
bb->ulBitOff += iLen;
}
} /* G5ENCInsertCode() */
//
// Flush any buffered bits to the output
//
static void G5ENCFlushBits(G5_BUFFERED_BITS *bb)
{
while (bb->ulBitOff >= 8)
{
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
bb->ulBits <<= 8;
bb->ulBitOff -= 8;
}
if (bb->ulBitOff) { // partial byte?
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
}
bb->ulBitOff = 0;
bb->ulBits = 0;
} /* G5ENCFlushBits() */
//
// Initialize the compressor
// This must be called before adding data to the output
//
static int g5_encode_init(G5ENCIMAGE *pImage, int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
{
int iError = G5_SUCCESS;
if (pImage == NULL || iHeight <= 0)
return G5_INVALID_PARAMETER;
pImage->iWidth = iWidth; // image size
pImage->iHeight = iHeight;
pImage->pCur = pImage->CurFlips;
pImage->pRef = pImage->RefFlips;
pImage->pOutBuf = pOut; // optional output buffer
pImage->iOutSize = iOutSize; // output buffer pre-allocated size
pImage->iDataSize = 0; // no data yet
pImage->y = 0;
for (int i=0; i<MAX_IMAGE_FLIPS; i++) {
pImage->RefFlips[i] = iWidth;
pImage->CurFlips[i] = iWidth;
}
pImage->bb.pBuf = pImage->pOutBuf;
pImage->bb.ulBits = 0;
pImage->bb.ulBitOff = 0;
pImage->iError = iError;
return iError;
} /* g5_encode_init() */
//
// Internal function to convert uncompressed 1-bit per pixel data
// into the run-end data needed to feed the G5 encoder
//
static int G5ENCEncodeLine(unsigned char *buf, int xsize, int16_t *pDest)
{
int iCount, xborder;
uint8_t i, c;
int8_t cBits;
int iLen;
int16_t x;
int16_t *pLimit = pDest + (MAX_IMAGE_FLIPS-4);
xborder = xsize;
iCount = (xsize + 7) >> 3; /* Number of bytes per line */
cBits = 8;
iLen = 0; /* Current run length */
x = 0;
c = *buf++; /* Get the first byte to start */
iCount--;
while (iCount >=0) {
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
i = bitcount[c]; /* Get the number of consecutive bits */
iLen += i; /* Add this length to total run length */
c <<= i;
cBits -= i; /* Minus the number in a byte */
if (cBits <= 0)
{
iLen += cBits; /* Adjust length */
cBits = 8;
c = *buf++; /* Get another data byte */
iCount--;
continue; /* Keep doing white until color change */
}
c = ~c; /* flip color to count black pixels */
/* Store the white run length */
xborder -= iLen;
if (xborder < 0)
{
iLen += xborder; /* Make sure run length is not past end */
break;
}
x += iLen;
*pDest++ = x;
iLen = 0;
doblack:
i = bitcount[c]; /* Get consecutive bits */
iLen += i; /* Add to total run length */
c <<= i;
cBits -= i;
if (cBits <= 0)
{
iLen += cBits; /* Adjust length */
cBits = 8;
c = *buf++; /* Get another data byte */
c = ~c; /* Flip color to find black */
iCount--;
if (iCount < 0)
break;
goto doblack;
}
/* Store the black run length */
c = ~c; /* Flip color again to find white pixels */
xborder -= iLen;
if (xborder < 0)
{
iLen += xborder; /* Make sure run length is not past end */
break;
}
x += iLen;
*pDest++ = x;
iLen = 0;
} /* while */
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
*pDest++ = xsize;
*pDest++ = xsize; // Store a few more XSIZE to end the line
*pDest++ = xsize; // so that the compressor doesn't go past
*pDest++ = xsize; // the end of the line
return G5_SUCCESS;
} /* G5ENCEncodeLine() */
//
// Compress a line of pixels and add it to the output
// the input format is expected to be MSB (most significant bit) first
// for example, pixel 0 is in byte 0 at bit 7 (0x80)
// Returns G5ENC_SUCCESS for each line if all is well and G5ENC_IMAGE_COMPLETE
// for the last line
//
static int g5_encode_encodeLine(G5ENCIMAGE *pImage, uint8_t *pPixels)
{
int16_t a0, a0_c, b2, a1;
int dx, run1, run2;
int xsize, iErr, iHighWater;
int iCur, iRef, iLen;
int iHLen; // number of bits for long horizontal codes
int16_t *CurFlips, *RefFlips;
G5_BUFFERED_BITS bb;
if (pImage == NULL || pPixels == NULL)
return G5_INVALID_PARAMETER;
iHighWater = pImage->iOutSize - 32;
iHLen = 32 - __builtin_clz(pImage->iWidth);
memcpy(&bb, &pImage->bb, sizeof(G5_BUFFERED_BITS)); // keep local copy
CurFlips = pImage->pCur;
RefFlips = pImage->pRef;
xsize = pImage->iWidth; /* For performance reasons */
// Convert the incoming line of pixels into run-end data
iErr = G5ENCEncodeLine(pPixels, pImage->iWidth, CurFlips);
if (iErr != G5_SUCCESS) return iErr; // exceeded the maximum number of color changes
/* Encode this line as G5 */
a0 = a0_c = 0;
iCur = iRef = 0;
while (a0 < xsize) {
b2 = RefFlips[iRef+1];
a1 = CurFlips[iCur];
if (b2 < a1) { /* Is b2 to the left of a1? */
/* yes, do pass mode */
a0 = b2;
iRef += 2;
G5ENCInsertCode(&bb, 1, 4); /* Pass code = 0001 */
} else { /* Try vertical and horizontal mode */
dx = RefFlips[iRef] - a1; /* b1 - a1 */
if (dx > 3 || dx < -3) { /* Horizontal mode */
G5ENCInsertCode(&bb, 1, 3); /* Horizontal code = 001 */
run1 = CurFlips[iCur] - a0;
run2 = CurFlips[iCur+1] - CurFlips[iCur];
if (run1 < 8) {
if (run2 < 8) { // short, short
G5ENCInsertCode(&bb, HORIZ_SHORT_SHORT, 2); /* short, short = 00 */
G5ENCInsertCode(&bb, run1, 3);
G5ENCInsertCode(&bb, run2, 3);
} else { // short, long
G5ENCInsertCode(&bb, HORIZ_SHORT_LONG, 2); /* short, long = 01 */
G5ENCInsertCode(&bb, run1, 3);
G5ENCInsertCode(&bb, run2, iHLen);
}
} else { // first run is long
if (run2 < 8) { // long, short
G5ENCInsertCode(&bb, HORIZ_LONG_SHORT, 2); /* long, short = 10 */
G5ENCInsertCode(&bb, run1, iHLen);
G5ENCInsertCode(&bb, run2, 3);
} else { // long, long
G5ENCInsertCode(&bb, HORIZ_LONG_LONG, 2); /* long, long = 11 */
G5ENCInsertCode(&bb, run1, iHLen);
G5ENCInsertCode(&bb, run2, iHLen);
}
}
a0 = CurFlips[iCur+1]; /* a0 = a2 */
if (a0 != xsize) {
iCur += 2; /* Skip two color flips */
while (RefFlips[iRef] != xsize && RefFlips[iRef] <= a0) {
iRef += 2;
}
}
} else { /* Vertical mode */
dx = (dx + 3) * 2; /* Convert to index table */
G5ENCInsertCode(&bb, vtable[dx], vtable[dx+1]);
a0 = a1;
a0_c = 1-a0_c;
if (a0 != xsize) {
if (iRef != 0) {
iRef -= 2;
}
iRef++; /* Skip a color change in cur and ref */
iCur++;
while (RefFlips[iRef] <= a0 && RefFlips[iRef] != xsize) {
iRef += 2;
}
}
} /* vertical mode */
} /* horiz/vert mode */
} /* while x < xsize */
iLen = (int)(bb.pBuf-pImage->pOutBuf);
if (iLen >= iHighWater) { // not enough space
pImage->iError = iErr = G5_DATA_OVERFLOW; // we don't have a better error
return iErr;
}
if (pImage->y == pImage->iHeight-1) { // last line of image
G5ENCFlushBits(&bb); // output the final buffered bits
// wrap up final output
pImage->iDataSize = 1 + (int)(bb.pBuf-pImage->pOutBuf);
iErr = G5_ENCODE_COMPLETE;
}
pImage->pCur = RefFlips; // swap current and reference lines
pImage->pRef = CurFlips;
pImage->y++;
memcpy(&pImage->bb, &bb, sizeof(bb));
return iErr;
} /* g5_encode_encodeLine() */
//
// Returns the number of bytes of G5 created by the encoder
//
static int g5_encode_getOutSize(G5ENCIMAGE *pImage)
{
int iSize = 0;
if (pImage != NULL)
iSize = pImage->iDataSize;
return iSize;
} /* g5_encode_getOutSize() */