599 lines
14 KiB
C
599 lines
14 KiB
C
/*--------------------------------------------------------------------------*\
|
|
| RLE.C - RLE Delta frame code |
|
|
|@@BEGIN_MSINTERNAL |
|
|
| |
|
|
| History: |
|
|
| 01/01/88 toddla Created |
|
|
| 10/30/90 davidmay Reorganized, rewritten somewhat. |
|
|
| 07/11/91 dannymi Un-hacked |
|
|
| 09/15/91 ToddLa Re-hacked |
|
|
|@@END_MSINTERNAL |
|
|
\*--------------------------------------------------------------------------*/
|
|
/**************************************************************************
|
|
*
|
|
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
|
|
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
|
|
* PURPOSE.
|
|
*
|
|
* Copyright (c) 1991 - 1995 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
**************************************************************************/
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <memory.h> // for _fmemcmp()
|
|
#include "msrle.h"
|
|
|
|
#define RLE_ESCAPE 0
|
|
#define RLE_EOL 0
|
|
#define RLE_EOF 1
|
|
#define RLE_JMP 2
|
|
#define RLE_RUN 3
|
|
|
|
typedef BYTE huge * HPRLE;
|
|
typedef BYTE far * LPRLE;
|
|
|
|
RGBTOL gRgbTol = {0, 0};
|
|
|
|
|
|
//
|
|
// RleDeltaFrame
|
|
//
|
|
// Calculate the RLE bits to go from hdib1 to hdib2
|
|
//
|
|
// hdibPrev - Previous DIB
|
|
// hdib - DIB to RLE
|
|
//
|
|
// returns
|
|
//
|
|
// handle to a RLE DIB
|
|
//
|
|
BOOL FAR PASCAL RleDeltaFrame(
|
|
LPBITMAPINFOHEADER lpbiRle, LPBYTE pbRle,
|
|
LPBITMAPINFOHEADER lpbiPrev, LPBYTE pbPrev,
|
|
LPBITMAPINFOHEADER lpbiDib, LPBYTE pbDib,
|
|
int iStart,
|
|
int iLen,
|
|
long tolTemporal,
|
|
long tolSpatial,
|
|
int maxRun,
|
|
int minJump)
|
|
{
|
|
LPBITMAPINFOHEADER lpbi;
|
|
|
|
int biHeight;
|
|
UINT cbJump=0;
|
|
int dy;
|
|
|
|
if (!lpbiDib)
|
|
return FALSE;
|
|
|
|
if (maxRun == 0)
|
|
maxRun = -1;
|
|
|
|
if (minJump == 0)
|
|
minJump = 4;
|
|
|
|
//
|
|
// Get info on the source and dest dibs
|
|
//
|
|
lpbi = lpbiDib;
|
|
biHeight = (int)lpbi->biHeight;
|
|
|
|
if (iLen <= 0)
|
|
iLen = biHeight;
|
|
|
|
iLen = min(biHeight-iStart, iLen);
|
|
|
|
//
|
|
// Hey! we only work with 8bpp DIBs if we get otherwise barf.
|
|
//
|
|
if (lpbi->biBitCount != 8 || lpbi->biCompression != BI_RGB)
|
|
return FALSE;
|
|
|
|
#if 0 // CompressBegin does this..
|
|
//
|
|
// Set up the table for quick sum of squares calculation (see rle.h)
|
|
//
|
|
if (!MakeRgbTable(lpbi))
|
|
return FALSE;
|
|
#endif
|
|
|
|
//
|
|
// lock all the buffers, and start the delta framin'
|
|
//
|
|
lpbi = lpbiRle;
|
|
|
|
if (iStart > 0)
|
|
pbDib = DibXYN(lpbiDib, pbDib,0,iStart,8);
|
|
|
|
if (iStart > 0 && lpbiPrev)
|
|
pbPrev = DibXYN(lpbiPrev,pbPrev,0,iStart,8);
|
|
|
|
if (lpbiPrev == NULL)
|
|
pbPrev = NULL;
|
|
|
|
while(iStart > 0)
|
|
{
|
|
dy = min(iStart,255);
|
|
*pbRle++ = RLE_ESCAPE;
|
|
*pbRle++ = RLE_JMP;
|
|
*pbRle++ = 0;
|
|
*pbRle++ = (BYTE)dy;
|
|
iStart -= dy;
|
|
cbJump += 4;
|
|
}
|
|
|
|
lpbi->biHeight = iLen;
|
|
|
|
|
|
#ifdef _WIN32
|
|
DeltaFrameC(
|
|
#else
|
|
DeltaFrame386(
|
|
#endif
|
|
lpbi, pbPrev, pbDib, pbRle, maxRun, minJump,
|
|
gRgbTol.hpTable, tolTemporal, tolSpatial);
|
|
|
|
|
|
lpbi->biHeight = biHeight;
|
|
lpbi->biSizeImage += cbJump; // adjust size to include JUMP!
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Next is a table that, for each pair of palette entries, helps determine
|
|
if two colours are close enough to be merged to a single colour
|
|
|
|
Let's say the first pixel of a frame is black, and the same pixel in the
|
|
next frame is gray. Should you bother painting that gray pixel or let it
|
|
stay black because it's close enough? With this table, you have 2 palettes
|
|
(one for each of the two frames you are comparing, or possibly two identical
|
|
palettes if you are filtering a single DIB) and a table associated with
|
|
those palettes. You can index into the table with the colour number of the
|
|
pixel in the first frame and the colour number of the pixel in the second
|
|
frame. The table value will be a number representing how different those
|
|
two colours are.
|
|
|
|
|Red1 - Red2|^2 + |Green1 - Green2|^2 + |Blue1 - Blue2|^2
|
|
|
|
is that value (sum of squares of differences). As soon as you start
|
|
using this table with a pair of palettes, those hpals are put in this
|
|
structure so that you know what pair of palettes the table is built with.
|
|
If you change a palette, you need to recompute the table. BUT: you don't
|
|
build the table at the beginning, you do it on demand. Initially, the
|
|
table is filled with a value of UNCOMPUTED, and as the values are needed,
|
|
they are put into the table, so a second call to the CloseEnough routine
|
|
with the same colours will exit extremely quickly with no calculations!
|
|
|
|
Prepare the table for looking up quickly the sum of squares of colours
|
|
of two palette entries (possibly in different palettes) */
|
|
|
|
|
|
DWORD NEAR _fastcall RgbCompare(RGBQUAD rgb1, RGBQUAD rgb2)
|
|
{
|
|
DWORD sum=0;
|
|
|
|
//
|
|
// lets do some magic so the compiler generates "good" code.
|
|
//
|
|
#define SUMSQ(a,b) \
|
|
if (a > b) \
|
|
sum += (WORD)(a-b) * (WORD)(a-b); \
|
|
else \
|
|
sum += (WORD)(b-a) * (WORD)(b-a);
|
|
|
|
SUMSQ(rgb1.rgbRed, rgb2.rgbRed);
|
|
SUMSQ(rgb1.rgbGreen, rgb2.rgbGreen);
|
|
SUMSQ(rgb1.rgbBlue, rgb2.rgbBlue);
|
|
|
|
return sum;
|
|
}
|
|
|
|
BOOL NEAR PASCAL MakeRgbTable(LPBITMAPINFOHEADER lpbi)
|
|
{
|
|
UINT i, j;
|
|
int n=0;
|
|
DWORD tol;
|
|
|
|
if (!lpbi)
|
|
return FALSE;
|
|
|
|
if (lpbi->biClrUsed == 0)
|
|
lpbi->biClrUsed = 1 << lpbi->biBitCount;
|
|
|
|
/* If the palette passed in has a different number of colours than */
|
|
/* the one in the table, we obviously need a new table */
|
|
|
|
if (gRgbTol.hpTable == NULL ||
|
|
(int)lpbi->biClrUsed != gRgbTol.ClrUsed ||
|
|
_fmemcmp(lpbi+1, gRgbTol.argbq, gRgbTol.ClrUsed * sizeof(RGBQUAD)))
|
|
{
|
|
if (gRgbTol.hpTable == NULL)
|
|
{
|
|
gRgbTol.hpTable = (LPVOID)GlobalAllocPtr(GHND|GMEM_SHARE, 256L * 256L * sizeof(DWORD));
|
|
|
|
if (gRgbTol.hpTable == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
gRgbTol.ClrUsed = (int)lpbi->biClrUsed; // get the actual colours
|
|
|
|
for (i = 0; i < (UINT)gRgbTol.ClrUsed; i++)
|
|
gRgbTol.argbq[i] = ((LPRGBQUAD)(lpbi + 1))[i];
|
|
|
|
for (i = 0; i < (UINT)gRgbTol.ClrUsed; i++)
|
|
{
|
|
for (j = 0; j <= i; j++)
|
|
{
|
|
tol = RgbCompare(gRgbTol.argbq[i], gRgbTol.argbq[j]);
|
|
|
|
gRgbTol.hpTable[256 * i + j] = tol;
|
|
gRgbTol.hpTable[256 * j + i] = tol;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
// ---- DeltaFrameC --------------------------------------------------------
|
|
|
|
#define TolLookUp(p, a, b) ( ((LPDWORD)p)[a * 256 + b] )
|
|
|
|
LPBYTE EncodeFragment(LPBYTE pIn, int len, LPBYTE pOut, LPDWORD pTol, DWORD tolerance, UINT maxrun);
|
|
LPBYTE EncodeAbsolute(LPBYTE pbDib, int len, LPBYTE pbRle);
|
|
int FindFragmentLength(LPBYTE pIn, LPBYTE pPrev, int len, UINT maxjmp, LPDWORD pTol, DWORD tol, PDWORD prunlen);
|
|
|
|
// rle format:
|
|
// byte 1: 0 - escape
|
|
// byte 2: 0 - eol
|
|
// byte 2: 1 - eof
|
|
// byte 2: 2 - jump x, y (bytes 3, 4)
|
|
// byte 2: >2 - absolute run of pixels - byte 2 is length
|
|
// byte 1: >0 - repeat solid colour - byte 1 is length
|
|
// byte 2 is solid pixel to repeat
|
|
|
|
|
|
|
|
|
|
// compression - in df.asm for Win16
|
|
extern void DeltaFrameC(
|
|
LPBITMAPINFOHEADER lpbi,
|
|
LPBYTE pbPrev,
|
|
LPBYTE pbDib,
|
|
LPBYTE pbRle,
|
|
UINT MaxRunLength,
|
|
UINT MinJumpLength,
|
|
LPDWORD TolTable,
|
|
DWORD tolTemporal,
|
|
DWORD tolSpatial)
|
|
{
|
|
int WidthBytes = (lpbi->biWidth+3) & (~3);
|
|
int x, y;
|
|
LPBYTE pbRle_Orig = pbRle;
|
|
|
|
if ((MaxRunLength == 0) || (MaxRunLength > 255)) {
|
|
MaxRunLength = 255;
|
|
}
|
|
|
|
if (pbPrev == NULL) {
|
|
|
|
// no previous frame, just encode each line spatially
|
|
|
|
for (y = lpbi->biHeight; y > 0; y--) {
|
|
|
|
pbRle = EncodeFragment(
|
|
pbDib,
|
|
lpbi->biWidth,
|
|
pbRle,
|
|
TolTable,
|
|
tolSpatial,
|
|
MaxRunLength);
|
|
|
|
// don't bother to insert an EOL if we are about to insert EOF
|
|
if (y > 0) {
|
|
* (WORD FAR *)pbRle = RLE_ESCAPE | (RLE_EOL << 8);
|
|
pbRle += sizeof(WORD);
|
|
}
|
|
|
|
pbDib += WidthBytes;
|
|
}
|
|
} else {
|
|
int jumpX = 0;
|
|
int jumpY = 0;
|
|
int frag, runlen;
|
|
|
|
|
|
for (y = 0; y < lpbi->biHeight; y++) {
|
|
|
|
x = 0;
|
|
|
|
while (x < lpbi->biWidth) {
|
|
|
|
// see how much is not the same as the previous frame,
|
|
// followed by how much is the same. frag is the length of
|
|
// the not-similar fragment; runlen is the length of the
|
|
// similar fragment.
|
|
|
|
frag = FindFragmentLength(
|
|
pbDib,
|
|
pbPrev,
|
|
lpbi->biWidth - x,
|
|
MinJumpLength,
|
|
TolTable,
|
|
tolTemporal,
|
|
&runlen
|
|
);
|
|
|
|
if (frag == 0) {
|
|
|
|
// no fragment, just a jump over the similar pixels.
|
|
//add up jumps until we need to output them
|
|
jumpX += runlen;
|
|
x += runlen;
|
|
pbPrev += runlen;
|
|
pbDib += runlen;
|
|
} else {
|
|
|
|
// output any saved jumps
|
|
if (jumpX < 0) {
|
|
|
|
// don't jump backwards - eol and jump forwards
|
|
*(WORD FAR *)pbRle = RLE_ESCAPE | (RLE_EOL << 8);
|
|
pbRle += sizeof(WORD);
|
|
|
|
// jump is now across to current position,
|
|
// and one fewer lines.
|
|
jumpX = x;
|
|
jumpY--;
|
|
}
|
|
|
|
while (jumpX + jumpY) {
|
|
int delta;
|
|
|
|
|
|
* (WORD FAR *)pbRle = RLE_ESCAPE | (RLE_JMP << 8);
|
|
pbRle += sizeof(WORD);
|
|
|
|
// max jump size is 255
|
|
|
|
delta = min(255, jumpX);
|
|
*pbRle++ = (BYTE) delta;
|
|
jumpX -= delta;
|
|
|
|
delta = min(255, jumpY);
|
|
*pbRle++ = (BYTE) delta;
|
|
jumpY -= delta;
|
|
|
|
}
|
|
|
|
// output the different fragment as a combination
|
|
// of solid runs and absolute pixels
|
|
pbRle = EncodeFragment(
|
|
pbDib,
|
|
frag,
|
|
pbRle,
|
|
TolTable,
|
|
tolSpatial,
|
|
MaxRunLength);
|
|
x += frag;
|
|
pbDib += frag;
|
|
pbPrev += frag;
|
|
}
|
|
}
|
|
|
|
// end-of-line
|
|
jumpY++;
|
|
|
|
// advance past DWORD-rounding bytes
|
|
pbPrev += (WidthBytes - lpbi->biWidth);
|
|
pbDib += (WidthBytes - lpbi->biWidth);
|
|
|
|
//adjust jumpX
|
|
jumpX -= x;
|
|
|
|
}
|
|
}
|
|
|
|
// end-of-frame
|
|
* (WORD FAR *)pbRle = RLE_ESCAPE | (RLE_EOF << 8);
|
|
pbRle += sizeof(WORD);
|
|
|
|
// update lpbi to correct size and format
|
|
lpbi->biSizeImage = (DWORD) (pbRle - pbRle_Orig);
|
|
lpbi->biCompression = BI_RLE8;
|
|
}
|
|
|
|
|
|
//
|
|
// encode a sequence of pixels as a mixture of solid runs and absolute
|
|
// pixels. write the rle data to pbRle and return pointer to the next
|
|
// available rle buffer.
|
|
LPBYTE
|
|
EncodeFragment(
|
|
LPBYTE pbDib,
|
|
int width,
|
|
LPBYTE pbRle,
|
|
LPDWORD TolTable,
|
|
DWORD tolerance,
|
|
UINT MaxRunLength
|
|
)
|
|
{
|
|
int maxrun, run;
|
|
BYTE px;
|
|
|
|
while (width > 0) {
|
|
|
|
maxrun = min(255, width);
|
|
MaxRunLength = min((int)MaxRunLength, maxrun);
|
|
|
|
px = *pbDib;
|
|
|
|
for (run = 0; run < maxrun; run++, pbDib++) {
|
|
|
|
// the same or similar ? - use tolerance table to compare pixel
|
|
// rgb values
|
|
// We're allowed a run of 255 if they're exact, but only a run of
|
|
// MaxRunLength if they're not exact, only close
|
|
if (px == *pbDib)
|
|
continue;
|
|
if (TolLookUp(TolTable,px,*pbDib) <= tolerance &&
|
|
run < (int)MaxRunLength)
|
|
continue;
|
|
|
|
// not close enough - end run
|
|
break;
|
|
}
|
|
|
|
// we have found the end of a run of identical pixels
|
|
|
|
// if the run is one pixel, then we switch into absolute mode.
|
|
// however, we cannot encode absolute runs of less than RLE_RUN
|
|
// pixels (the runlength code is an escape code and must not coincide
|
|
// with RLE_JMP, RLE_EOL and RLE_EOF.
|
|
|
|
if ((run > 1) || (width < RLE_RUN)) {
|
|
|
|
// write out run length and colour
|
|
* (WORD FAR *)pbRle = run | (px << 8);
|
|
pbRle += sizeof(WORD);
|
|
|
|
width -= run;
|
|
|
|
} else {
|
|
|
|
// we have a 'run' of one pixel - back up to point at this.
|
|
pbDib--;
|
|
|
|
// write out an absolute run. now we are in abs mode, we need
|
|
// a solid run of at least 4 pixels for it to be worth leaving
|
|
// and re-entering abs mode
|
|
|
|
for (run = 0; run < maxrun; run++) {
|
|
|
|
// at the end of the fragment ?
|
|
if ((maxrun - run) < 4) {
|
|
// yes - so no point in looking for a solid run -
|
|
// just dump all the remainder as an absolute block
|
|
pbRle = EncodeAbsolute(pbDib, maxrun, pbRle);
|
|
pbDib += maxrun;
|
|
width -= maxrun;
|
|
break;
|
|
}
|
|
|
|
px = pbDib[run];
|
|
if ( (TolLookUp(TolTable,px,pbDib[run + 1]) <= tolerance) &&
|
|
(TolLookUp(TolTable,px,pbDib[run + 2]) <= tolerance) &&
|
|
(TolLookUp(TolTable,px,pbDib[run + 3]) <= tolerance)) {
|
|
|
|
// we have run bytes to encode followed by four
|
|
// similar pixels
|
|
|
|
pbRle = EncodeAbsolute(pbDib, run, pbRle);
|
|
pbDib += run;
|
|
width -= run;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pbRle;
|
|
}
|
|
|
|
LPBYTE
|
|
EncodeAbsolute(LPBYTE pbDib, int runlen, LPBYTE pbRle)
|
|
{
|
|
if (runlen < RLE_RUN) {
|
|
|
|
// cannot encode absolute runs of less than RLE_RUN as it
|
|
// conflicts with other rle escapes - so encode each pixel
|
|
// as a run of 1 of that pixel
|
|
int i;
|
|
for (i = 0; i < runlen; i++) {
|
|
|
|
* (WORD FAR *) pbRle = 1 | ((*pbDib++) << 8);
|
|
pbRle += sizeof(WORD);
|
|
}
|
|
return pbRle;
|
|
|
|
}
|
|
|
|
// absolute run of > RLE_RUN
|
|
|
|
* (WORD FAR *)pbRle = RLE_ESCAPE | (runlen << 8);
|
|
pbRle += sizeof(WORD);
|
|
|
|
while (runlen >= 2) {
|
|
* (WORD FAR *) pbRle = * (WORD UNALIGNED FAR *)pbDib;
|
|
pbRle += sizeof(WORD);
|
|
pbDib += sizeof(WORD);
|
|
runlen -= 2;
|
|
}
|
|
|
|
// remember to keep word alignment
|
|
if (runlen) {
|
|
*pbRle++ = *pbDib++;
|
|
*pbRle++ = 0;
|
|
}
|
|
|
|
return pbRle;
|
|
}
|
|
|
|
// count how many pixels are not the same as the previous frame, and how
|
|
// long is the run of similar pixels after it. We must find at least minjump
|
|
// similar pixels before we stop.
|
|
int
|
|
FindFragmentLength(
|
|
LPBYTE pIn,
|
|
LPBYTE pPrev,
|
|
int len,
|
|
UINT minjump,
|
|
LPDWORD pTol,
|
|
DWORD tol,
|
|
PDWORD prunlen
|
|
)
|
|
{
|
|
int x;
|
|
int run = 0;
|
|
|
|
for (x = 0; x < len; x++) {
|
|
|
|
|
|
if ((*pIn == *pPrev) || (TolLookUp(pTol, *pIn, *pPrev) <= tol)) {
|
|
run++;
|
|
} else {
|
|
|
|
// have we accumulated a run long enough to be worth
|
|
// returning ?
|
|
|
|
if (run >= (int)minjump) {
|
|
|
|
*prunlen = run;
|
|
return x - run;
|
|
} else {
|
|
run = 0;
|
|
}
|
|
}
|
|
pIn++;
|
|
pPrev++;
|
|
}
|
|
|
|
// end of line - did we find a run ?
|
|
if (run < (int) minjump) {
|
|
*prunlen = 0;
|
|
return len;
|
|
} else {
|
|
*prunlen = run;
|
|
return x - run;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endif
|