feat: Overhaul font format into CrossPoint font

This commit is contained in:
Dave Allie
2026-01-28 23:30:39 +11:00
parent da4d3b5ea5
commit 567fa6e1e2
142 changed files with 254312 additions and 263366 deletions

View File

@@ -0,0 +1,51 @@
#include "g5enc.inl"
#include "g5dec.inl"
//
// Group5 1-bit image compression library
// Written by Larry Bank (bitbank@pobox.com)
// Decoder C++ wrapper functions
//
// 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/>.
//
int G5DECODER::init(int iWidth, int iHeight, uint8_t *pData, int iDataSize)
{
return g5_decode_init(&_g5dec, iWidth, iHeight, pData, iDataSize);
} /* init() */
int G5DECODER::decodeLine(uint8_t *pOut)
{
return g5_decode_line(&_g5dec, pOut);
} /* decodeLine() */
//
// Encoder C++ wrapper functions
//
int G5ENCODER::init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
{
return g5_encode_init(&_g5enc, iWidth, iHeight, pOut, iOutSize);
} /* init() */
int G5ENCODER::encodeLine(uint8_t *pPixels)
{
return g5_encode_encodeLine(&_g5enc, pPixels);
} /* encodeLine() */
int G5ENCODER::size()
{
return g5_encode_getOutSize(&_g5enc);
} /* size() */

View File

@@ -0,0 +1,171 @@
#ifndef __GROUP5__
#define __GROUP5__
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __AVR__
#include <avr/pgmspace.h>
#endif
//
// Group5 1-bit image compression 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/>.
//
// The name "Group5" is derived from the CCITT Group4 standard
// This code is based on a lot of the good ideas from CCITT T.6
// for FAX image compression, but modified to work in a very
// constrained environment. The Huffman tables for horizontal
// mode have been replaced with a simple 2-bit flag followed by
// short or long counts of a fixed length. The short codes are
// always 3 bits (run lengths 0-7) and the long codes are the
// number of bits needed to encode the width of the image.
// For example, if a 320 pixel wide image is being compressed,
// the longest horizontal run needed is 320, which requires 9
// bits to encode. The 2 prefix bits have the following meaning:
// 00 = short, short (3+3 bits)
// 01 = short, long (3+N bits)
// 10 = long, short (N+3 bits)
// 11 = long, long (N+N bits)
// The rest of the code works identically to Group4 2D FAX
//
// Caution - this is the maximum number of color changes per line
// The default value is set low to work embedded systems with little RAM
// for font compression, this is plenty since each line of a character should have
// a maximum of 7 color changes
// You can define this in your compiler macros to override the default vlaue
//
#ifndef MAX_IMAGE_FLIPS
#ifdef __AVR__
#define MAX_IMAGE_FLIPS 32
#else
#define MAX_IMAGE_FLIPS 512
#endif // __AVR__
#endif
// Horizontal prefix bits
enum {
HORIZ_SHORT_SHORT=0,
HORIZ_SHORT_LONG,
HORIZ_LONG_SHORT,
HORIZ_LONG_LONG
};
// Return code for encoder and decoder
enum {
G5_SUCCESS = 0,
G5_INVALID_PARAMETER,
G5_DECODE_ERROR,
G5_UNSUPPORTED_FEATURE,
G5_ENCODE_COMPLETE,
G5_DECODE_COMPLETE,
G5_NOT_INITIALIZED,
G5_DATA_OVERFLOW,
G5_MAX_FLIPS_EXCEEDED
};
//
// Decoder state
//
typedef struct g5_dec_image_tag
{
int iWidth, iHeight; // image size
int iError;
int y; // last y value drawn
int iVLCSize;
int iHLen; // length of 'long' horizontal codes for this image
int iPitch; // width in bytes of output buffer
uint32_t u32Accum; // fractional scaling accumulator
uint32_t ulBitOff, ulBits; // vlc decode variables
uint8_t *pSrc, *pBuf; // starting & current buffer pointer
int16_t *pCur, *pRef; // current state of current vs reference flips
int16_t CurFlips[MAX_IMAGE_FLIPS];
int16_t RefFlips[MAX_IMAGE_FLIPS];
} G5DECIMAGE;
// Due to unaligned memory causing an exception, we have to do these macros the slow way
#ifdef __AVR__
// assume PROGMEM as the source of data
inline uint32_t TIFFMOTOLONG(uint8_t *p)
{
uint32_t u32 = pgm_read_dword(p);
return __builtin_bswap32(u32);
}
#else
#define TIFFMOTOLONG(p) (((uint32_t)(*p)<<24UL) + ((uint32_t)(*(p+1))<<16UL) + ((uint32_t)(*(p+2))<<8UL) + (uint32_t)(*(p+3)))
#endif // __AVR__
#define TOP_BIT 0x80000000
#define MAX_VALUE 0xffffffff
// Must be a 32-bit target processor
#define REGISTER_WIDTH 32
#define BIGUINT uint32_t
//
// G5 Encoder
//
typedef struct g5_buffered_bits
{
unsigned char *pBuf; // buffer pointer
uint32_t ulBits; // buffered bits
uint32_t ulBitOff; // current bit offset
uint32_t ulDataSize; // available data
} G5_BUFFERED_BITS;
//
// Encoder state
//
typedef struct g5_enc_image_tag
{
int iWidth, iHeight; // image size
int iError;
int y; // last y encoded
int iOutSize;
int iDataSize; // generated output size
uint8_t *pOutBuf;
int16_t *pCur, *pRef; // pointers to swap current and reference lines
G5_BUFFERED_BITS bb;
int16_t CurFlips[MAX_IMAGE_FLIPS];
int16_t RefFlips[MAX_IMAGE_FLIPS];
} G5ENCIMAGE;
#ifdef __cplusplus
//
// The G5 classes wrap portable C code which does the actual work
//
class G5ENCODER
{
public:
int init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize);
int encodeLine(uint8_t *pPixels);
int size();
private:
G5ENCIMAGE _g5enc;
};
class G5DECODER
{
public:
int init(int iWidth, int iHeight, uint8_t *pData, int iDataSize);
int decodeLine(uint8_t *pOut);
private:
G5DECIMAGE _g5dec;
};
#endif // __cplusplus
#endif // __GROUP5__

View File

@@ -0,0 +1,346 @@
//
// Group5
// A 1-bpp image decoder
//
// 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"
/*
The code tree that follows has: bit_length, decode routine
These codes are for Group 4 (MMR) decoding
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
*/
static const uint8_t code_table[128] =
{0x90, 0, 0x40, 0, /* trash, uncompr mode - codes 0 and 1 */
3, 7, /* V(-3) pos = 2 */
0x13, 7, /* V(3) pos = 3 */
2, 6, 2, 6, /* V(-2) pos = 4,5 */
0x12, 6, 0x12, 6, /* V(2) pos = 6,7 */
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, /* pass pos = 8->F */
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, /* horiz pos = 10->1F */
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
/* V(-1) pos = 20->2F */
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, /* V(1) pos = 30->3F */
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3};
static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
{
if (pImage == NULL || iWidth < 1 || iHeight < 1 || pData == NULL || iDataSize < 1)
return G5_INVALID_PARAMETER;
pImage->iVLCSize = iDataSize;
pImage->pSrc = pData;
pImage->ulBitOff = 0;
pImage->y = 0;
pImage->ulBits = TIFFMOTOLONG(pData); // preload the first 32 bits of data
pImage->iWidth = iWidth;
pImage->iHeight = iHeight;
return G5_SUCCESS;
} /* g5_decode_init() */
static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
{
int x, len, run;
uint8_t lBit, rBit, *p;
int iStart = 0, xright = pPage->iWidth;
uint8_t *pDest;
iStart = 0;
pDest = pOut;
len = (xright+7)>>3; // number of bytes to generate
for (x=0; x<len; x++) {
pOut[x] = 0xff; // start with white and only draw the black runs
}
x = 0;
while (x < xright) { // while the scaled x is within the window bounds
x = *pCurFlips++; // black starting point
run = *pCurFlips++ - x; // get the black run
x -= iStart;
if (x >= xright || run == 0)
break;
if ((x + run) > 0) { /* If the run is visible, draw it */
if (x < 0) {
run += x; /* draw only visible part of run */
x = 0;
}
if ((x + run) > xright) { /* Don't let it go off right edge */
run = xright - x;
}
/* Draw this run */
lBit = 0xff << (8 - (x & 7));
rBit = 0xff >> ((x + run) & 7);
len = ((x+run)>>3) - (x >> 3);
p = &pDest[x >> 3];
if (len == 0) {
lBit |= rBit;
*p &= lBit;
} else {
*p++ &= lBit;
while (len > 1) {
*p++ = 0;
len--;
}
*p = rBit;
}
} // visible run
} /* while drawing line */
} /* G5DrawLine() */
//
// Initialize internal structures to decode the image
//
static void Decode_Begin(G5DECIMAGE *pPage)
{
int i, xsize;
int16_t *CurFlips, *RefFlips;
xsize = pPage->iWidth;
RefFlips = pPage->RefFlips;
CurFlips = pPage->CurFlips;
/* Seed the current and reference line with XSIZE for V(0) codes */
for (i=0; i<MAX_IMAGE_FLIPS-2; i++) {
RefFlips[i] = xsize;
CurFlips[i] = xsize;
}
/* Prefill both current and reference lines with 7fff to prevent it from
walking off the end if the data gets bunged and the current X is > XSIZE
3-16-94 */
CurFlips[i] = RefFlips[i] = 0x7fff;
CurFlips[i+1] = RefFlips[i+1] = 0x7fff;
pPage->pCur = CurFlips;
pPage->pRef = RefFlips;
pPage->pBuf = pPage->pSrc;
pPage->ulBits = TIFFMOTOLONG(pPage->pSrc); // load 32 bits to start
pPage->ulBitOff = 0;
// Calculate the number of bits needed for a long horizontal code
#ifdef __AVR__
pPage->iHLen = 16 - __builtin_clz(pPage->iWidth);
#else
pPage->iHLen = 32 - __builtin_clz(pPage->iWidth);
#endif
} /* Decode_Begin() */
//
// Decode a single line of G5 data (private function)
//
static int DecodeLine(G5DECIMAGE *pPage)
{
signed int a0, a0_p, b1;
int16_t *pCur, *pRef, *RefFlips, *CurFlips;
int xsize, tot_run=0, tot_run1 = 0;
int32_t sCode;
uint32_t lBits;
uint32_t ulBits, ulBitOff;
uint8_t *pBuf/*, *pBufEnd*/;
uint32_t u32HMask, u32HLen; // horizontal code mask and length
pCur = CurFlips = pPage->pCur;
pRef = RefFlips = pPage->pRef;
ulBits = pPage->ulBits;
ulBitOff = pPage->ulBitOff;
pBuf = pPage->pBuf;
// pBufEnd = &pPage->pSrc[pPage->iVLCSize];
u32HLen = pPage->iHLen;
u32HMask = (1 << u32HLen) - 1;
a0 = -1;
xsize = pPage->iWidth;
while (a0 < xsize) { /* Decode this line */
if (ulBitOff > (REGISTER_WIDTH-8)) { // need at least 7 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
if ((int32_t)(ulBits << ulBitOff) < 0) { /* V(0) code is the most frequent case (1 bit) */
a0 = *pRef++;
ulBitOff++; // length = 1 bit
*pCur++ = a0;
} else { /* Slower method for the less frequence codes */
lBits = (ulBits >> ((REGISTER_WIDTH - 8) - ulBitOff)) & 0xfe; /* Only the first 7 bits are useful */
sCode = code_table[lBits]; /* Get the code type as an 8-bit value */
ulBitOff += code_table[lBits+1]; /* Get the code length */
switch (sCode) {
case 1: /* V(-1) */
case 2: /* V(-2) */
case 3: /* V(-3) */
a0 = *pRef - sCode; /* A0 = B1 - x */
*pCur++ = a0;
if (pRef == RefFlips) {
pRef += 2;
}
pRef--;
while (a0 >= *pRef) {
pRef += 2;
}
break;
case 0x11: /* V(1) */
case 0x12: /* V(2) */
case 0x13: /* V(3) */
a0 = *pRef++; /* A0 = B1 */
b1 = a0;
a0 += sCode & 7; /* A0 = B1 + x */
if (b1 != xsize && a0 < xsize) {
while (a0 >= *pRef) {
pRef += 2;
}
}
if (a0 > xsize) {
a0 = xsize;
}
*pCur++ = a0;
break;
case 0x20: /* Horizontal codes */
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
a0_p = a0;
if (a0 < 0) {
a0_p = 0;
}
lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3; // get 2-bit prefix for code type
// There are 4 possible horizontal cases: short/short, short/long, long/short, long/long
// These are encoded in a 2-bit prefix code, followed by 3 bits for short or N bits for long code
// N is the log base 2 of the image width (e.g. 320 pixels requires 9 bits)
ulBitOff += 2;
switch (lBits) {
case HORIZ_SHORT_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
break;
case HORIZ_SHORT_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
break;
case HORIZ_LONG_SHORT:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
ulBitOff += 3;
break;
case HORIZ_LONG_LONG:
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
pBuf += (ulBitOff >> 3);
ulBitOff &= 7;
ulBits = TIFFMOTOLONG(pBuf);
}
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
ulBitOff += u32HLen;
break;
} // switch on lBits
a0 = a0_p + tot_run;
*pCur++ = a0;
a0 += tot_run1;
if (a0 < xsize) {
while (a0 >= *pRef) {
pRef += 2;
}
}
*pCur++ = a0;
break;
case 0x30: /* Pass code */
pRef++; /* A0 = B2, iRef+=2 */
a0 = *pRef++;
break;
default: /* ERROR */
pPage->iError = G5_DECODE_ERROR;
goto pilreadg5z;
} /* switch */
} /* Slow climb */
}
/*--- Convert flips data into run lengths ---*/
*pCur++ = xsize; /* Terminate the line properly */
*pCur++ = xsize;
pilreadg5z:
// Save the current VLC decoder state
pPage->ulBits = ulBits;
pPage->ulBitOff = ulBitOff;
pPage->pBuf = pBuf;
return pPage->iError;
} /* DecodeLine() */
//
// Decompress the VLC data
//
static int g5_decode_line(G5DECIMAGE *pPage, uint8_t *pOut)
{
int rc;
uint8_t *pBufEnd;
int16_t *t1;
if (pPage == NULL || pOut == NULL)
return G5_INVALID_PARAMETER;
if (pPage->y >= pPage->iHeight)
return G5_DECODE_COMPLETE;
pPage->iError = G5_SUCCESS;
if (pPage->y == 0) { // first time through
Decode_Begin(pPage);
}
pBufEnd = &pPage->pSrc[pPage->iVLCSize];
if (pPage->pBuf >= pBufEnd) { // read past the end, error
pPage->iError = G5_DECODE_ERROR;
return G5_DECODE_ERROR;
}
rc = DecodeLine(pPage);
if (rc == G5_SUCCESS) {
// Draw the current line
G5DrawLine(pPage, pPage->pCur, pOut);
/*--- Swap current and reference lines ---*/
t1 = pPage->pRef;
pPage->pRef = pPage->pCur;
pPage->pCur = t1;
pPage->y++;
if (pPage->y >= pPage->iHeight) {
pPage->iError = G5_DECODE_COMPLETE;
}
} else {
pPage->iError = rc;
}
return pPage->iError;
} /* Decode() */

View File

@@ -0,0 +1,315 @@
//
// 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() */